超大图片显示,可任意缩放,移动,不用DiskLruCache

时间:2022-07-11 12:56:11

1.演示,代码

  下载示例apk      下载项目 :  https://gitee.com/xi/LImage.git

  超大图片显示,可任意缩放,移动,不用DiskLruCache

2.遇到的问题

  • 想省内存,不太可能
  • 只支持拖拽手势,不支持缩放相对简单,解码view对应的区域就可以。
  • 不支持缩放好像说不过去,同时支持缩放+拖拽后变复杂,如转屏后的位置,指定锚点缩放,缩放后又移动,移动后又缩放。
  • 用系统图库打开图片?直接打开超大图片不能正常显示.用图库打开图片所在目录?没有找到相关代码.

3.缩放,移动的思路

3.1 缩放思路

  • 用一张解码区域是整张图片,但是inSampleSize大小适中的缩略图缩放.虽然图片是不清晰的,但是省内存,省解码时间.
  • 缩小过程不需要高清的.
  • 放大到100%的时候就重新解码当前view对应区域的位图,inSampleSize = 1 .

3.2 移动思路

  • 当缩放比例小于100%时,就移动缩略图,
  • 当缩放比例等于100%时,按当前view区域大小平移,重新解码对应的位图.

4.核心代码

4.1 解码

  • BitmapRegionDecoder 可以指定一个区域(大图范围内,超出会抛异常)对一张大位图解码,解出一张小位图.然后在view上显示这个小位图.
  • BitmapFactory.Options 解码选项,下面是常用的几个重要选项:
inSampleSize 缩略大小,值为2的n次幂(n为自然数).其它值无意义

inPreferredConfig

色彩模式,决定一个像素占用的字节数

inBitmap

指定复用的位图..不用在申请内存. 
  • 代码
     private BitmapRegionDecoder     mDecoder;
     private BitmapFactory.Options   mOptions;
     private Rect                    mDecodeRegion;
     void initRegionDecoder(){
         try {
             InputStream fs = getResources().getAssets().open("world_map.jpg");
             mDecoder = BitmapRegionDecoder.newInstance(fs,false);
             mBitmapWidth = mDecoder.getWidth();
             mBitmapHeight = mDecoder.getHeight();

             mOptions = new BitmapFactory.Options();
             mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
             mOptions.inSampleSize = ;

         } catch (IOException e) {

             e.printStackTrace();
         }

     }

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        //...

         mDecodeRegion = new Rect(left,top,left + viewWidth ,top + viewHeight );
 //        mDecodeRegion = new Rect(0,0,mBitmapWidth ,mBitmapHeight);

         mOptions.inSampleSize = calculateInSampleSize(mDecodeRegion.width(),mDecodeRegion.height(),viewWidth,viewHeight);
 //        mOptions.inSampleSize = 2;

         long begin,end;
         begin = SystemClock.elapsedRealtime();
         mBitmap = mDecoder.decodeRegion(mDecodeRegion,mOptions);
         end = SystemClock.elapsedRealtime();

         //..
     }

4.2 手势

  • ScaleGestureDetector   缩放手势识别器
  • GestureDetectorCompat  移动手势识别器

  这两个比较简单,先构造,然后在 public boolean onTouchEvent(MotionEvent event) 里接收触摸事件,最后在相应的手势回调方法处理手势就可以.

4.3 变形

  缩放: 用变形矩阵.小于100%时移动也用变形矩阵,等于100%时自己计算.

     @Override
     protected void onDraw(Canvas canvas) {

         if (mPercentage < 100.0f && mThumbnailBitmap != null){
             canvas.drawBitmap(mThumbnailBitmap,mMatrix,mPaint);
         }else if(mPercentage == 100.0f && mBitmap != null){
             canvas.drawBitmap(mBitmap,mBitmapLeft,mBitmapTop,mPaint);
         }
         //...

     }

