咱们在作开发的时候老是会不可避免的遇到加载图片的状况,当图片的尺寸小于ImageView的尺寸的时候,咱们固然能够很happy的去直接加载展现。c++
可是若是咱们要加载的图片远远大于ImageView的大小,直接用ImageView去展现的话,就会带来很差的视觉效果,也会占用太多的内存和性能开销。程序员
甚至这张图片足够大到致使程序oom崩溃。这个时候咱们就须要对图片进行特殊的处理了:面试
#1、图片压缩算法
图片太大,那我就想办法把它压缩变小呗。老铁,这思路彻底没毛病。canvas
BitmapFactory这个类就提供了多个解析方法(decodeResource、decodeStream、decodeFile等)用于建立Bitmap。网络
咱们能够根据图片的来源来选择解析方法。架构
BitmapFactory为这些方法都提供了一个可选的参数BitmapFactory.Options,用来辅助咱们解析图片。这个参数有一个属性inSampleSize,这个属性能够帮助咱们来进行图片的压缩。app
为了解释inSampleSize的效果,咱们能够举个栗子。
好比咱们有一张20481536的图片,设置inSampleSize的值为4,就能够把这张图片压缩为512384,长短各缩小了4倍,所占内存就缩小了16倍。
这就明了了,inSampleSize的做用就是能够把图片的长短缩小inSampleSize倍,所占内存缩小inSampleSize的平方。
官方文档对于inSampleSize的值也作了一些要求,那就是inSampleSize的值必须大于等于1,若是给定的值小于1,那就默认为1。
并且inSampleSize的值须要是2的倍数,若是不是的话,就会自动变为离这个值向下最近的2的倍数的值,好比给定的值是3,那么最终 inSampleSize的值会是2。ide
固然了,这个inSampleSize的值咱们也不可能随便就给,最好使咱们能获取到照片的原始大小,再根据须要进行压缩。别急,Google 都帮咱们想好了!BitmapFactory.Options有一个属性inJustDecodeBounds,这个属性当为true的时候,代表咱们当前只是为了获取当前图片的边界的大小,此时BitmapFactory的解析图片方法的返回值为 null,该方法是一个十分轻量级的方法。这样咱们就能够很愉快的拿到图片大小了,代码以下:性能
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; // 当前只为获取图片的边界大小 BitmapFactory.decodeResource(getResources(), R.drawable.bigpic, options); int outHeight = options.outHeight; int outWidth = options.outWidth; String outMimeType = options.outMimeType;
拿到了图片的大小,咱们就能够根据须要计算出所须要压缩的大小了:
private int caculateSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { int sampleSize = 1; int picWidth = options.outWidth; int picHeight = options.outHeight; if (picWidth > reqWidth || picHeight > reqHeight) { int halfPicWidth = picWidth / 2; int halfPicHeight = picHeight / 2; while (halfPicWidth / sampleSize > reqWidth || halfPicHeight / sampleSize > reqHeight) { sampleSize *= 2; } } return sampleSize; }
下面就是完整的代码:
mIvBigPic = findViewById(R.id.iv_big_pic); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; // 当前只为获取图片的边界大小 BitmapFactory.decodeResource(getResources(), R.drawable.bigpic, options); int outHeight = options.outHeight; int outWidth = options.outWidth; String outMimeType = options.outMimeType; System.out.println("outHeight = " + outHeight + " outWidth = " + outWidth + " outMimeType = " + outMimeType); options.inJustDecodeBounds = false; options.inSampleSize = caculateSampleSize(options, getScreenWidth(), getScreenHeight()); Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bigpic, options); mIvBigPic.setImageBitmap(bitmap);
这样图片压缩到这里就差很少结束了。
#2、局部展现
有时候咱们经过压缩能够取得很好的效果,但有时候效果就不那么美好了,例如长图像清明上河图,像这类的长图,若是咱们直接压缩展现的话,这张图彻底看不清,很影响体验。这时咱们就能够采用局部展现,而后滑动查看的方式去展现图片。
Android里面是利用BitmapRegionDecoder来局部展现图片的,展现的是一块矩形区域。为了完成这个功能那么就须要一个方法设置图片,另外一个方法设置展现的区域。
初始化
BitmapRegionDecoder提供了一系列的newInstance来进行初始化,支持传入文件路径,文件描述符和文件流InputStream等
例如:
mRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
上面这个方法解决了传入图片,接下来就要去设置展现区域了。
Bitmap bitmap = mRegionDecoder.decodeRegion(mRect, sOptions);
参数一是一个Rect,参数二是BitmapFactory.Options,能够用来控制inSampleSize,inPreferredConfig等。下面是一个简单的例子,展现图片最前面屏幕大的部分:
try { BitmapRegionDecoder regionDecoder = BitmapRegionDecoder.newInstance(inputStream, false); BitmapFactory.Options options1 = new BitmapFactory.Options(); options1.inPreferredConfig = Bitmap.Config.ARGB_8888; Bitmap bitmap = regionDecoder.decodeRegion(new Rect(0, 0, getScreenWidth(), getScreenHeight()), options1); mIvBigPic.setImageBitmap(bitmap); } catch (IOException e) { e.printStackTrace(); }
固然了,这只是最简单的用法,对于咱们想要彻底展现图片并没什么用!客官,稍安勿躁,前途已经明了!既然咱们能够实现区域展现,那咱们可不能够自定义一个View,能够随着咱们的手指滑动展现图片的不一样区域。yes! of course。那么咱们就继续吧!
根据上面的分析,咱们自定义控件的思路就很明白了:
提供一个设置图片的路口;
重写onTouchEvent,根据用户移动的手势,修改图片显示的区域;
每次更新区域参数后,调用invalidate,onDraw里面去regionDecoder.decodeRegion拿到bitmap,去draw
废话很少说,直接上代码:
public class BigImageView extends View { private static final String TAG = "BigImageView"; private BitmapRegionDecoder mRegionDecoder; private int mImageWidth, mImageHeight; private Rect mRect = new Rect(); private static BitmapFactory.Options sOptions = new BitmapFactory.Options(); { sOptions.inPreferredConfig = Bitmap.Config.ARGB_8888; } 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); } public void setInputStream(InputStream inputStream) { try { mRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = false; BitmapFactory.decodeStream(inputStream, null, options); mImageHeight = options.outHeight; mImageWidth = options.outWidth; requestLayout(); invalidate(); } catch (IOException e) { e.printStackTrace(); } } int downX = 0; int downY = 0; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downX = (int) event.getX(); downY = (int) event.getY(); break; case MotionEvent.ACTION_MOVE: int curX = (int) event.getX(); int curY = (int) event.getY(); int moveX = curX - downX; int moveY = curY - downY; onMove(moveX, moveY); System.out.println(TAG + " moveX = " + moveX + " curX = " + curX + " downX = " + downX); downX = curX; downY = curY; break; case MotionEvent.ACTION_UP: break; } return true; } private void onMove(int moveX, int moveY) { if (mImageWidth > getWidth()) { mRect.offset(-moveX, 0); checkWidth(); invalidate(); } if (mImageHeight > getHeight()) { mRect.offset(0, -moveY); checkHeight(); invalidate(); } } private void checkWidth() { Rect rect = mRect; if (rect.right > mImageWidth) { rect.right = mImageWidth; rect.left = mImageWidth - getWidth(); } if (rect.left < 0) { rect.left = 0; rect.right = getWidth(); } } private void checkHeight() { Rect rect = mRect; if (rect.bottom > mImageHeight) { rect.bottom = mImageHeight; rect.top = mImageHeight - getHeight(); } if (rect.top < 0) { rect.top = 0; rect.bottom = getWidth(); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = getMeasuredWidth(); int height = getMeasuredHeight(); mRect.left = 0; mRect.top = 0; mRect.right = width; mRect.bottom = height; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Bitmap bitmap = mRegionDecoder.decodeRegion(mRect, sOptions); canvas.drawBitmap(bitmap, 0, 0, null); } }
根据上述源码:
在setInputStream方法里面初始BitmapRegionDecoder,获取图片的实际宽高;
onMeasure方法里面给Rect赋初始化值,控制开始显示的图片区域;
onTouchEvent监听用户手势,修改Rect参数来修改图片展现区域,而且进行边界检测,最后invalidate;
在onDraw里面根据Rect获取Bitmap而且绘制。
#最后
学习不是件简单的事,分享一下咱们阿里p7架构师的学习路线
做为一个Android程序员,要学的东西也不少。
放出来本身整理好的Android学习内容帮助你们学习提高进阶
还有如今的学习趋势flutter,kotin等的资料,都已经整理好,节省搜索的时间来学习
若是你有须要的话,能够点赞+评论,关注我,而后私信我