Android使用BitmapRegionDecoder加载超大图片方案

来源:互联网 发布:男人眼中的女神 知乎 编辑:程序博客网 时间:2024/05/29 16:34

BitmapRegionDecoder主要用于显示图片的某一块矩形区域,如果你需要显示某个图片的指定区域,那么这个类非常合适。

对于该类的用法,非常简单,既然是显示图片的某一块区域,那么至少只需要一个方法去设置图片;一个方法传入显示的区域即可;详见:

  • BitmapRegionDecoder提供了一系列的newInstance方法来构造对象,支持传入文件路径,文件描述符,文件的inputstrem等。

    例如:

    [java] view plain copy
    1. BitmapRegionDecoder bitmapRegionDecoder =  
    2.   BitmapRegionDecoder.newInstance(inputStream, false);  

  • 上述解决了传入我们需要处理的图片,那么接下来就是显示指定的区域。

    [java] view plain copy
    1. bitmapRegionDecoder.decodeRegion(rect, options);  

    参数一很明显是一个rect,参数二是BitmapFactory.Options,你可以控制图片的inSampleSize,inPreferredConfig等。

那么下面看一个超级简单的例子:

[java] view plain copy
  1. import android.graphics.Bitmap;  
  2. import android.graphics.BitmapFactory;  
  3. import android.graphics.BitmapRegionDecoder;  
  4. import android.graphics.Rect;  
  5. import android.os.Bundle;  
  6. import android.support.v7.app.AppCompatActivity;  
  7. import android.widget.ImageView;  
  8.   
  9.   
  10. import java.io.IOException;  
  11. import java.io.InputStream;  
  12.   
  13. public class LargeImageViewActivity extends AppCompatActivity  
  14. {  
  15.     private ImageView mImageView;  
  16.   
  17.     @Override  
  18.     protected void onCreate(Bundle savedInstanceState)  
  19.     {  
  20.         super.onCreate(savedInstanceState);  
  21.         setContentView(R.layout.activity_large_image_view);  
  22.   
  23.         mImageView = (ImageView) findViewById(R.id.id_imageview);  
  24.         try  
  25.         {  
  26.             InputStream inputStream = getAssets().open("tangyan.jpg");  
  27.   
  28.             //获得图片的宽、高  
  29.             BitmapFactory.Options tmpOptions = new BitmapFactory.Options();  
  30.             tmpOptions.inJustDecodeBounds = true;  
  31.             BitmapFactory.decodeStream(inputStream, null, tmpOptions);  
  32.             int width = tmpOptions.outWidth;  
  33.             int height = tmpOptions.outHeight;  
  34.   
  35.             //设置显示图片的中心区域  
  36.             BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);  
  37.             BitmapFactory.Options options = new BitmapFactory.Options();  
  38.             options.inPreferredConfig = Bitmap.Config.RGB_565;  
  39.             Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 100, height / 2 - 100, width / 2 + 100, height / 2 + 100), options);  
  40.             mImageView.setImageBitmap(bitmap);  
  41.   
  42.   
  43.         } catch (IOException e)  
  44.         {  
  45.             e.printStackTrace();  
  46.         }  
  47.   
  48.   
  49.     }  
  50.   
  51. }  

上述代码,就是使用BitmapRegionDecoder去加载assets中的图片,调用bitmapRegionDecoder.decodeRegion解析图片的中间矩形区域,返回bitmap,最终显示在ImageView上。

根据上面的分析呢,我们这个自定义控件思路就非常清晰了:

  • 提供一个设置图片的入口
  • 重写onTouchEvent,在里面根据用户移动的手势,去更新显示区域的参数
  • 每次更新区域参数后,调用invalidate,onDraw里面去regionDecoder.decodeRegion拿到bitmap,去draw

代码如下:

