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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容