(六)巨图加载 —— 优化图片内存

版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。

一、BitmapRegionDecoder

当一张图片的分辨率特别大的时候,我们把这张图完整的加载进来,在屏幕上并没有办法显示这张图片的全部内容。但是,全部加载进来的话,还会占用较大的内存,严重时候会产生 OOM。

这时候我们可以使用 BitmapRegionDecoder,进行加载图片的一部分内容进行显示。

基本用法:

    private void load() {

        InputStream inputStream = null;

        try {
            inputStream = getAssets().open("big.png");

            //isShareable: 输入流是否共享,false 内部拷贝输入流
            BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(inputStream, false);

            Rect rect = new Rect();
            rect.left = 0;
            rect.top = 0;
            rect.right = 100;
            rect.bottom = 100;

            decoder.decodeRegion(rect, null);

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

BitmapRegionDecoder 的 newInstance 有一个参数是 isShareable,表示输入流是否是同一个,true 表示的是同一个,我们如果对代码中的输入流进行关闭,BitmapRegionDecoder 中的输入流也将关闭。所以一般设为 false。

BitmapRegionDecoder 的 decodeRegion 方法,需要两个参数,分别是 Rect 和 BitmapFactory.Options。Rect 表示要加载的巨图区域,起始点在图片的左上角。

这里写图片描述
注:红色区域表示巨图,黑色区域表示 Rect 截取的区域。

二、自定义控件加载巨图

这边实现自定义控件来加载巨图,这边只针对长长图,不正对宽长图进行处理,思路一样,有需要的可以自己扩展。

BigImageView :

package com.xiaoyue.bigimage;

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.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;

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

/**
 * 加载长度的 View
 */
public class BigImageView extends View {

    //指定要加载区域的矩形
    private Rect mRect;
    //区域解码器
    private BitmapRegionDecoder mDecoder;
    //解码图片的配置
    private BitmapFactory.Options mOptions;

    //图片的宽高
    private int mImageWidth;
    private int mImageHeight;
    //图片缩放系数
    private float mScale;

    //控件的宽高
    private int mViewWidth;
    private int mViewHeight;
    //控件显示的图片
    private Bitmap mBitmap;

    //滑动
    private final Scroller mScroller;

    public BigImageView(Context context) {
        this(context, null);
    }

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

    public BigImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

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

    /**
     * 初始化
     */
    private void init() {
        mRect = new Rect();
        mOptions = new BitmapFactory.Options();
    }

    /**
     * 设置要显示的图片的输入流
     * @param inputStream 图片文件的输入流
     */
    public void setImage(InputStream inputStream) {
        //先获取图片的宽高
        mOptions.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(inputStream, null, mOptions);
        mImageWidth = mOptions.outWidth;
        mImageHeight = mOptions.outHeight;

        //设置允许复用
        mOptions.inMutable = true;
        //设置格式,减小内存
        mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
        //需设置回来,否则无法解析图片
        mOptions.inJustDecodeBounds = false;

        try {
            mDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
        } catch (IOException e) {
            e.printStackTrace();
        }

        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //获取测量宽高
        mViewWidth = getMeasuredWidth();
        mViewHeight = getMeasuredHeight();

        //还没设置 Image
        if(mDecoder == null){
            return;
        }

        //确定要加载的图片的区域
        mRect.left = 0;
        mRect.top = 0;
        mRect.right = mImageWidth;
        //获得缩放因子
        mScale = mViewWidth / (float) mImageWidth;
        // 需要加载的高 * 缩放因子 = 视图view的高
        // x * mScale = mViewHeight
        mRect.bottom = (int) (mViewHeight / mScale);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //还没设置 Image
        if(mDecoder == null){
            return;
        }
        //复用上一张bitmap
        mOptions.inBitmap = mBitmap;
        //解码指定区域
        mBitmap = mDecoder.decodeRegion(mRect, mOptions);
        //使用矩阵 对图片进行 缩放
        Matrix matrix = new Matrix();
        matrix.setScale(mScale, mScale);
        //画出来
        canvas.drawBitmap(mBitmap, matrix, null);
    }

}

定义一个 BigImageView 继承 View,添加基本的构造函数,初始化方法。采用了巨图加载的 BitmapRegionDecoder 进行部分区域的加载,还对图片的宽进行了缩放。

另外,比较重要的一点是,通过 BitmapFactory.Options 设置了Bitmap 内存的复用,减小了 Bitmap 内存申请次数。

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.xiaoyue.bigimage.BigImageView
        android:id="@+id/big_image_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</android.support.constraint.ConstraintLayout>

效果:
这里写图片描述
注:背后文字是一张长图片。

这样就简单的吧图片截取出来并显示,我们还需要为他加上滑动,这边使用的是手势处理 GestureDetector 进行处理。

BigImageView:

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

    //指定要加载区域的矩形
    private Rect mRect;
    //区域解码器
    private BitmapRegionDecoder mDecoder;
    //解码图片的配置
    private BitmapFactory.Options mOptions;

    //图片的宽高
    private int mImageWidth;
    private int mImageHeight;
    //图片缩放系数
    private float mScale;

    //控件的宽高
    private int mViewWidth;
    private int mViewHeight;
    //控件显示的图片
    private Bitmap mBitmap;

    //手势处理
    private GestureDetector mGestureDetector;
    //滑动
    private final Scroller mScroller;

    public BigImageView(Context context) {
        this(context, null);
    }

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

    public BigImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        init();
        //手势
        mGestureDetector = new GestureDetector(context, this);
        setOnTouchListener(this);
        // 滑动帮助
        mScroller = new Scroller(context);
    }

    /**
     * 初始化
     */
    private void init() {
        mRect = new Rect();
        mOptions = new BitmapFactory.Options();
    }

    /**
     * 设置要显示的图片的输入流
     * @param inputStream 图片文件的输入流
     */
    public void setImage(InputStream inputStream) {
        //先获取图片的宽高
        mOptions.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(inputStream, null, mOptions);
        mImageWidth = mOptions.outWidth;
        mImageHeight = mOptions.outHeight;

        //设置允许复用
        mOptions.inMutable = true;
        //设置格式,减小内存
        mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
        //需设置回来,否则无法解析图片
        mOptions.inJustDecodeBounds = false;

        try {
            mDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
        } catch (IOException e) {
            e.printStackTrace();
        }

        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //获取测量宽高
        mViewWidth = getMeasuredWidth();
        mViewHeight = getMeasuredHeight();

        //还没设置 Image
        if(mDecoder == null){
            return;
        }

        //确定要加载的图片的区域
        mRect.left = 0;
        mRect.top = 0;
        mRect.right = mImageWidth;
        //获得缩放因子
        mScale = mViewWidth / (float) mImageWidth;
        // 需要加载的高 * 缩放因子 = 视图view的高
        // x * mScale = mViewHeight
        mRect.bottom = (int) (mViewHeight / mScale);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //还没设置 Image
        if(mDecoder == null){
            return;
        }
        //复用上一张bitmap
        mOptions.inBitmap = mBitmap;
        //解码指定区域
        mBitmap = mDecoder.decodeRegion(mRect, mOptions);
        //使用矩阵 对图片进行 缩放
        Matrix matrix = new Matrix();
        matrix.setScale(mScale, mScale);
        //画出来
        canvas.drawBitmap(mBitmap, matrix, null);

    }

    @Override
    public boolean onDown(MotionEvent e) {
        //如果滑动还没有停止 强制停止
        if (!mScroller.isFinished()){
            mScroller.forceFinished(true);
        }
        //继续接收后续事件
        return true;
    }

    @Override
    public void onShowPress(MotionEvent e) {

    }

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

    @Override
    public void onLongPress(MotionEvent e) {

    }

    /**
     * 手指在屏幕上拖动
     * @param e1 手指按下去的事件 -- 获取开始的坐标
     * @param e2 当前手势事件 -- 获取当前的坐标
     * @param distanceX  x方向移动的距离
     * @param distanceY  y方向移动的距离
     * @return
     */
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        // 手指从下往上 图片也要往上 distanceY是负数, top 和 bottom 在减
        // 手指从上往下 图片也要往下 distanceY是正数, top 和 bottom 在加
        //改变加载图片的区域
        mRect.offset(0, (int) distanceY);
        //bottom大于图片高了, 或者 top小于0了
        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 启动 flin 的第一个向下运动事件
     * @param e2 触发当前 onFlin 的移动运动事件
     * @param velocityX  速度,每秒 x 方向移动的像素
     * @param velocityY  速度,每秒 y 方向移动的像素
     * @return
     */
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        /**
         * startX: 滑动开始的x坐标
         * startY: 滑动开始的y坐标
         * velocityX:两个速度
         * velocityY:
         * minX: x方向的最小值
         * max 最大
         * y
         */
        //计算器
        mScroller.fling(0, mRect.top,
                0, (int)-velocityY,
                0,0,0,
                mImageHeight - (int) (mViewHeight / mScale));
        return false;
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        //触摸事件转交给 mGestureDetector 进行处理
        return mGestureDetector.onTouchEvent(event);
    }

    //获取计算结果并且重绘
    @Override
    public void computeScroll() {
        //已经计算结束 return
        if (mScroller.isFinished()){
            return;
        }
        //true 表示当前动画未结束
        if (mScroller.computeScrollOffset()){
            //
            mRect.top = mScroller.getCurrY();
            mRect.bottom = mRect.top+ (int) (mViewHeight / mScale);
            invalidate();
        }
    }
}

效果:
这里写图片描述

三、附

代码链接

猜你喜欢

转载自blog.csdn.net/qq_18983205/article/details/81121545
今日推荐