要优雅!Android中这样加载大图片和长图片
我们在做开发的时候总是会不可避免的遇到加载图片的情况,当图片的尺寸小于ImageView的尺寸的时候,我们当然可以很happy的去直接加载展示。
但是如果我们要加载的图片远远大于ImageView的大小,直接用ImageView去展示的话,就会带来不好的视觉效果,也会占用太多的内存和性能开销。
甚至这张图片足够大到导致程序oom崩溃。这个时候我们就需要对图片进行特殊的处理了:
一、图片压缩
图片太大,那我就想办法把它压缩变小呗。老铁,这思路完全没毛病。
BitmapFactory这个类就提供了多个解析方法(decodeResource、decodeStream、decodeFile等)用于创建Bitmap。
我们可以根据图片的来源来选择解析方法。
比如如果图片来源于网络,就可以使用decodeStream方法;如果是sd卡里面的图片,就可以选择decodeFile方法;如果是资源文件里面的图片,就可以使用decodeResource方法等。这些方法会为创建的Bitmap分配内存,如果图片过大的话就会导致 oom。
BitmapFactory为这些方法都提供了一个可选的参数BitmapFactory.Options,用来辅助我们解析图片。这个参数有一个属性inSampleSize,这个属性可以帮助我们来进行图片的压缩。
为了解释inSampleSize的效果,我们可以举个栗子。
比如我们有一张20481536的图片,设置inSampleSize的值为4,就可以把这张图片压缩为512384,长短各缩小了4倍,所占内存就缩小了16倍。
这就明了了,inSampleSize的作用就是可以把图片的长短缩小inSampleSize倍,所占内存缩小inSampleSize的平方。
官方文档对于inSampleSize的值也做了一些要求,那就是inSampleSize的值必须大于等于1,如果给定的值小于1,那就默认为1。
而且inSampleSize的值需要是2的倍数,如果不是的话,就会自动变为离这个值向下最近的2的倍数的值,比如给定的值是3,那么最终 inSampleSize的值会是2。
当然了,这个inSampleSize的值我们也不可能随便就给,最好使我们能获取到照片的原始大小,再根据需要进行压缩。别急,谷歌都帮我们想好了!BitmapFactory.Options有一个属性inJustDecodeBounds,这个属性当为true的时候,表明我们当前只是为了获取当前图片的边界的大小,此时BitmapFactory的解析图片方法的返回值为 null,该方法是一个十分轻量级的方法。这样我们就可以很愉快的拿到图片大小了,代码如下:
BitmapFactory.Optionsoptions=newBitmapFactory.Options();options.inJustDecodeBounds=true;//当前只为获取图片的边界大小BitmapFactory.decodeResource(getResources(),R.drawable.bigpic,options);intoutHeight=options.outHeight;intoutWidth=options.outWidth;StringoutMimeType=options.outMimeType;
拿到了图片的大小,我们就可以根据需要计算出所需要压缩的大小了:
privateintcaculateSampleSize(BitmapFactory.Optionsoptions,intreqWidth,intreqHeight){intsampleSize=1;intpicWidth=options.outWidth;intpicHeight=options.outHeight;if(picWidth>reqWidth||picHeight>reqHeight){inthalfPicWidth=picWidth/2;inthalfPicHeight=picHeight/2;while(halfPicWidth/sampleSize>reqWidth||halfPicHeight/sampleSize>reqHeight){sampleSize*=2;}}returnsampleSize;}
下面就是完整的代码:
mIvBigPic=findViewById(R.id.iv_big_pic);BitmapFactory.Optionsoptions=newBitmapFactory.Options();options.inJustDecodeBounds=true;//当前只为获取图片的边界大小BitmapFactory.decodeResource(getResources(),R.drawable.bigpic,options);intoutHeight=options.outHeight;intoutWidth=options.outWidth;StringoutMimeType=options.outMimeType;System.out.println("outHeight="+outHeight+"outWidth="+outWidth+"outMimeType="+outMimeType);options.inJustDecodeBounds=false;options.inSampleSize=caculateSampleSize(options,getScreenWidth(),getScreenHeight());Bitmapbitmap=BitmapFactory.decodeResource(getResources(),R.drawable.bigpic,options);mIvBigPic.setImageBitmap(bitmap);
这样图片压缩到这里就差不多结束了。
二、局部展示有时候我们通过压缩可以取得很好的效果,但有时候效果就不那么美好了,例如长图像清明上河图,像这类的长图,如果我们直接压缩展示的话,这张图完全看不清,很影响体验。这时我们就可以采用局部展示,然后滑动查看的方式去展示图片。
Android里面是利用BitmapRegionDecoder来局部展示图片的,展示的是一块矩形区域。为了完成这个功能那么就需要一个方法设置图片,另一个方法设置展示的区域。
初始化
BitmapRegionDecoder提供了一系列的newInstance来进行初始化,支持传入文件路径,文件描述符和文件流InputStream等
例如:
mRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
上面这个方法解决了传入图片,接下来就要去设置展示区域了。
Bitmap bitmap = mRegionDecoder.decodeRegion(mRect, sOptions);
参数一是一个Rect,参数二是BitmapFactory.Options,可以用来控制inSampleSize,inPreferredConfig等。下面是一个简单的例子,展示图片最前面屏幕大的部分:
try{BitmapRegionDecoderregionDecoder=BitmapRegionDecoder.newInstance(inputStream,false);BitmapFactory.Optionsoptions1=newBitmapFactory.Options();options1.inPreferredConfig=Bitmap.Config.ARGB_8888;Bitmapbitmap=regionDecoder.decodeRegion(newRect(0,0,getScreenWidth(),getScreenHeight()),options1);mIvBigPic.setImageBitmap(bitmap);}catch(IOExceptione){e.printStackTrace();}
当然了,这只是最简单的用法,对于我们想要完全展示图片并没什么用!客官,稍安勿躁,前途已经明了!既然我们可以实现区域展示,那我们可不可以自定义一个View,可以随着我们的手指滑动展示图片的不同区域。yes! of course。那么我们就继续吧!
根据上面的分析,我们自定义控件的思路就很明白了:
提供一个设置图片的路口;
重写onTouchEvent,根据用户移动的手势,修改图片显示的区域;
每次更新区域参数后,调用invalidate,onDraw里面去regionDecoder.decodeRegion拿到bitmap,去draw
废话不多说,直接上代码:
publicclassBigImageViewextendsView{privatestaticfinalStringTAG="BigImageView";privateBitmapRegionDecodermRegionDecoder;privateintmImageWidth,mImageHeight;privateRectmRect=newRect();privatestaticBitmapFactory.OptionssOptions=newBitmapFactory.Options();{sOptions.inPreferredConfig=Bitmap.Config.ARGB_8888;}publicBigImageView(Contextcontext){this(context,null);}publicBigImageView(Contextcontext,@NullableAttributeSetattrs){this(context,attrs,0);}publicBigImageView(Contextcontext,@NullableAttributeSetattrs,intdefStyleAttr){super(context,attrs,defStyleAttr);}publicvoidsetInputStream(InputStreaminputStream){try{mRegionDecoder=BitmapRegionDecoder.newInstance(inputStream,false);BitmapFactory.Optionsoptions=newBitmapFactory.Options();options.inJustDecodeBounds=false;BitmapFactory.decodeStream(inputStream,null,options);mImageHeight=options.outHeight;mImageWidth=options.outWidth;requestLayout();invalidate();}catch(IOExceptione){e.printStackTrace();}}intdownX=0;intdownY=0;@OverridepublicbooleanonTouchEvent(MotionEventevent){switch(event.getAction()){caseMotionEvent.ACTION_DOWN:downX=(int)event.getX();downY=(int)event.getY();break;caseMotionEvent.ACTION_MOVE:intcurX=(int)event.getX();intcurY=(int)event.getY();intmoveX=curX-downX;intmoveY=curY-downY;onMove(moveX,moveY);System.out.println(TAG+"moveX="+moveX+"curX="+curX+"downX="+downX);downX=curX;downY=curY;break;caseMotionEvent.ACTION_UP:break;}returntrue;}privatevoidonMove(intmoveX,intmoveY){if(mImageWidth>getWidth()){mRect.offset(-moveX,0);checkWidth();invalidate();}if(mImageHeight>getHeight()){mRect.offset(0,-moveY);checkHeight();invalidate();}}privatevoidcheckWidth(){Rectrect=mRect;if(rect.right>mImageWidth){rect.right=mImageWidth;rect.left=mImageWidth-getWidth();}if(rect.left<0){rect.left=0;rect.right=getWidth();}}privatevoidcheckHeight(){Rectrect=mRect;if(rect.bottom>mImageHeight){rect.bottom=mImageHeight;rect.top=mImageHeight-getHeight();}if(rect.top<0){rect.top=0;rect.bottom=getWidth();}}@OverrideprotectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){super.onMeasure(widthMeasureSpec,heightMeasureSpec);intwidth=getMeasuredWidth();intheight=getMeasuredHeight();mRect.left=0;mRect.top=0;mRect.right=width;mRect.bottom=height;}@OverrideprotectedvoidonDraw(Canvascanvas){super.onDraw(canvas);Bitmapbitmap=mRegionDecoder.decodeRegion(mRect,sOptions);canvas.drawBitmap(bitmap,0,0,null);}}
根据上述源码:
在setInputStream方法里面初始BitmapRegionDecoder,获取图片的实际宽高;
onMeasure方法里面给Rect赋初始化值,控制开始显示的图片区域;
onTouchEvent监听用户手势,修改Rect参数来修改图片展示区域,并且进行边界检测,最后invalidate;
在onDraw里面根据Rect获取Bitmap并且绘制。
学习不是件简单的事,分享一下我们阿里p7架构师的学习路线
作为一个Android程序员,要学的东西也很多。
放出来自己整理好的Android学习内容帮助大家学习提升进阶
面试题合集入门级书籍PDF:Java、c、c++Android进阶精选书籍PDF阿里规范文档Android开发技巧进阶PDF大全高级进阶视频源码算法学习视频未完待续还有现在的学习趋势flutter,kotin等的资料,都已经整理好,节省搜索的时间来学习
如果你有需要的话,可以点赞+评论,关注我,然后点击了解详情
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。