Android自定义View实现加载大长图

Android加载大长图

一、为什么Android加载图片要进行压缩?

Android系统为app分配的内存是有限的,在Android开发中如果不对图片加载进行处理,可能会导致OOM(Out of Memory),所以图片的压缩不得不作为Android开发中比用的一项技能点。

二、Android开发中图片的大小如何计算?

图片大小 = 图片的width乘以图片的height然后再乘以每一个像素所占用的字节数

这个字节数需要根据图片解码模式来获得,Android中提供了6种方案,常用的只有三个,如下所示。

Bitmap.Config.ARGB_8888:由4个8位组成,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位(4字节)

Bitmap.Config.ARGB_4444:由4个4位组成,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位 (2字节)

Bitmap.Config.RGB_565:没有透明度,R=5,G=6,B=5,,那么一个像素点占5+6+5=16位(2字节)  
色彩模式ARGB分别是什么含义
A:透明度(Alpha)    
R:红色(Red)    
G:绿(Green)    
B:蓝(Blue)
需要了解知识:
8bit(位)=1byte(字节)
1024byte=1KB
1024kb=1MB
1024mb=1GB

三、图片加载优化

通常为了避免内存泄漏在加载图片时往往进行压缩或进行内存复用

Bitmap内存复用

内存复用,通俗来说就是我已经创建了一个Bitmap对象了,那么我接着还想用一个bitmap对象,那么就可以复用上一个Bitmap对象。

(1)内存复用缺点

这样做有两个缺点,第一就是会将之前的图片覆盖掉,第二就是后边加载的图片必须小于或者等于之前的图片大小,否则就不行。

(2)内存复用优点

bitmap复用内存块,不需要再重新给这个bitmap申请一块新的内存,避免了一次内存的分配和回收,从而改善了运行效率。

四、自定义view实现使用内存复用加载长图

(1)背景介绍

实现目标:加载显示大长图

理论:只加载需要显示的部分其他部分不加载

(2)实现步骤
主要分为三大步 :
  1. 画出图形
  2. 手指移动滑动图片
  3. 加载assets目录下长图
具体一步步实现代码 :

第一步,在自定义view构造器中设置BigView所需要的一些成员变量

mRect = new Rect(); //矩形区域
mOptions = new BitmapFactory.Options(); //内存复用
mGestureDetector = new GestureDetector(context,this); //手势识别
mScroller = new Scroller(context); //滚动类
setOnTouchListener(this); //设置监听

第二步,设置图片得到图片信息(注意获取图片宽和高不能将图片整个加载进内存)

 public void setImage(InputStream is){
    mOptions.inJustDecodeBounds = true;  //设置仅加载图片的宽高信息不将图片加载的内存中
    BitmapFactory.decodeStream(is,null,mOptions);
    mImageWith = mOptions.outWidth;
    mImageHeight = mOptions.outHeight;

    mOptions.inMutable = true; //开启复用
    mOptions.inPreferredConfig = Bitmap.Config.RGB_565;  //设置解码模式(质量压缩),设置格式为RGB565(相比ARGB_8888去掉了透明通道,消耗内存少,但是图片质量会降低一些)
    mOptions.inJustDecodeBounds = false; //消除仅加载图片的宽高

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

    requestLayout(); //刷新
}  

第三步,开始测量,得到view的宽高保存在Rect中,测量加载的图片缩放成什么样子

@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 = mImageWith;
    //计算缩放因子
    mScale = mViewWidth/(float)mImageWith;
    mRect.bottom = (int) (mViewHeight/mScale);
}

第四步,画出具体的内容

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //判断解码器是不是为null,如果解码器没有拿到,表示没有设置图片
    if(mDecoder == null){
        return;
    }
    //真正内存复用 注意复用的bitmap必须要跟即将解码的bitmap尺寸一样
    mOptions.inBitmap = mBitmap;  //绑定一个已经加载的Bitmap对象
    //指定解码区域
    mBitmap = mDecoder.decodeRegion(mRect,mOptions);
    //得到一个矩阵进行缩放,相当于得到的view的大小
    Matrix matrix = new Matrix();
    matrix.setScale(mScale,mScale);
    canvas.drawBitmap(mBitmap,matrix,null);
}

第五步,处理点击事件

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

第六步,处理手按下去

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

第七步,处理滑动事件

//e1:开始事件,手按下去,开始获取坐标
//e2:获取当前事件坐标
//xy:xy轴移动的距离
@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;
}

第八步,处理惯性问题

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    //注意:手势滑动方法onFling中的velocityY参数与Scroller.fling方法的参数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;
    }
    //滚动还没结束,加载
    if(mScroller.computeScrollOffset()){
        mRect.top = mScroller.getCurrY();
        mRect.bottom = mRect.top+(int)(mViewHeight/mScale);
        invalidate();
    }
}  

第十步,在加载长图的布局文件中使用BigView布局,然后在对应activity中获取BigView并加载assets目录下图片

BigView bigView = findViewById(R.id.bigView);
InputStream is = null;
try {
    is = getAssets().open("long.jpg");

} catch (IOException e) {
    e.printStackTrace();
}
bigView.setImage(is);  

五、demo地址

https://github.com/zyx670618/BigImageExample.git

你可能感兴趣的:(Android自定义View实现加载大长图)