android 加载长图

丢,这个问题纠结老长时间了,后面还是没百度,自己瞎琢磨弄出来,感觉自己棒棒的

1.自定义View的方法,详情查看弘扬的博客,百度上广为流传的方法,额,这块得要在滑动这块处理下,要不然给人感觉是上下滑动时整体特别卡顿的,反正我比较渣,这块我没搞好。哈哈,贴出这份百度上广为流传的代码



import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;

import androidx.annotation.Nullable;

import java.io.IOException;
import java.io.InputStream;

public class BigView extends View implements GestureDetector.OnGestureListener, View.OnTouchListener {

    private Rect mRect;//可显示区域的位置,当前可显示多少图片内容
    private BitmapFactory.Options mOptions;
    private GestureDetector mGestureDetector;
    private Scroller mScroller;
    private int mImageWidth;
    private int mImageHeight;
    private BitmapRegionDecoder mDecoder;//核心类
    private int mViewWidth;
    private int mViewHeight;
    private float mScale;
    private Bitmap bitmap;

    public BigView(Context context) {
        this(context, null, 0);
    }

    public BigView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BigView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //指定要加载的区域
        mRect = new Rect();
        //需要复用
        mOptions = new BitmapFactory.Options();
        //手势识别类
        mGestureDetector = new GestureDetector(context, this);
        //设置onTouchListener
        setOnTouchListener(this);


        //滑动帮助
        mScroller = new Scroller(context);

    }

    /**
     * 由使用者输入一张图片
     */
    public void setImage(InputStream is,int type) {
        //先读取原图片的信息   高,宽
        mOptions.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is, null, mOptions);
        mImageWidth = mOptions.outWidth;
        mImageHeight = mOptions.outHeight;
        if(mImageWidth==-1||mImageHeight==-1){
            //暂时不知道什么鬼,先写死为好,丢,反正目前需求就2张本地图
            switch (type){
                case 0:
                    mImageWidth=750;
                    mImageHeight=2200;
                    break;
                case 1:
                    mImageWidth=750;
                    mImageHeight=3569;
                    break;
            }
        }
        //开启复用
        mOptions.inMutable = true;
        //设置格式成RGB_565,因为565 存储像素点占用内存小,一个像素点只需要两个字节
        mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
        mOptions.inJustDecodeBounds = false;

        //创建一个区域解码器
        try {
            mDecoder = BitmapRegionDecoder.newInstance(is, false);
        } catch (IOException e) {
            e.printStackTrace();
        }


        requestLayout();
    }
    /**
     * 在测量的时候把我们需要的内存区域获取到  存入到mRect中
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取测量的view的大小
        mViewWidth = getMeasuredWidth();
        mViewHeight = getMeasuredHeight();

        //确定要加载的图片的区域
        mRect.left = 0;
        mRect.top = 0;
        mRect.right = mImageWidth;
        //获取一个缩放因子
        mScale = mViewWidth / (float) mImageWidth;
        //高度就根据缩放比进行获取
        mRect.bottom = (int) (mViewHeight / mScale);

    }

    /**
     * 画出内容
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //如果解码器拿不到,表示没有设置过要显示的图片
        if (null == mDecoder) {
            return;
        }
        //复用上一张bitmap
        mOptions.inBitmap = bitmap;
        //解码指定的区域
        bitmap = mDecoder.decodeRegion(mRect, mOptions);
        //把得到的矩阵大小的内存进行缩放  得到view的大小
        Matrix matrix = new Matrix();
        matrix.setScale(mScale, mScale);
        //画出来
        canvas.drawBitmap(bitmap, matrix, null);


    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        //交给手势处理
        return mGestureDetector.onTouchEvent(event);
    }


    /**
     * 手按下的回调
     *
     * @param e
     * @return
     */
    @Override
    public boolean onDown(MotionEvent e) {
        //如果移动还没有停止,强制停止
        if (!mScroller.isFinished()) {
            mScroller.forceFinished(true);
        }
        //继续接收后续事件
        return true;
    }


    /**
     * @param e1        接下
     * @param e2        移动
     * @param distanceX 左右移动时的距离
     * @param distanceY 上下移动时的距离
     * @return
     */
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        //上下移动的时候,需要改变显示区域   改mRect
        mRect.offset(0, (int) distanceY);
        //处理移动时已经移到了两个顶端的问题
        if (mRect.bottom > mImageHeight) {
            mRect.bottom = mImageHeight;
            mRect.top = mImageHeight - (int) (mViewHeight / mScale);
        }
        if (mRect.top < 0) {
            mRect.top = 0;
            mRect.bottom = (int) (mViewHeight / mScale);
        }
        invalidate();
        return false;
    }

    /**
     * 处理惯性问题
     *
     * @param e1
     * @param e2
     * @param velocityX 每秒移动的x点
     * @param velocityY
     * @return
     */
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        //做计算  -velocityY  正负号问题,相反的
        // ( 按下手指不拿开,屏幕跟着手势方向,若松开,方向则向相反方向滑动 ) 故 使用 负值才能正常使用
        mScroller.fling(0, mRect.top,
                0, (int) -velocityY,
                0, 0,
                0, mImageHeight - (int) (mViewHeight / mScale));
        return false;
    }

    /*
    使用上一个接口的计算结果
     */
    @Override
    public void computeScroll() {
        if (mScroller.isFinished()) {
            return;
        }
        //true 表示当前滑动还没有结束
        if (mScroller.computeScrollOffset()) {
            mRect.top = mScroller.getCurrY();
            mRect.bottom = mRect.top + (int) (mViewHeight / mScale);
            invalidate();
        }
    }

    @Override
    public void onShowPress(MotionEvent e) {
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {

    }
}

