我们在做开发的时候总是会不可避免的遇到加载图片的情况,当图片的尺寸小于ImageView
的尺寸的时候,我们当然可以很happy
的去直接加载展示。但是如果我们要加载的图片远远大于ImageView
的大小,直接用ImageView
去展示的话,就会带来不好的视觉效果,也会占用太多的内存和性能开销。甚至这张图片足够大到导致程序oom
崩溃。这个时候我们就需要对图片进行特殊的处理了:
一、图片压缩
图片太大,那我就想办法把它压缩变小呗。老铁,这思路完全没毛病。BitmapFactory
这个类就提供了多个解析方法(decodeResource
、decodeStream
、decodeFile
等)用于创建Bitmap
。我们可以根据图片的来源来选择解析方法。比如如果图片来源于网络,就可以使用decodeStream
方法;如果是sd卡里面的图片,就可以选择decodeFile
方法;如果是资源文件里面的图片,就可以使用decodeResource
方法等。这些方法会为创建的Bitmap
分配内存,如果图片过大的话就会导致 oom。
BitmapFactory
为这些方法都提供了一个可选的参数,用来辅助我们解析图片。这个参数有一个属性
inSampleSize
,这个属性可以帮助我们来进行图片的压缩。为了解释inSampleSize
的效果,我们可以举个栗子。比如我们有一张2048*1536的图片,设置inSampleSize
的值为4,就可以把这张图片压缩为512*384,长短各缩小了4倍,所占内存就缩小了16倍。这就明了了,inSampleSize
的作用就是可以把图片的长短缩小inSampleSize
倍,所占内存缩小inSampleSize
的平方。官方文档对于inSampleSize
的值也做了一些要求,那就是inSampleSize
的值必须大于等于1,如果给定的值小于1,那就默认为1。而且inSampleSize
的值需要是2的倍数,如果不是的话,就会自动变为离这个值向下最近的2的倍数的值,比如给定的值是3,那么最终 inSampleSize
的值会是2。
当然了,这个inSampleSize
的值我们也不可能随便就给,最好使我们能获取到照片的原始大小,再根据需要进行压缩。别急,谷歌都帮我们想好了!有一个属性
inJustDecodeBounds
,这个属性当为true
的时候,表明我们当前只是为了获取当前图片的边界的大小,此时BitmapFactory
的解析图片方法的返回值为 null,该方法是一个十分轻量级的方法。这样我们就可以很愉快的拿到图片大小了,代码如下:
options = new ();
= true; // 当前只为获取图片的边界大小
(getResources(), , options);
int outHeight = ;
int outWidth = ;
String outMimeType = ;
拿到了图片的大小,我们就可以根据需要计算出所需要压缩的大小了:
private int caculateSampleSize( options, int reqWidth, int reqHeight) {
int sampleSize = 1;
int picWidth = ;
int picHeight = ;
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(.iv_big_pic);
options = new ();
= true; // 当前只为获取图片的边界大小
(getResources(), , options);
int outHeight = ;
int outWidth = ;
String outMimeType = ;
("outHeight = " + outHeight + " outWidth = " + outWidth + " outMimeType = " + outMimeType);
= false;
= caculateSampleSize(options, getScreenWidth(), getScreenHeight());
Bitmap bitmap = (getResources(), , options);
(bitmap);
这样图片压缩到这里就差不多结束了。
二、局部展示
有时候我们通过压缩可以取得很好的效果,但有时候效果就不那么美好了,例如长图像清明上河图,像这类的长图,如果我们直接压缩展示的话,这张图完全看不清,很影响体验。这时我们就可以采用局部展示,然后滑动查看的方式去展示图片。
Android
里面是利用BitmapRegionDecoder
来局部展示图片的,展示的是一块矩形区域。为了完成这个功能那么就需要一个方法设置图片,另一个方法设置展示的区域。
- 初始化
BitmapRegionDecoder
提供了一系列的newInstance
来进行初始化,支持传入文件路径,文件描述符和文件流InputStream
等
例如:
mRegionDecoder = (inputStream, false);
上面这个方法解决了传入图片,接下来就要去设置展示区域了。
Bitmap bitmap = (mRect, sOptions);
参数一是一个Rect
,参数二是,可以用来控制
inSampleSize
,inPreferredConfig
等。下面是一个简单的例子,展示图片最前面屏幕大的部分:
try {
BitmapRegionDecoder regionDecoder = (inputStream, false);
options1 = new ();
= .ARGB_8888;
Bitmap bitmap = (new Rect(0, 0, getScreenWidth(), getScreenHeight()), options1);
(bitmap);
} catch (IOException e) {
();
}
当然了,这只是最简单的用法,对于我们想要完全展示图片并没什么用!客官,稍安勿躁,前途已经明了!既然我们可以实现区域展示,那我们可不可以自定义一个View
,可以随着我们的手指滑动展示图片的不同区域。yes! of course。那么我们就继续吧!
根据上面的分析,我们自定义控件的思路就很明白了:
- 提供一个设置图片的路口;
- 重写onTouchEvent,根据用户移动的手势,修改图片显示的区域;
- 每次更新区域参数后,调用invalidate,onDraw里面去拿到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 sOptions = new ();
{
= .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 = (inputStream, false);
options = new ();
= false;
(inputStream, null, options);
mImageHeight = ;
mImageWidth = ;
requestLayout();
invalidate();
} catch (IOException e) {
();
}
}
int downX = 0;
int downY = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (()) {
case MotionEvent.ACTION_DOWN:
downX = (int) ();
downY = (int) ();
break;
case MotionEvent.ACTION_MOVE:
int curX = (int) ();
int curY = (int) ();
int moveX = curX - downX;
int moveY = curY - downY;
onMove(moveX, moveY);
(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()) {
(-moveX, 0);
checkWidth();
invalidate();
}
if (mImageHeight > getHeight()) {
(0, -moveY);
checkHeight();
invalidate();
}
}
private void checkWidth() {
Rect rect = mRect;
if ( > mImageWidth) {
= mImageWidth;
= mImageWidth - getWidth();
}
if ( < 0) {
= 0;
= getWidth();
}
}
private void checkHeight() {
Rect rect = mRect;
if ( > mImageHeight) {
= mImageHeight;
= mImageHeight - getHeight();
}
if ( < 0) {
= 0;
= getWidth();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
= 0;
= 0;
= width;
= height;
}
@Override
protected void onDraw(Canvas canvas) {
(canvas);
Bitmap bitmap = (mRect, sOptions);
(bitmap, 0, 0, null);
}
}
根据上述源码:
- 在
setInputStream
方法里面初始BitmapRegionDecoder
,获取图片的实际宽高;onMeasure
方法里面给Rect
赋初始化值,控制开始显示的图片区域;onTouchEvent
监听用户手势,修改Rect
参数来修改图片展示区域,并且进行边界检测,最后invalidate
;- 在
onDraw
里面根据Rect
获取Bitmap
并且绘制。
转载链接:/p/4640764bfbc6