美文网首页小技巧
通过BitmapRegionDecoder实现高清长图的加载

通过BitmapRegionDecoder实现高清长图的加载

作者: JustCode | 来源:发表于2017-07-28 22:10 被阅读53次

    前言

    在商城这类项目中,肯定是有在商品详情页显示长图的需求,当然用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,如果超过此大小将不会显示。

    相关文章

      网友评论

        本文标题:通过BitmapRegionDecoder实现高清长图的加载

        本文链接:https://www.haomeiwen.com/subject/xtjflxtx.html