2.额,百度上还有个说法是用WebView的方法,这个是动态获取的,正常图片如果后台处理是富文本编辑下的话,则可以使用此方法全屏显示

String webs = "<!DOCTYPE html>\n"
                                + "<html><meta name=\"viewport\" content=\"initial-scale=1.0, minimum-scale=0.0, maximum-scale=1.0, user-scalable=no,vertical-align: bottom\"/>\n"
                                + "<head><meta charset=\"utf-8\"> <head>\n" + "<body><style>*{ width: 100%; margin: 0; padding: 0; box-sizing: border-box;} img{ width: 100%,vertical-align: bottom;display: block;}</style>"
                                + 图片标签+ "</body>\n</html>";
webView.loadDataWithBaseURL(null, webs, "text/html", "UTF-8", null);

3.这种我是挺推荐的,我是这么实现的,不存在OOM的问题,滑动也贼流畅,原理也简单,就是对bitmap进行压缩,然后计算图片宽高比例与屏幕比例得出来的值重新绘制一个新的bitmap出来,嗯,就这么简单,直接贴代码

 private void compressBitmap() {
        //RGB565压缩
        BitmapFactory.Options mOptions = new BitmapFactory.Options();
        mOptions.inJustDecodeBounds = true;//表示解析图片的时候,只解析长度和宽度,不载入图片,这样就节省内存开支。
        mOptions.inPreferredConfig = Bitmap.Config.RGB_565;//前文提到的表格一目了然,这样会节省一半的内存。
        mOptions.inSampleSize = calculateInSampleSize(mOptions, MyApplication.defaultWidth, MyApplication.defaultHidth);//计算缩放的比例,inSampleSize只能是2的整数次幂,如果不是的话,向下取得最大的2的整数次幂,
        // 比如比例为7,向下寻找2的整数次幂,就是4。如果缩放比例是4的话,7.9M的那张图片最后占用的内存会是7.9/16=0.49M,完全不用担心OOM的发生。
        mOptions.inJustDecodeBounds = false;//计算好压缩比例后,去加载解析原图。
        switch (type) {
            case 0:
                bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.donate_fate_strategy, mOptions);//解析文件得到Bitmap。
                //  getBinding().ivLargeImg.setBackgroundResource(图片);
                break;
            case 1:
                bitmap = BitmapFactory.decodeResource(getResources(), 图片, mOptions);//解析文件得到Bitmap。
                //   getBinding().ivLargeImg.setBackgroundResource(R.mipmap.donate_fate_use);
                break;
        }
        float imgWhite = mOptions.outWidth;
        float imgHeight = mOptions.outHeight;
//
//        //根据屏幕宽高比算出当前ImagView的宽高比
//        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getBinding().ivLargeImg.getLayoutParams();
//        layoutParams.width = MyApplication.defaultWidth;
//        layoutParams.height = Float.valueOf((MyApplication.defaultWidth * imgHeight) / imgWhite).intValue();
//        getBinding().ivLargeImg.setLayoutParams(layoutParams);
        ivBitmap = BitmapUtil.changeBitmapSize(bitmap, MyApplication.defaultWidth, Float.valueOf((MyApplication.defaultWidth * imgHeight) / imgWhite).intValue());
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
             
                if (ValidateUtils.isValidate(ivBitmap)) {
                    getBinding().ivLargeImg.setImageBitmap(ivBitmap);
                }

            }
        });
    }

    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            final int heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);
            inSampleSize = heightRatio > widthRatio ? heightRatio : widthRatio;
        }
        return inSampleSize;
    }

使用完后记得在onDestroy中释放掉,额,不推荐在onPause中释放,因为容易触发

 java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@84709c2这个错误,

释放代码

      if (ValidateUtils.isValidate(bitmap) && !bitmap.isRecycled()) {
            bitmap.recycle();
            bitmap = null;
        }
        if (ValidateUtils.isValidate(ivBitmap) && !ivBitmap.isRecycled()) {
            ivBitmap.recycle();
            ivBitmap = null;
        }

额,这个ValidateUtils.isValidate,就是一个判空操作来的,不要管

然后布局文件代码是这样搞得:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="none"
            android:fillViewport="true">
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">
                <ImageView
                    android:id="@+id/iv_large_img"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:visibility="gone"/>
           
                <ImageView
                    android:id="@+id/iv_back"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:paddingLeft="11.5dp"
                    android:paddingTop="16.5dp"
                    android:paddingRight="11.5dp"
                    android:paddingBottom="16.5dp"
                    android:src="@mipmap/img_donate_back_black" />
            </RelativeLayout>

        </ScrollView>

</layout>

嗯,大概就这样子,丢,继续加班去,最近都没时间学kotlin了,难受

猜你喜欢

转载自blog.csdn.net/z936689039/article/details/106749936