1. 程式人生 > >Android 加載大圖

Android 加載大圖

ons 功能實現 圖片加載 sources 距離 val factor lean esp

  • 在 Android 開發中, Bitmap 是個吃內存大戶,稍微操作不當就會 OOM 。雖然現在第三方的圖片加載庫已經很多,很完善,但是作為一個 Androider 還得知道如何自己進行操作來加載大圖。
  • 為什麽加載圖片會很容易造成 OOM 呢,主要是從圖片加載到內存說起,假如一個圖片的分辨率是 1000*20000,那麽這張圖片加載的內存中的大致大小為 1000*20000*4 = 80000000 字節,那麽就是占用內存為 77 M 左右,這樣的話,很容易造成 OOM 。
  • 為了不 OOM ,Android 提供了 BitmapFactory.Options 的 inJustDecodeBounds 和 inSimpleSize ,合理使用這些變量可以輕松的加載圖片

inJustDecodeBounds

  • 通過把該變量設置為 true ,可以在不加載圖片的情況下,拿到圖片的分辨率。這時,decodeResource 方法返回的 Bitmap 是 null
  • 代碼
        BitmapFactory.Options options = new Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(getResources(), R.drawable.bigpicture, options);
        int outHeight = options.outHeight;
        int outWidth = options.outWidth;

inSimpleSize

  • 通過 inJustDecodeBounds 拿到了圖片的分辨率,那麽通過 ImageView 的寬和高與圖片的寬高進行比較,只有當圖片的寬和高任意一個都比圖片的寬和高都小的時候,計算結束。inSimpleSize 的大小,默認值為 1 ,然後按照 2 的倍數增加,假如 inSimpleSize 為 2,那麽圖片的寬和高就按照1/2縮放,這樣加載到內存就縮小了4倍。
  • 代碼
        BitmapFactory.Options options = new Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(getResources(), R.drawable.bigpicture, options);
        int outHeight = options.outHeight;
        int outWidth = options.outWidth;
        int inSampleSize = 1;
        int height = view.getMeasuredHeight();
        int width = view.getMeasuredWidth();
        if (outHeight > height || outWidth > width) {
            int halfHeight = outHeight / 2;
            int halfWidth = outWidth / 2;
            while ((halfHeight / inSampleSize) >= height || (halfWidth / inSampleSize) >= width) {
                inSampleSize *= 2;
            }
        }

加載大圖

    private void loadImage(ImageView view) {
        BitmapFactory.Options options = new Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(getResources(), R.drawable.bigpicture, options);
        int outHeight = options.outHeight;
        int outWidth = options.outWidth;
        int inSampleSize = 1;
        int height = view.getMeasuredHeight();
        int width = view.getMeasuredWidth();
        if (outHeight > height || outWidth > width) {
            int halfHeight = outHeight / 2;
            int halfWidth = outWidth / 2;
            while ((halfHeight / inSampleSize) >= height || (halfWidth / inSampleSize) >= width) {
                inSampleSize *= 2;
            }
        }
        options.inSampleSize = inSampleSize;
        options.inJustDecodeBounds = false;
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bigpicture, options);
        view.setImageBitmap(bitmap);
    }

展示巨圖局部

  • 加載巨圖,針對上面方式的話,就無法看清楚了。比如清明上河圖,這時候就需要使用到另外的一個類 BitmapRegionDecoder ,通過該類可以展示圈定的矩形區域,這樣就可以更加清晰的看到局部了。
  • 代碼
            InputStream inputStream = getResources().getAssets().open("bigpicture.jpg");
            BitmapRegionDecoder regionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
            int measuredHeight = view.getMeasuredHeight();
            int measuredWidth = view.getMeasuredWidth();
            Rect rect = new Rect(0, 0, measuredWidth, measuredHeight);
            Bitmap bitmap = regionDecoder.decodeRegion(rect, new Options());
            view.setImageBitmap(bitmap);
  • 以上就可以在 ImageView 中展示圖片的局部。

    滑動顯示巨圖

  • 上面功能實現了展示巨圖的局部,但是想要通過滑動顯示巨圖的其他區域,就需要自定義 View , 重寫 onTouchEvent() 方法,根據手指滑動的距離,重新計算 Rect 的區域,來實現加載大圖布局。
  • 幾個要點:
    1. 在 onMeasure 中拿到測量後的大小,設置給 Rect
    2. 在 onTouchEvent() 方法中,計算滑動的距離,然後設置給 Rect
    3. 設置了新的顯示區域以後,調用 invalidate() 方法, 請求繪制,這時候會調用 onDraw() 方法
    4. 在 onDraw() 方法中根據 Rect 拿到需要顯示的局部 Bitmap, 通過 Canvas 繪制回來。
    public class BigImageView extends View {
    
      private int slideX = 0;
      private int slideY = 0;
      private BitmapRegionDecoder bitmapRegionDecoder;
      private Paint paint;
      private int mImageWidth;
      private int mImageHeight;
      private Options options;
      private Rect mRect;
    
      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);
          init();
      }
    
      private void init() {
          mRect = new Rect();
          paint = new Paint();
          try {
              InputStream inputStream = getResources().getAssets().open("bigpicture.jpg");
    
              options = new Options();
              options.inJustDecodeBounds = true;
              BitmapFactory.decodeStream(inputStream, null, options);
              mImageWidth = options.outWidth;
              mImageHeight = options.outHeight;
              options.inJustDecodeBounds = false;
              options.inPreferredConfig = Config.RGB_565;
              bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
          } catch (IOException e) {
              e.printStackTrace();
          }
    
      }
      float downX = 0;
      float downY = 0;
      @Override
      public boolean onTouchEvent(MotionEvent event) {
          switch (event.getAction()) {
              case MotionEvent.ACTION_DOWN:
                  downX = event.getRawX();
                  downY = event.getRawY();
                  break;
              case MotionEvent.ACTION_MOVE:
                  float moveX = event.getRawX();
                  float moveY = event.getRawY();
                  slideX = (int) (moveX - downX);
                  slideY = (int) (moveY - downY);
                  if (mImageWidth > getWidth()) {
                      mRect.offset(-slideX, 0);
                      if (mRect.right > mImageWidth) {
                          mRect.right = mImageWidth;
                          mRect.left = mRect.right - getWidth();
                      }
                      if (mRect.left < 0) {
                          mRect.left = 0;
                          mRect.right = getWidth();
                      }
                      invalidate();
                  }
                  if (mImageHeight > getHeight()) {
                      mRect.offset(0, -slideY);
                      if (mRect.bottom > mImageHeight) {
                          mRect.bottom = mImageHeight;
                          mRect.top = mRect.bottom - getHeight();
                      }
                      if (mRect.top < 0) {
                          mRect.top = 0;
                          mRect.bottom = getHeight();
                      }
                      invalidate();
                  }
                  downX = moveX;
                  downY = moveY;
                  break;
              default:
                  break;
          }
    
          return true;
      }
    
      @Override
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          super.onMeasure(widthMeasureSpec, heightMeasureSpec);
          mRect.left = 0;
          mRect.right = getMeasuredWidth() + mRect.left;
          mRect.top = 0;
          mRect.bottom = getMeasuredHeight() + mRect.top;
      }
    
      @Override
      protected void onDraw(Canvas canvas) {
          super.onDraw(canvas);
          Bitmap bitmap = bitmapRegionDecoder.decodeRegion(mRect, options);
          canvas.drawBitmap(bitmap, 0, 0, paint);
      }
    }

Android 加載大圖