前言
在商城这类项目中,肯定是有在商品详情页显示长图的需求,当然用h5那就另说了。如果直接使用BitmapFactory.decodeXXX
,然后往ImageView
上面setImageBitmap
,那么程序也就直接抛给你个OOM。通过一波百度 or google,找到了BitmapRegionDecoder
这个类,并且实现了此功能。
正题
BitmapRegionDecoder
处理图像的原理是:给定一个长方形范围(Rect),然后通过decodeRegion
方法来显示此范围。因此要显示长图片,那就需要将长图分割成多个Rect,然后依次有序得显示。因此BitmapRegionDecoder
实际上是起到图像分割的作用,因此类似截图拼图的功能也是可以利用它来进行实现的。
-
实现思路:商品的详情页,宽度上固定不变的,也就是屏幕的宽度。为了可以让图像的x轴方向完整并且清晰得显示在整个屏幕上,除了需要美工的支持之外,我们还是需要就图像进行一定的压缩的,毕竟android的分辨率有这么多种。因此第一步就是先压缩图片,然后通过
BitmapRegionDecoder
将图片在y轴上进行分割,最后通过一个RecyclerView来显示这个图像列表。还可以添加图像的缓存功能,不过暂时还没有实现这个功能。 -
直接上代码:
public class RecycleView4BigPicActivity extends AppCompatActivity { private static final String TAG = "RecycleView4BigPicActiv"; private RecyclerView mRecyclerView; private ImageAdapter mImageAdapter; private SparseArray<Bitmap> mBitmapList = new SparseArray<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_recycle_view_big_pic); mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(linearLayoutManager); mRecyclerView.setHasFixedSize(true); // 图片高度是固定的,通过此属性来提高性能 mRecyclerView.setAdapter(mImageAdapter = new ImageAdapter()); loadPic(); } // 加载图片 private void loadPic() { InputStream is = null; String picName = "bigpic.png"; BitmapRegionDecoder regionDecoder; try { is = getAssets().open(picName); regionDecoder = BitmapRegionDecoder.newInstance(is, false); // 获取图片的真是宽高 final int width = regionDecoder.getWidth(); final int height = regionDecoder.getHeight(); BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = calculateInSampleSize(width); // 期望item的高度值 // 这边只考虑了长图,也就是高度比宽度大很多 final int desiredItemHeight = width; // 余数 final int remainder = height % desiredItemHeight; // 切割后图片数量,也就是item的数量 final int bmpCount = height / desiredItemHeight; for (int i = 0; i < bmpCount; i++) { Rect rect; // 如果总长度能够整除期望item高度,那么每块bitmap的高度相同 = desiredItemHeight // 如果两者不能整除,那么最后一块bitmap的高度 = desiredItemHeight + remainder(余数) if (i == bmpCount - 1) { rect = new Rect(0, desiredItemHeight * i, width, desiredItemHeight * (i + 1) + remainder); } else { rect = new Rect(0, desiredItemHeight * i, width, desiredItemHeight * (i + 1)); } Bitmap bitmap = regionDecoder.decodeRegion(rect, options); mBitmapList.append(i, bitmap); } mImageAdapter.setData(mBitmapList); } catch (IOException e) { e.printStackTrace(); } finally { if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } } // 计算采样率 private int calculateInSampleSize(int actualWidth) { // 获取手机屏幕的分辨率 DisplayMetrics displayMetrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); final int screenWidth = displayMetrics.widthPixels; // 图片在手机上是全屏显示的,因此宽度是固定的,根据屏幕宽度跟图片实际宽度来计算采样率(inSampleSize) final double ratio = actualWidth / screenWidth; return (int) (ratio + 1); } @Override protected void onDestroy() { super.onDestroy(); int size = 0; if (mBitmapList != null && (size = mBitmapList.size()) > 0) { for (int i = 0; i < size; i++) { Bitmap b = mBitmapList.get(i); if (b != null) { // recycle之后,虚拟机gc的时候就会回收这部分内容 b.recycle(); } } } } }
BitmapRegionDecoder
是通过newInstance()
方法进行创建的,这边使用的是:
itmapRegionDecoder newInstance(InputStream is, boolean isShareable)
- is:很好理解,也就是图片输入流
- isShareable:比较难以理解 —— 如果这是true,那么BitmapRegionDecoder可能会保持一个浅复制输入。如果是false,然后BitmapRegionDecoder将显式地创建一个副本输入数据,并保留。 即使是true,仍然可能是深复制输入数据。 如果图像被逐行编码,true可能会降低解码速度。
还有一点需要注意的是,
BitmapRegionDecoder
最大只能显示4096 * 4096 尺寸的图像Rect,如果超过此大小将不会显示。
网友评论