[java] view plain copy
  1. import android.content.Context;  
  2. import android.graphics.Bitmap;  
  3. import android.graphics.BitmapFactory;  
  4. import android.graphics.BitmapRegionDecoder;  
  5. import android.graphics.Canvas;  
  6. import android.graphics.Rect;  
  7. import android.util.AttributeSet;  
  8. import android.view.MotionEvent;  
  9. import android.view.View;  
  10.   
  11. import java.io.IOException;  
  12. import java.io.InputStream;  
  13.   
  14. public class LargeImageView extends View  
  15. {  
  16.     private BitmapRegionDecoder mDecoder;  
  17.     /** 
  18.      * 图片的宽度和高度 
  19.      */  
  20.     private int mImageWidth, mImageHeight;  
  21.     /** 
  22.      * 绘制的区域 
  23.      */  
  24.     private volatile Rect mRect = new Rect();  
  25.   
  26.     private MoveGestureDetector mDetector;  
  27.   
  28.   
  29.     private static final BitmapFactory.Options options = new BitmapFactory.Options();  
  30.   
  31.     static  
  32.     {  
  33.         options.inPreferredConfig = Bitmap.Config.RGB_565;  
  34.     }  
  35.   
  36.     public void setInputStream(InputStream is)  
  37.     {  
  38.         try  
  39.         {  
  40.             mDecoder = BitmapRegionDecoder.newInstance(is, false);  
  41.             BitmapFactory.Options tmpOptions = new BitmapFactory.Options();  
  42.             // Grab the bounds for the scene dimensions  
  43.             tmpOptions.inJustDecodeBounds = true;  
  44.             BitmapFactory.decodeStream(is, null, tmpOptions);  
  45.             mImageWidth = tmpOptions.outWidth;  
  46.             mImageHeight = tmpOptions.outHeight;  
  47.   
  48.             requestLayout();  
  49.             invalidate();  
  50.         } catch (IOException e)  
  51.         {  
  52.             e.printStackTrace();  
  53.         } finally  
  54.         {  
  55.   
  56.             try  
  57.             {  
  58.                 if (is != null) is.close();  
  59.             } catch (Exception e)  
  60.             {  
  61.             }  
  62.         }  
  63.     }  
  64.   
  65.   
  66.     public void init()  
  67.     {  
  68.         mDetector = new MoveGestureDetector(getContext(), new MoveGestureDetector.SimpleMoveGestureDetector()  
  69.         {  
  70.             @Override  
  71.             public boolean onMove(MoveGestureDetector detector)  
  72.             {  
  73.                 int moveX = (int) detector.getMoveX();  
  74.                 int moveY = (int) detector.getMoveY();  
  75.   
  76.                 if (mImageWidth > getWidth())  
  77.                 {  
  78.                     mRect.offset(-moveX, 0);  
  79.                     checkWidth();  
  80.                     invalidate();  
  81.                 }  
  82.                 if (mImageHeight > getHeight())  
  83.                 {  
  84.                     mRect.offset(0, -moveY);  
  85.                     checkHeight();  
  86.                     invalidate();  
  87.                 }  
  88.   
  89.                 return true;  
  90.             }  
  91.         });  
  92.     }  
  93.   
  94.   
  95.     private void checkWidth()  
  96.     {  
  97.   
  98.   
  99.         Rect rect = mRect;  
  100.         int imageWidth = mImageWidth;  
  101.         int imageHeight = mImageHeight;  
  102.   
  103.         if (rect.right > imageWidth)  
  104.         {  
  105.             rect.right = imageWidth;  
  106.             rect.left = imageWidth - getWidth();  
  107.         }  
  108.   
  109.         if (rect.left < 0)  
  110.         {  
  111.             rect.left = 0;  
  112.             rect.right = getWidth();  
  113.         }  
  114.     }  
  115.   
  116.   
  117.     private void checkHeight()  
  118.     {  
  119.   
  120.         Rect rect = mRect;  
  121.         int imageWidth = mImageWidth;  
  122.         int imageHeight = mImageHeight;  
  123.   
  124.         if (rect.bottom > imageHeight)  
  125.         {  
  126.             rect.bottom = imageHeight;  
  127.             rect.top = imageHeight - getHeight();  
  128.         }  
  129.   
  130.         if (rect.top < 0)  
  131.         {  
  132.             rect.top = 0;  
  133.             rect.bottom = getHeight();  
  134.         }  
  135.     }  
  136.   
  137.   
  138.     public LargeImageView(Context context, AttributeSet attrs)  
  139.     {  
  140.         super(context, attrs);  
  141.         init();  
  142.     }  
  143.   
  144.     @Override  
  145.     public boolean onTouchEvent(MotionEvent event)  
  146.     {  
  147.         mDetector.onToucEvent(event);  
  148.         return true;  
  149.     }  
  150.   
  151.     @Override  
  152.     protected void onDraw(Canvas canvas)  
  153.     {  
  154.         Bitmap bm = mDecoder.decodeRegion(mRect, options);  
  155.         canvas.drawBitmap(bm, 00null);  
  156.     }  
  157.   
  158.     @Override  
  159.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  
  160.     {  
  161.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  162.   
  163.         int width = getMeasuredWidth();  
  164.         int height = getMeasuredHeight();  
  165.   
  166.         int imageWidth = mImageWidth;  
  167.         int imageHeight = mImageHeight;  
  168.   
  169.          //默认直接显示图片的中心区域,可以自己去调节  
  170.         mRect.left = imageWidth / 2 - width / 2;  
  171.         mRect.top = imageHeight / 2 - height / 2;  
  172.         mRect.right = mRect.left + width;  
  173.         mRect.bottom = mRect.top + height;  
  174.   
  175.     }  
  176.   
  177.   
  178. }  

