美文网首页
性能优化<第八篇>:大图优化

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

作者: NoBugException | 来源:发表于2021-06-26 22:58 被阅读0次

假如有一张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

[本章完...]

相关文章

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

    假如有一张100M的图片,要求显示在手机中,那么该如何做呢?首先,很容易想到的是先压缩图片,再加载被压缩后的图片,...

  • 常用的后端性能优化六种方式:缓存化+服务化+异步化等

    性能优化专题 前端性能优化 数据库性能优化 jvm和多线程优化 架构层面优化 缓存性能优化 常用的后端性能优化六大...

  • Android性能优化 - 消除卡顿

    性能优化系列阅读 Android性能优化 性能优化 - 消除卡顿 性能优化 - 内存优化 性能分析工具 - Tra...

  • Android性能优化 - 内存优化

    性能优化系列阅读 Android性能优化 性能优化 - 消除卡顿 性能优化- 内存优化 性能分析工具 - Trac...

  • 前端性能优化(中)

    性能优化调研系列文章 《前端性能优化(上)》 《前端性能优化(中)》 《前端性能优化(下)》 《前端性能优化(上)...

  • 前端性能优化(下)

    性能优化调研系列文章 《前端性能优化(上)》 《前端性能优化(中)》 《前端性能优化(下)》 《前端性能优化(中)...

  • webpack 性能优化

    webpack性能优化 开发环境性能优化 生产环境性能优化 开发环境性能优化 优化打包构建速度 优化调试功能 生产...

  • iOS性能优化 - 整理

    本文主要包含: 性能优化 - 卡顿性能优化 - 耗电优化性能优化 - APP启动优化安装包瘦身 一  性能优化 -...

  • Awesome Extra

    性能优化 性能优化模式 常见性能优化策略的总结 Spark 性能优化指南——基础篇 Spark 性能优化指南——高...

  • 【React.js 20】React性能优化

    React性能优化 React性能优化主要分三块: React 组件性能优化 属性传递优化针对单组件性能优化,很多...

网友评论

      本文标题:性能优化<第八篇>:大图优化

      本文链接:https://www.haomeiwen.com/subject/tvctultx.html