4.4 LargeImageView.java

 package com.example.ff.limage;

 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapRegionDecoder;
 import android.graphics.BlurMaskFilter;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.AsyncTask;
 import android.os.Environment;
 import android.os.Parcelable;
 import android.os.SystemClock;
 import android.support.annotation.Nullable;
 import android.support.v4.view.GestureDetectorCompat;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 import android.view.ScaleGestureDetector;
 import android.view.View;

 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.math.BigDecimal;

 import static android.os.Environment.isExternalStorageRemovable;

 public class LargeImageView extends View {

     private String                  TAG = "LargeImageView";

     private Paint                   mPaint;
     private BitmapRegionDecoder     mDecoder;
     private BitmapFactory.Options   mOptions;
     private Rect                    mDecodeRegion;
     private static Bitmap           mThumbnailBitmap;
     private Bitmap                  mBitmap;
     private Matrix                  mMatrix;
     private float                   thumbnailWidth,thumbnailHeight;
     private float                   mPercentage;
     private float                   mThumbnailLeft,mThumbnailTop,mBitmapLeft,mBitmapTop;
     private int                     mBitmapWidth,mBitmapHeight;
     private Point                   mFocus;
     private boolean                 mScaling = false;

     private DiskLruCache            mDiskLruCache;

     public void destroyBitmaps(){
         Log.e(TAG, "destroyBitmaps: release bitmaps" );
         if (!mDecoder.isRecycled()) {
             mDecoder.recycle();
         }
         if (!mBitmap.isRecycled()){
             mBitmap.recycle();
             mBitmap = null;
         }
     }
     public void destroyThumbnailBitmap(){
         if (!mThumbnailBitmap.isRecycled()){
             mThumbnailBitmap.recycle();
             mThumbnailBitmap = null;
         }
     }
     public void createThumbnailBitmap(){
         if (null == mThumbnailBitmap){
             DecodeTask  task = new DecodeTask();
             task.execute(mDecodeRegion);
         }
     }
     public void createBitmaps(){

     }
     private class DecodeTask extends AsyncTask<Rect,Void,Void>{

         @Override
         protected Void doInBackground(Rect... rects) {
             Log.e(TAG, "doInBackground: "  );
             long begin,end;

             mDecodeRegion = ,,mBitmapWidth,mBitmapHeight);

             mOptions.inSampleSize = ;

             begin = SystemClock.elapsedRealtime();
             mThumbnailBitmap = mDecoder.decodeRegion(mDecodeRegion,mOptions);
             end = SystemClock.elapsedRealtime();
             Log.e(TAG, "doInBackground: decode mThumbnailBitmap need " + (end - begin) + "  ms. "  );

             return null;
         }
     }

     @Override
     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
     }

     // 打印Matrix内数据
     void showMatrix(Matrix matrix,String tag){
         // 下面的代码是为了查看matrix中的元素
         ];
         matrix.getValues(matrixValues);
         String temp ;
         ; i < ; ++i) {
             temp = "";
             ; j < ; ++j) {
                 temp += matrixValues[ * i + j] + "\t";
             }
             Log.e(tag, temp);
         }
     }
     private void printThumbnailRect(){
         float left = mThumbnailLeft;
         float top = mThumbnailTop;
         float right = mThumbnailLeft + thumbnailWidth;
         float bottom = mThumbnailTop + thumbnailHeight;

         Log.e(TAG, "printThumbnailRect: left = " + left + " top = " + top + " right = " + right + " bottom = " + bottom
             + " width = " + thumbnailWidth + " height = " + thumbnailHeight);
     }
     private ScaleGestureDetector scaleDetector;
     private ScaleGestureDetector.SimpleOnScaleGestureListener scaleGestureListener = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
         @Override
         public boolean onScale(ScaleGestureDetector detector) {
             float factor = detector.getScaleFactor();
              ) return true;
              && factor > ){
                 return true;
             }
             if (thumbnailWidth * factor < mBitmapWidth){
                 if (thumbnailWidth * factor >= mBitmapWidth * 0.01f){
                     thumbnailWidth *= factor;
                     thumbnailHeight *= factor;
                     mMatrix.postScale(factor,factor,detector.getFocusX(),detector.getFocusY());
                 }
             }else{
                 factor = mBitmapWidth / thumbnailWidth;
                 mMatrix.postScale(factor,factor,detector.getFocusX(),detector.getFocusY());

                 thumbnailWidth = mBitmapWidth;
                 thumbnailHeight = mBitmapHeight;
                 mPercentage = 100.0f;

                 ];
                 mMatrix.getValues(matrix);
                 mThumbnailLeft = matrix[];
                 mThumbnailTop = matrix[];

                 float left = mThumbnailLeft;
                 float top = mThumbnailTop;
                 float right = mThumbnailLeft + thumbnailWidth;
                 float bottom = mThumbnailTop + thumbnailHeight;

                 int viewWith = getWidth();
                 int viewHeight = getHeight();

                 mBitmapLeft = ;
                 mBitmapTop = ;

                 ){
                     mBitmapLeft = left;
                     mDecodeRegion.left = ;
                     mDecodeRegion.right = (int) (viewWith - left);
                     ){
                         mBitmapTop = top;
                         mDecodeRegion.top = ;
                         mDecodeRegion.bottom = (int) (viewHeight - top);
                     }else if(bottom < viewHeight){
                         mDecodeRegion.bottom = mBitmapHeight;
                         mDecodeRegion.top = (int) (mBitmapHeight - bottom);
                     }else {
                         mDecodeRegion.top = (int) -top;
                         mDecodeRegion.bottom = mDecodeRegion.top + viewHeight;
                     }
                 }){
                     mBitmapTop = top;
                     mDecodeRegion.top = ;
                     mDecodeRegion.bottom = (int) (viewHeight - top);
                     if (right < viewWith){
                         mDecodeRegion.right = mBitmapWidth;
                         mDecodeRegion.left = (int) (mBitmapWidth - right);
                     }else{
                         mDecodeRegion.left = (int) -left;
                         mDecodeRegion.right = mDecodeRegion.left + viewWith;
                     }
                 }else if(right < viewWith ){
                     mDecodeRegion.right = mBitmapWidth;
                     mDecodeRegion.left = (int) (mBitmapWidth - right);
                     if(bottom < viewHeight){
                         mDecodeRegion.bottom = mBitmapHeight;
                         mDecodeRegion.top = (int) (mBitmapHeight - bottom);
                     }else{
                         mDecodeRegion.top = (int) -top;
                         mDecodeRegion.bottom = mDecodeRegion.top + viewHeight;
                     }
                 }else if(bottom < viewHeight){
                     mDecodeRegion.left = (int) -left;
                     mDecodeRegion.right = mDecodeRegion.left + viewWith;

                     mDecodeRegion.bottom = mBitmapHeight;
                     mDecodeRegion.top = (int) (mBitmapHeight - bottom);
                 }else{
                     mDecodeRegion.left = (int) -left;
                     mDecodeRegion.right = mDecodeRegion.left + viewWith;
                     mDecodeRegion.top = (int) -top;
                     mDecodeRegion.bottom = mDecodeRegion.top + viewHeight;
                 }
                 mOptions.inSampleSize = ;

                  && mDecodeRegion.height() > ){
                     mBitmap = mDecoder.decodeRegion(mDecodeRegion,mOptions);
                 }else{
                     if (mBitmap != null && !mBitmap.isRecycled()){
                         mBitmap.recycle();
                         mBitmap = null;
                     }
                 }

             }
             BigDecimal bd = ).setScale(,BigDecimal.ROUND_HALF_UP);
             mPercentage = bd.floatValue();
             Log.i(TAG, "onScale: bitmap.w = " + mThumbnailBitmap.getWidth() + " bitmap.h = " + mThumbnailBitmap.getHeight()
                     + " thumbnailWidth = " + thumbnailWidth + " thumbnailHeight = " + thumbnailHeight);

             invalidate();
             return true;
         }

         @Override
         public boolean onScaleBegin(ScaleGestureDetector detector) {

             mScaling = true;

             float focusX = detector.getFocusX();
             float focusY = detector.getFocusY();
             mFocus = new Point((int)focusX,(int)focusY);

             return true;
         }

         @Override
         public void onScaleEnd(ScaleGestureDetector detector) {
             mScaling = false;
             mFocus = null;

             super.onScaleEnd(detector);
         }
     };

     private GestureDetectorCompat scrollDetector;
     private GestureDetector.SimpleOnGestureListener simpleOnGestureListener = new GestureDetector.SimpleOnGestureListener() {
         @Override
         public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {

              && Math.abs(distanceY) < ) return true;

             ];
             mMatrix.getValues(matrix);
             mThumbnailLeft = matrix[];
             mThumbnailTop = matrix[];
             int viewWith = getWidth();
             int viewHeight = getHeight();

             if (mThumbnailLeft <= viewWith && mThumbnailLeft >= -thumbnailWidth
                     && mThumbnailTop <= viewHeight && mThumbnailTop >= -thumbnailHeight){
                  && viewWith - mThumbnailLeft < -distanceX){
                     distanceX = -(viewWith - mThumbnailLeft);
                 } && mThumbnailLeft + thumbnailWidth < distanceX){
                     distanceX = mThumbnailLeft + thumbnailWidth;
                 }
                  && viewHeight - mThumbnailTop < -distanceY){
                     distanceY = -(viewHeight - mThumbnailTop);
                 } && mThumbnailTop + thumbnailHeight < distanceY){
                     distanceY = mThumbnailTop + thumbnailHeight;
                 }

                 mMatrix.postTranslate(-distanceX,-distanceY);

                 if (mPercentage == 100.0f){
                     mMatrix.getValues(matrix);
                     mThumbnailLeft = matrix[];
                     mThumbnailTop = matrix[];

                     float left = mThumbnailLeft;
                     float top = mThumbnailTop;
                     float right = mThumbnailLeft + thumbnailWidth;
                     float bottom = mThumbnailTop + thumbnailHeight;

                     mBitmapLeft = ;
                     mBitmapTop = ;

                     ){
                         mBitmapLeft = left;
                         mDecodeRegion.left = ;
                         mDecodeRegion.right = (int) (viewWith - left);
                         ){
                             mBitmapTop = top;
                             mDecodeRegion.top = ;
                             mDecodeRegion.bottom = (int) (viewHeight - top);
                         }else if(bottom < viewHeight){
                             mDecodeRegion.bottom = mBitmapHeight;
                             mDecodeRegion.top = (int) (mBitmapHeight - bottom);
                         }else {
                             mDecodeRegion.top = (int) -top;
                             mDecodeRegion.bottom = mDecodeRegion.top + viewHeight;
                         }
                     }){
                         mBitmapTop = top;
                         mDecodeRegion.top = ;
                         mDecodeRegion.bottom = (int) (viewHeight - top);
                         if (right < viewWith){
                             mDecodeRegion.right = mBitmapWidth;
                             mDecodeRegion.left = (int) (mBitmapWidth - right);
                         }else{
                             mDecodeRegion.left = (int) -left;
                             mDecodeRegion.right = mDecodeRegion.left + viewWith;
                         }
                     }else if(right < viewWith ){
                         mDecodeRegion.right = mBitmapWidth;
                         mDecodeRegion.left = (int) (mBitmapWidth - right);
                         if(bottom < viewHeight){
                             mDecodeRegion.bottom = mBitmapHeight;
                             mDecodeRegion.top = (int) (mBitmapHeight - bottom);
                         }else{
                             mDecodeRegion.top = (int) -top;
                             mDecodeRegion.bottom = mDecodeRegion.top + viewHeight;
                         }
                     }else if(bottom < viewHeight){
                         mDecodeRegion.left = (int) -left;
                         mDecodeRegion.right = mDecodeRegion.left + viewWith;

                         mDecodeRegion.bottom = mBitmapHeight;
                         mDecodeRegion.top = (int) (mBitmapHeight - bottom);
                     }else{
                         mDecodeRegion.left = (int) -left;
                         mDecodeRegion.right = mDecodeRegion.left + viewWith;
                         mDecodeRegion.top = (int) -top;
                         mDecodeRegion.bottom = mDecodeRegion.top + viewHeight;
                     }
                     mOptions.inSampleSize = ;

                     Log.e(TAG, "onScroll: mDecodeRegion = " + mDecodeRegion );
                      && mDecodeRegion.height() > ){
                         mBitmap = mDecoder.decodeRegion(mDecodeRegion,mOptions);
                     }else{
                         mBitmap = null;
                     }
                 }
                 invalidate();
             }
             return true;
         }

         @Override
         public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
             return true;
         }
     };
     private void initGestureDetector() {
         scrollDetector = new GestureDetectorCompat(getContext(), simpleOnGestureListener);
         scaleDetector = new ScaleGestureDetector(getContext(), scaleGestureListener);
     }

     @Override
     public boolean onTouchEvent(MotionEvent event) {
         boolean ret = scaleDetector.onTouchEvent(event);
         ret |= scrollDetector.onTouchEvent(event);
         return ret || true;
     }
      *  * ; // 256MB
     private static final String DISK_CACHE_SUBDIR = "thumbnails";

     void initCache(){

         try {
             Context context = getContext();
             File cacheDir = context.getCacheDir();

             cacheDir = context.getExternalCacheDir();

             final String cachePath = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||!isExternalStorageRemovable()
                     ? context.getExternalCacheDir().getPath()
                     : context.getCacheDir().getPath();
             cacheDir = new File(cachePath + File.separator + DISK_CACHE_SUBDIR);

             PackageManager pm = context.getPackageManager();
             PackageInfo pi = pm.getPackageInfo(context.getPackageName(), );

             int appVersion = pi.versionCode;
             ;

             mDiskLruCache = DiskLruCache.open(cacheDir, appVersion,valueCount,DISK_CACHE_SIZE);

         } catch (IOException e) {
             e.printStackTrace();
         } catch (PackageManager.NameNotFoundException e) {
             e.printStackTrace();
         }

     }
     void init(){
         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
         mPaint.setStyle(Paint.Style.FILL);
         mPaint.setTextSize();
         setLayerType(View.LAYER_TYPE_SOFTWARE,mPaint);

 //        LinearGradient backGradient = new LinearGradient(0, 0, 0, mPaint.getTextSize(), new int[]{Color.BLACK, Color.GRAY ,Color.YELLOW}, null, Shader.TileMode.CLAMP);
 //        mPaint.setShader(backGradient);

         mMatrix = new Matrix();

 //        initCache();

         initGestureDetector();

         initRegionDecoder();

     }
     void initRegionDecoder(){
         try {
             InputStream fs = getResources().getAssets().open("world_map.jpg");
             mDecoder = BitmapRegionDecoder.newInstance(fs,false);
             mBitmapWidth = mDecoder.getWidth();
             mBitmapHeight = mDecoder.getHeight();

             mOptions = new BitmapFactory.Options();
             mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
             mOptions.inSampleSize = ;

         } catch (IOException e) {

             e.printStackTrace();
         }

     }
     private void getAttrs(Context context, AttributeSet attrs) {
         TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LargeImageViewAttrs);
         String file = ta.getString(R.styleable.LargeImageViewAttrs_src);
         ta.recycle();
     }

     public LargeImageView(Context context) {
         super(context);
         init();
     }

     public LargeImageView(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
         getAttrs(context,attrs);
         init();
     }

     public LargeImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         getAttrs(context,attrs);
         init();
     }

     @SuppressLint("NewApi")
     public LargeImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         getAttrs(context,attrs);
         init();
     }

     @Override
     protected void onRestoreInstanceState(Parcelable state) {
         super.onRestoreInstanceState(state);
     }

     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
     }

     public static int calculateInSampleSize(int width, int height, int reqWidth, int reqHeight) {
         // Raw height and width of image
         ;

         if (height > reqHeight || width > reqWidth) {

             final ;
             final ;

              || halfWidth <= ) return inSampleSize;

             if (width > height) {
                 // Calculate the largest inSampleSize value that is a power of 2 and keeps both
                 // height and width larger than the requested height and width.
                 while ((halfWidth / inSampleSize) >= reqWidth) {
                     inSampleSize *= ;
                 }
             } else {
                 while ((halfHeight / inSampleSize) >= reqHeight) {
                     inSampleSize *= ;
                 }
             }
         }
         return inSampleSize;
     }

     @Override
     protected void onDraw(Canvas canvas) {

         if (mPercentage < 100.0f && mThumbnailBitmap != null){
             canvas.drawBitmap(mThumbnailBitmap,mMatrix,mPaint);
         }else if(mPercentage == 100.0f && mBitmap != null){
             canvas.drawBitmap(mBitmap,mBitmapLeft,mBitmapTop,mPaint);
         }
         if (mFocus != null){
             mPaint.setColor(Color.RED);
             mPaint.setMaskFilter(, BlurMaskFilter.Blur.SOLID));
             mPaint.setColor(Color.parseColor("#ff0000"));
             canvas.drawCircle(mFocus.x,mFocus.y,,mPaint);
         }

         //draw percentage
         String percentage = mPercentage + "%";

         float textSize = mPaint.getTextSize();
         float percentWidth = mPaint.measureText("100.0%");

         float circleX = getWidth() - percentWidth  ;
         float circleY = percentWidth  ;

         mPaint.setColor(Color.parseColor("#7f7A378B"));
         mPaint.setMaskFilter(, BlurMaskFilter.Blur.NORMAL));
         canvas.drawCircle(circleX ,circleY,percentWidth * 0.66f,mPaint);

         mPaint.setColor(Color.WHITE);
         mPaint.setMaskFilter(null);
         percentWidth = mPaint.measureText(percentage);
         circleY += textSize / ;
         circleX -= percentWidth / ;
         canvas.drawText(percentage,circleX ,circleY,mPaint);

     }
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

         Log.e(TAG, "onMeasure: " );

         int viewWidth = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();

         ) viewWidth = ;

         viewWidth = resolveSizeAndState(viewWidth, widthMeasureSpec, );

         int viewHeight = getSuggestedMinimumHeight() + getPaddingBottom() + getPaddingTop();

         ) viewHeight = ;

         //计算最佳值,在其中解析了 heightMeasureSpec
         viewHeight = resolveSizeAndState(viewHeight, heightMeasureSpec, );

         //将量算的结果保存到View的成员变量mMeasuredWidth 和mMeasuredHeight中。
         setMeasuredDimension(viewWidth, viewHeight);

         // 量算完成之后,View的父控件就可以通过调用
         // getMeasuredWidth、getMeasuredState、getMeasuredWidthAndState
         // 这三个方法获取View的量算结果。

         mBitmapLeft = ;
         mBitmapTop = ;

          - viewWidth / ;
          - viewHeight / ;

         mDecodeRegion = new Rect(left,top,left + viewWidth ,top + viewHeight );
 //        mDecodeRegion = new Rect(0,0,mBitmapWidth ,mBitmapHeight);

         mOptions.inSampleSize = calculateInSampleSize(mDecodeRegion.width(),mDecodeRegion.height(),viewWidth,viewHeight);
 //        mOptions.inSampleSize = 2;

         long begin,end;
         begin = SystemClock.elapsedRealtime();
         mBitmap = mDecoder.decodeRegion(mDecodeRegion,mOptions);
         end = SystemClock.elapsedRealtime();

         thumbnailWidth = mBitmapWidth;
         thumbnailHeight = mBitmapHeight;

         BigDecimal bd = ).setScale(,BigDecimal.ROUND_HALF_UP);
         mPercentage = bd.floatValue();

         Log.e(TAG, "init region = " + mDecodeRegion + " ratio = " + (float) mDecodeRegion.width() / mDecodeRegion.height()
                 + " bitmap.w = " + mBitmap.getWidth() + " bitmap.h = " + mBitmap.getHeight() + " ratio = " + (float) mBitmap.getWidth() / mBitmap.getHeight()
                 + " need " + (end - begin) + " ms");
         if (mThumbnailBitmap != null){
             Log.e(TAG, "onMeasure : mThumbnailBitmap is ok.");

             float width = mThumbnailBitmap.getWidth();
             float height = mThumbnailBitmap.getHeight();
              - width / ;
              - height / ;
             mMatrix.setTranslate(dx,dy);

             float factor = mBitmapWidth / width;
             mMatrix.postScale(factor,factor,viewWidth/ ,viewHeight /);

         }
     }

     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         Log.e(TAG, "onFinishInflate: " );
         createThumbnailBitmap();
     }

     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         Log.e(TAG, "onAttachedToWindow: " );
     }
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         Log.e(TAG, "onDetachedFromWindow: " );

     }

     @Override
     protected void onWindowVisibilityChanged(int visibility) {
         super.onWindowVisibilityChanged(visibility);
         Log.e(TAG, "onWindowVisibilityChanged : "  + visibility);

     }
     @Override
     public void onWindowFocusChanged(boolean hasWindowFocus) {
         super.onWindowFocusChanged(hasWindowFocus);
         Log.e(TAG, "onWindowFocusChanged:  hasWindowFocus = " + hasWindowFocus );
     }

     Bitmap loadBitmapFromCache(String key){
         Bitmap bitmap = null;
         try {
             DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
             if (snapshot != null) {
                 InputStream );
                 bitmap = BitmapFactory.decodeStream(is);
             }
         } catch (IOException e) {
             e.printStackTrace();
         }
         return bitmap;
     }
     void pushToCache(String key, Bitmap value){
         try {
             DiskLruCache.Editor editor = mDiskLruCache.edit(key);

             if (editor != null){
                 OutputStream );
                 value.compress(Bitmap.CompressFormat.JPEG, , out);
                 out.flush();
                 editor.commit();
                 mDiskLruCache.flush();
             }

         } catch (IOException e) {
             e.printStackTrace();
         }
     }
 }