性能优化<第八篇>:大图优化

假如有一张100M的图片,要求显示在手机中,那么该如何做呢?
首先,很容易想到的是先压缩图片,再加载被压缩后的图片,但是压缩后的图片很容易造成图片失真,不仅如此,由于图片很大,如果将整张图片放在手机屏幕上,很难看清图片的真实内容。

再假如,有一张很长的图片(长图),比如文章内容,宽度很窄,长度特别长,我们想通过手指拖动的方式浏览长图,还要保证不会发生OOM。

那么,怎么通过代码可以实现:通过拖动的方式浏览大图,并且不会发生OOM呢?

1、首先,需要认识一下区域解码器(BitmapRegionDecoder):

    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(inputStream, false);
    Bitmap bitmap = decoder.decodeRegion(rect, options);

所谓区域,也就是使用Rect对象在图片上指定某个区域,每次只会解析指定区域的图片,这样的话,缓存中始终只有大图的部分区域。

2、自定义一个View,使用区域解码器,加载并显示大图的部分区域;

<下面直接贴出源码>

3、添加手势GestureDetector

GestureDetector detector = new GestureDetector(context, this);
@Override
public boolean onTouchEvent(MotionEvent event) {
    // 手势和触摸事件绑定
    return detector.onTouchEvent(event);
}

重写几个重要的方法:onScrollonDownonShowPressonSingleTapUponFlingcomputeScroll

4、效果展示

图片宽和高都很大的情况:

63.gif

图片宽度很大,高度很短的情况:

64.gif

图片宽度很短,高度很长的情况:

65.gif

5、源码,代码已经调好,较为稳定,支持大图、长图

package com.example.bitimageoptimization;

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.util.Log;
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 BitImageView extends View implements GestureDetector.OnGestureListener {

    private static final String TAG = BitImageView.class.getSimpleName();

    private BitmapFactory.Options options;

    // 控件宽度
    private int measuredWidth = 0;

    // 控件高度
    private int measuredHeight = 0;

    // 区域解码器的矩形参数
    private Rect rect;

    // 手势类
    private GestureDetector detector;

    // 区域解码器
    private BitmapRegionDecoder decoder;

    private Scroller scroller;

    // 图片宽度
    private int imageWidth;

    // 图片高度
    private int imageHeight;

    // 图片宽度的缩放因子
    private float widthScale = 1;

    // 图片高度的缩放因子
    private float heightScale = 1;

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

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

    public BitImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        detector = new GestureDetector(context, this);
        options = new BitmapFactory.Options();
        scroller = new Scroller(context);
    }

    /**
     * 加载图片
     *
     * @param inputStream inputStream
     */
    public void loadImage(InputStream inputStream) {
        options.inJustDecodeBounds = true;
        // 解码后的bitmap,只有属性(宽、高),没有大小,不占用内存
        BitmapFactory.decodeStream(inputStream, null, options);
        // 图片宽度
        imageWidth = options.outWidth;
        if (imageWidth < measuredWidth) {
            widthScale = measuredWidth / (imageWidth * 1.0f);
            imageWidth = measuredWidth;
        }
        // 图片高度
        imageHeight = options.outHeight;
        if (imageHeight < measuredHeight) {
            heightScale = measuredHeight / (imageHeight * 1.0f);
            imageHeight = measuredHeight;
        }
        // 可复用开关打开
        options.inMutable = true;
        options.inJustDecodeBounds = false;
        // 根据图片大小,初始化矩形参数
        rect = initRect(imageWidth, imageHeight);
        try {
            // 初始化区域解码器
            decoder = BitmapRegionDecoder.newInstance(inputStream, false);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 重新执行测量、布局、绘制
        requestLayout();
    }

    /**
     * 初始化区域解码器的矩形参数
     *
     * @param imageWidth imageWidth
     * @param imageHeight imageHeight
     * @return
     */
    private Rect initRect(int imageWidth, int imageHeight) {
        Rect rect = new Rect();
        if (imageWidth > measuredWidth) { // 图片的宽度大于控件宽度
            rect.left = (imageWidth - measuredWidth) / 2;
            rect.right = measuredWidth + rect.left;
        } else { // 如果相等
            rect.left = 0;
            rect.right = measuredWidth;
        }
        if (imageHeight > measuredHeight) { // 图片的高度大于控件高度
            rect.top = (imageHeight - measuredHeight) / 2;
            rect.bottom = measuredHeight + rect.top;
        } else { // 如果相等
            rect.top = 0;
            rect.bottom = measuredHeight;
        }

        return rect;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measuredWidth = getMeasuredWidth();
        measuredHeight = getMeasuredHeight();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (decoder == null) { // 区域解码器的对象不能为空
            Log.i(TAG, "decoder is null");
            return;
        }
        Bitmap bitmap = decoder.decodeRegion(rect, options);
        Matrix matrix = new Matrix();
        matrix.setScale(widthScale * heightScale, widthScale * heightScale);
        if (bitmap != null) {
            canvas.drawBitmap(bitmap, matrix, null);
        }
    }

    @Override
    public boolean onDown(MotionEvent e) {
        if (!scroller.isFinished()) {
            scroller.forceFinished(false);
        }
        return true;
    }

    @Override
    public void onShowPress(MotionEvent e) {

    }

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

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        if (rect == null) {
            Log.i(TAG, "rect is null");
            return true;
        }
        rect.offset((int) distanceX, (int) distanceY);
        if (rect.left < 0) {
            rect.left = 0;
            rect.right = measuredWidth;
        }
        if (rect.top < 0) {
            rect.top = 0;
            rect.bottom = measuredHeight;
        }
        if (rect.right > imageWidth) {
            rect.right = imageWidth;
            rect.left = imageWidth - measuredWidth;
        }
        if (rect.bottom > imageHeight) {
            rect.bottom = imageHeight;
            rect.top = imageHeight - measuredHeight;
        }
        invalidate(); // 重新绘制
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {

    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        if (rect == null) {
            Log.i(TAG, "onFling, rect is null");
            return true;
        }
        if (scroller == null) {
            Log.i(TAG, "onFling, scroller is null");
            return true;
        }
        scroller.fling(rect.left, rect.top, (int) -velocityX, (int) -velocityY, 0, imageWidth - measuredWidth, 0, imageHeight - measuredHeight);
        return false;
    }

    /**
     * 在滚动过程中的处理
     */
    @Override
    public void computeScroll() {
        if (scroller.isFinished()) { // 如果已经停止滚动,那么就不处理
            return;
        }
        if (scroller.computeScrollOffset()) {
            rect.left = scroller.getCurrX();
            rect.top = scroller.getCurrY();
            rect.right = rect.left + measuredWidth;
            rect.bottom = rect.top + measuredHeight;
            invalidate();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 手势和触摸事件绑定
        return detector.onTouchEvent(event);
    }

}

调试代码:

    load_image.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            InputStream inputStream = null;
            try{
                 inputStream = getAssets().open("big_image1.jpg");
                bigImageView.loadImage(inputStream);
            }catch(Exception e) {
                e.printStackTrace();
            } finally {
                if(inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    });

6、Github地址

https://github.com/NoBugException/BitImageOptimization

[本章完...]

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容