根据上述源码:

  1. setInputStream里面去获得图片的真实的宽度和高度,以及初始化我们的mDecoder
  2. onMeasure里面为我们的显示区域的rect赋值,大小为view的尺寸
  3. onTouchEvent里面我们监听move的手势,在监听的回调里面去改变rect的参数,以及做边界检查,最后invalidate
  4. 在onDraw里面就是根据rect拿到bitmap,然后draw了

ok,上面并不复杂,不过大家有没有注意到,这个监听用户move手势的代码写的有点奇怪,恩,这里模仿了系统的ScaleGestureDetector,编写了MoveGestureDetector,代码如下:

  • MoveGestureDetector

    [java] view plain copy
    1. import android.content.Context;  
    2. import android.graphics.PointF;  
    3. import android.view.MotionEvent;  
    4.   
    5. public class MoveGestureDetector extends BaseGestureDetector  
    6. {  
    7.   
    8.     private PointF mCurrentPointer;  
    9.     private PointF mPrePointer;  
    10.     //仅仅为了减少创建内存  
    11.     private PointF mDeltaPointer = new PointF();  
    12.   
    13.     //用于记录最终结果,并返回  
    14.     private PointF mExtenalPointer = new PointF();  
    15.   
    16.     private OnMoveGestureListener mListenter;  
    17.   
    18.   
    19.     public MoveGestureDetector(Context context, OnMoveGestureListener listener)  
    20.     {  
    21.         super(context);  
    22.         mListenter = listener;  
    23.     }  
    24.   
    25.     @Override  
    26.     protected void handleInProgressEvent(MotionEvent event)  
    27.     {  
    28.         int actionCode = event.getAction() & MotionEvent.ACTION_MASK;  
    29.         switch (actionCode)  
    30.         {  
    31.             case MotionEvent.ACTION_CANCEL:  
    32.             case MotionEvent.ACTION_UP:  
    33.                 mListenter.onMoveEnd(this);  
    34.                 resetState();  
    35.                 break;  
    36.             case MotionEvent.ACTION_MOVE:  
    37.                 updateStateByEvent(event);  
    38.                 boolean update = mListenter.onMove(this);  
    39.                 if (update)  
    40.                 {  
    41.                     mPreMotionEvent.recycle();  
    42.                     mPreMotionEvent = MotionEvent.obtain(event);  
    43.                 }  
    44.                 break;  
    45.   
    46.         }  
    47.     }  
    48.   
    49.     @Override  
    50.     protected void handleStartProgressEvent(MotionEvent event)  
    51.     {  
    52.         int actionCode = event.getAction() & MotionEvent.ACTION_MASK;  
    53.         switch (actionCode)  
    54.         {  
    55.             case MotionEvent.ACTION_DOWN:  
    56.                 resetState();//防止没有接收到CANCEL or UP ,保险起见  
    57.                 mPreMotionEvent = MotionEvent.obtain(event);  
    58.                 updateStateByEvent(event);  
    59.                 break;  
    60.             case MotionEvent.ACTION_MOVE:  
    61.                 mGestureInProgress = mListenter.onMoveBegin(this);  
    62.                 break;  
    63.         }  
    64.   
    65.     }  
    66.   
    67.     protected void updateStateByEvent(MotionEvent event)  
    68.     {  
    69.         final MotionEvent prev = mPreMotionEvent;  
    70.   
    71.         mPrePointer = caculateFocalPointer(prev);  
    72.         mCurrentPointer = caculateFocalPointer(event);  
    73.   
    74.         //Log.e("TAG", mPrePointer.toString() + " ,  " + mCurrentPointer);  
    75.   
    76.         boolean mSkipThisMoveEvent = prev.getPointerCount() != event.getPointerCount();  
    77.   
    78.         //Log.e("TAG", "mSkipThisMoveEvent = " + mSkipThisMoveEvent);  
    79.         mExtenalPointer.x = mSkipThisMoveEvent ? 0 : mCurrentPointer.x - mPrePointer.x;  
    80.         mExtenalPointer.y = mSkipThisMoveEvent ? 0 : mCurrentPointer.y - mPrePointer.y;  
    81.   
    82.     }  
    83.   
    84.     /** 
    85.      * 根据event计算多指中心点 
    86.      * 
    87.      * @param event 
    88.      * @return 
    89.      */  
    90.     private PointF caculateFocalPointer(MotionEvent event)  
    91.     {  
    92.         final int count = event.getPointerCount();  
    93.         float x = 0, y = 0;  
    94.         for (int i = 0; i < count; i++)  
    95.         {  
    96.             x += event.getX(i);  
    97.             y += event.getY(i);  
    98.         }  
    99.   
    100.         x /= count;  
    101.         y /= count;  
    102.   
    103.         return new PointF(x, y);  
    104.     }  
    105.   
    106.   
    107.     public float getMoveX()  
    108.     {  
    109.         return mExtenalPointer.x;  
    110.   
    111.     }  
    112.   
    113.     public float getMoveY()  
    114.     {  
    115.         return mExtenalPointer.y;  
    116.     }  
    117.   
    118.   
    119.     public interface OnMoveGestureListener  
    120.     {  
    121.         public boolean onMoveBegin(MoveGestureDetector detector);  
    122.   
    123.         public boolean onMove(MoveGestureDetector detector);  
    124.   
    125.         public void onMoveEnd(MoveGestureDetector detector);  
    126.     }  
    127.   
    128.     public static class SimpleMoveGestureDetector implements OnMoveGestureListener  
    129.     {  
    130.   
    131.         @Override  
    132.         public boolean onMoveBegin(MoveGestureDetector detector)  
    133.         {  
    134.             return true;  
    135.         }  
    136.   
    137.         @Override  
    138.         public boolean onMove(MoveGestureDetector detector)  
    139.         {  
    140.             return false;  
    141.         }  
    142.   
    143.         @Override  
    144.         public void onMoveEnd(MoveGestureDetector detector)  
    145.         {  
    146.         }  
    147.     }  
    148.   
    149. }  

  • BaseGestureDetector

    [java] view plain copy
    1. import android.content.Context;  
    2. import android.view.MotionEvent;  
    3.   
    4.   
    5. public abstract class BaseGestureDetector  
    6. {  
    7.   
    8.     protected boolean mGestureInProgress;  
    9.   
    10.     protected MotionEvent mPreMotionEvent;  
    11.     protected MotionEvent mCurrentMotionEvent;  
    12.   
    13.     protected Context mContext;  
    14.   
    15.     public BaseGestureDetector(Context context)  
    16.     {  
    17.         mContext = context;  
    18.     }  
    19.   
    20.   
    21.     public boolean onToucEvent(MotionEvent event)  
    22.     {  
    23.   
    24.         if (!mGestureInProgress)  
    25.         {  
    26.             handleStartProgressEvent(event);  
    27.         } else  
    28.         {  
    29.             handleInProgressEvent(event);  
    30.         }  
    31.   
    32.         return true;  
    33.   
    34.     }  
    35.   
    36.     protected abstract void handleInProgressEvent(MotionEvent event);  
    37.   
    38.     protected abstract void handleStartProgressEvent(MotionEvent event);  
    39.   
    40.     protected abstract void updateStateByEvent(MotionEvent event);  
    41.   
    42.     protected void resetState()  
    43.     {  
    44.         if (mPreMotionEvent != null)  
    45.         {  
    46.             mPreMotionEvent.recycle();  
    47.             mPreMotionEvent = null;  
    48.         }  
    49.         if (mCurrentMotionEvent != null)  
    50.         {  
    51.             mCurrentMotionEvent.recycle();  
    52.             mCurrentMotionEvent = null;  
    53.         }  
    54.         mGestureInProgress = false;  
    55.     }  
    56.   
    57.   
    58. }  

    你可能会说,一个move手势搞这么多代码,太麻烦了。的确是的,move手势的检测非常简单,那么之所以这么写呢,主要是为了可以复用,比如现在有一堆的XXXGestureDetector,当我们需要监听什么手势,就直接拿个detector来检测多方便。我相信大家肯定也郁闷过Google,为什么只有ScaleGestureDetector而没有RotateGestureDetector呢。

原创粉丝点击