想必这个错误,接触了大图显示的开发者基本都遇到过:
W/OpenGLRenderer: Bitmap too large to be uploaded into a texture (1200×6298, max=4096×4096)
原因是,当开启硬件加速的时候,GPU对于OpenGLRenderer 有一个尺寸限制,不同的手机会有不同的限制,这个限制值可以通过canvas.getMaximumBitmapHeight()和canvas.getMaximumBitmapWidth()来获得。
网上搜索到的方案,很多比较粗暴,比如关闭硬件加速:
<application
android:hardwareAccelerated="false" >
这样,会导致手机运行app卡顿,没有开启硬件加速流畅。
另外一种常见的方案就是,对图片进行压缩显示,控制尺寸不超过限制值。
根据原图片宽高比,动态算出压缩后的尺寸上限,然后使用BitmapFactory.Options,设置图片的尺寸,以下代码引用自郭霖大神:
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// 源图片的高度和宽度
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// 计算出实际宽高和目标宽高的比率
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
// 一定都会大于等于目标的宽和高。
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 调用上面定义的方法计算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 使用获取到的inSampleSize值再次解析图片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public static Bitmap decodeSampledBitmapFromFilePath(String imagePath,
int reqWidth, int reqHeight) {
// 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath, options);
// 调用上面定义的方法计算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 使用获取到的inSampleSize值再次解析图片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(imagePath,options);
}
这样虽然可以解决无法显示的问题,但是,如果我们的需求就是要高清显示大图,不能牺牲图片细节的话,那这个解决方案显然是不可取的。
就没有一种不牺牲显示效果的解决方案吗?没有的话,我也不会写这篇文章了。
subsampling-scale-image-view
GitHub地址:
https://github.com/davemorrissey/subsampling-scale-image-view
这个库为大图而生,可以支持 20,000×20,000px 的超级大图,更大的话,加载会变得较慢慢。平常的小尺寸图片当然也是支持的,你可以把它作为你显示图片的通用控件。
subsampling-scale-image-view采用二次采样和区块显示,来解析和显示超大图,他可以有效避免大图的OOM,而且不丢失任何细节。同时也支持平时图片都有的触摸手势:双击放大,双指放大等。
使用教程
1,引入到Android Studio,添加依赖:
compile 'com.davemorrissey.labs:subsampling-scale-image-view:3.6.0'
2,在对应的布局文件中,替换普通ImageView:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
3,现在就可以在你的activity或者fragment中加载图片了:
SubsamplingScaleImageView imageView = (SubsamplingScaleImageView)findViewById(id.imageView);
imageView.setImage(ImageSource.resource(R.drawable.monkey));
// ... or ...
imageView.setImage(ImageSource.asset("map.png"))
// ... or ...
imageView.setImage(ImageSource.uri("/sdcard/DCIM/DSCM00123.JPG"));
// bitmap也可以,但是作者不建议这么做,因为这可能会造成OOM。
SubsamplingScaleImageView imageView = (SubsamplingScaleImageView)findViewById(id.imageView);
imageView.setImage(ImageSource.bitmap(bitmap));
4,常用属性:
a.设置双击放大的动画时长:
imageView. setDoubleTapZoomDuration(200);// 单位是ms
b.设置是否自动旋转,或者指定旋转:
imageView.setOrientation(ORIENTATION_USE_EXIF);// 自动根据EXIF信息进行旋转
imageView.setOrientation(ORIENTATION_0);// 不旋转
imageView.setOrientation(ORIENTATION_90);// 顺时针旋转90度
imageView.setOrientation(ORIENTATION_180);// 顺时针旋转180度
imageView.setOrientation(ORIENTATION_270);// 顺时针旋转270度
c.更多属性可以去阅读作者写的wiki:https://github.com/davemorrissey/subsampling-scale-image-view/wiki/07.-Configuration
5,如果是网络图片,可以这么做:
Glide:推荐
SubsamplingScaleImageView image = (SubsamplingScaleImageView) findViewById(R.id.image);
image.setDoubleTapZoomDuration(200);// 单位是ms
image.setOrientation(ORIENTATION_USE_EXIF);// 自动根据EXIF信息进行旋转
Glide.with(activity)
.load(path)
.downloadOnly(new ViewTarget<SubsamplingScaleImageView, File>(image) {
@Override
public void onResourceReady(File resource, GlideAnimation<? super File> glideAnimation) {
image.setImage(ImageSource.uri(Uri.fromFile(resource)));
}
});
必须使用gilde的downloadOnly模式,先下载到本地,再使用SubsamplingScaleImageView的setImage方法,才能最高效的显示图片。
Picasso:加载的是Bitmap,所以不建议使用Picasso
SubsamplingScaleImageView image = (SubsamplingScaleImageView) findViewById(R.id.image);
image.setDoubleTapZoomDuration(200);// 单位是ms
image.setOrientation(ORIENTATION_USE_EXIF);// 自动根据EXIF信息进行旋转
Picasso.with(activity)
.load(path)
.into(new com.squareup.picasso.Target() {
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom loadedFrom) {
image.setImage(ImageSource.bitmap(bitmap));
}
@Override
public void onBitmapFailed(Drawable drawable) {
}
@Override
public void onPrepareLoad(Drawable drawable) {
}
});