1.演示,代码
下载示例apk 下载项目 : https://gitee.com/xi/LImage.git
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 |
指定复用的位图..不用在申请内存. |
- 代码
1 private BitmapRegionDecoder mDecoder; 2 private BitmapFactory.Options mOptions; 3 private Rect mDecodeRegion; 4 void initRegionDecoder(){ 5 try { 6 InputStream fs = getResources().getAssets().open("world_map.jpg"); 7 mDecoder = BitmapRegionDecoder.newInstance(fs,false); 8 mBitmapWidth = mDecoder.getWidth(); 9 mBitmapHeight = mDecoder.getHeight(); 10 11 mOptions = new BitmapFactory.Options(); 12 mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888; 13 mOptions.inSampleSize = 1; 14 15 } catch (IOException e) { 16 17 e.printStackTrace(); 18 } 19 20 } 21 22 23 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 24 25 //... 26 27 mDecodeRegion = new Rect(left,top,left + viewWidth ,top + viewHeight ); 28 // mDecodeRegion = new Rect(0,0,mBitmapWidth ,mBitmapHeight); 29 30 mOptions.inSampleSize = calculateInSampleSize(mDecodeRegion.width(),mDecodeRegion.height(),viewWidth,viewHeight); 31 // mOptions.inSampleSize = 2; 32 33 long begin,end; 34 begin = SystemClock.elapsedRealtime(); 35 mBitmap = mDecoder.decodeRegion(mDecodeRegion,mOptions); 36 end = SystemClock.elapsedRealtime(); 37 38 //.. 39 }
4.2 手势
- ScaleGestureDetector 缩放手势识别器
- GestureDetectorCompat 移动手势识别器
这两个比较简单,先构造,然后在 public boolean onTouchEvent(MotionEvent event) 里接收触摸事件,最后在相应的手势回调方法处理手势就可以.
4.3 变形
缩放: 用变形矩阵.小于100%时移动也用变形矩阵,等于100%时自己计算.
1 @Override 2 protected void onDraw(Canvas canvas) { 3 4 if (mPercentage < 100.0f && mThumbnailBitmap != null){ 5 canvas.drawBitmap(mThumbnailBitmap,mMatrix,mPaint); 6 }else if(mPercentage == 100.0f && mBitmap != null){ 7 canvas.drawBitmap(mBitmap,mBitmapLeft,mBitmapTop,mPaint); 8 } 9 //... 10 11 }
4.4 LargeImageView.java
1 package com.example.ff.limage; 2 3 import android.annotation.SuppressLint; 4 import android.content.Context; 5 import android.content.pm.PackageInfo; 6 import android.content.pm.PackageManager; 7 import android.content.res.TypedArray; 8 import android.graphics.Bitmap; 9 import android.graphics.BitmapFactory; 10 import android.graphics.BitmapRegionDecoder; 11 import android.graphics.BlurMaskFilter; 12 import android.graphics.Canvas; 13 import android.graphics.Color; 14 import android.graphics.Matrix; 15 import android.graphics.Paint; 16 import android.graphics.Point; 17 import android.graphics.Rect; 18 import android.os.AsyncTask; 19 import android.os.Environment; 20 import android.os.Parcelable; 21 import android.os.SystemClock; 22 import android.support.annotation.Nullable; 23 import android.support.v4.view.GestureDetectorCompat; 24 import android.util.AttributeSet; 25 import android.util.Log; 26 import android.view.GestureDetector; 27 import android.view.MotionEvent; 28 import android.view.ScaleGestureDetector; 29 import android.view.View; 30 31 import java.io.File; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.io.OutputStream; 35 import java.math.BigDecimal; 36 37 import static android.os.Environment.isExternalStorageRemovable; 38 39 public class LargeImageView extends View { 40 41 private String TAG = "LargeImageView"; 42 43 private Paint mPaint; 44 private BitmapRegionDecoder mDecoder; 45 private BitmapFactory.Options mOptions; 46 private Rect mDecodeRegion; 47 private static Bitmap mThumbnailBitmap; 48 private Bitmap mBitmap; 49 private Matrix mMatrix; 50 private float thumbnailWidth,thumbnailHeight; 51 private float mPercentage; 52 private float mThumbnailLeft,mThumbnailTop,mBitmapLeft,mBitmapTop; 53 private int mBitmapWidth,mBitmapHeight; 54 private Point mFocus; 55 private boolean mScaling = false; 56 57 58 private DiskLruCache mDiskLruCache; 59 60 61 public void destroyBitmaps(){ 62 Log.e(TAG, "destroyBitmaps: release bitmaps" ); 63 if (!mDecoder.isRecycled()) { 64 mDecoder.recycle(); 65 } 66 if (!mBitmap.isRecycled()){ 67 mBitmap.recycle(); 68 mBitmap = null; 69 } 70 } 71 public void destroyThumbnailBitmap(){ 72 if (!mThumbnailBitmap.isRecycled()){ 73 mThumbnailBitmap.recycle(); 74 mThumbnailBitmap = null; 75 } 76 } 77 public void createThumbnailBitmap(){ 78 if (null == mThumbnailBitmap){ 79 DecodeTask task = new DecodeTask(); 80 task.execute(mDecodeRegion); 81 } 82 } 83 public void createBitmaps(){ 84 85 } 86 private class DecodeTask extends AsyncTask<Rect,Void,Void>{ 87 88 @Override 89 protected Void doInBackground(Rect... rects) { 90 Log.e(TAG, "doInBackground: " ); 91 long begin,end; 92 93 mDecodeRegion = new Rect(0,0,mBitmapWidth,mBitmapHeight); 94 95 mOptions.inSampleSize = 2; 96 97 begin = SystemClock.elapsedRealtime(); 98 mThumbnailBitmap = mDecoder.decodeRegion(mDecodeRegion,mOptions); 99 end = SystemClock.elapsedRealtime(); 100 Log.e(TAG, "doInBackground: decode mThumbnailBitmap need " + (end - begin) + " ms. " ); 101 102 return null; 103 } 104 } 105 106 @Override 107 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 108 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 109 } 110 111 // 打印Matrix内数据 112 void showMatrix(Matrix matrix,String tag){ 113 // 下面的代码是为了查看matrix中的元素 114 float[] matrixValues = new float[9]; 115 matrix.getValues(matrixValues); 116 String temp ; 117 for (int i = 0; i < 3; ++i) { 118 temp = ""; 119 for (int j = 0; j < 3; ++j) { 120 temp += matrixValues[3 * i + j] + " "; 121 } 122 Log.e(tag, temp); 123 } 124 } 125 private void printThumbnailRect(){ 126 float left = mThumbnailLeft; 127 float top = mThumbnailTop; 128 float right = mThumbnailLeft + thumbnailWidth; 129 float bottom = mThumbnailTop + thumbnailHeight; 130 131 Log.e(TAG, "printThumbnailRect: left = " + left + " top = " + top + " right = " + right + " bottom = " + bottom 132 + " width = " + thumbnailWidth + " height = " + thumbnailHeight); 133 } 134 private ScaleGestureDetector scaleDetector; 135 private ScaleGestureDetector.SimpleOnScaleGestureListener scaleGestureListener = new ScaleGestureDetector.SimpleOnScaleGestureListener() { 136 @Override 137 public boolean onScale(ScaleGestureDetector detector) { 138 float factor = detector.getScaleFactor(); 139 if (factor == 1 ) return true; 140 if (mPercentage == 100 && factor > 1){ 141 return true; 142 } 143 if (thumbnailWidth * factor < mBitmapWidth){ 144 if (thumbnailWidth * factor >= mBitmapWidth * 0.01f){ 145 thumbnailWidth *= factor; 146 thumbnailHeight *= factor; 147 mMatrix.postScale(factor,factor,detector.getFocusX(),detector.getFocusY()); 148 } 149 }else{ 150 factor = mBitmapWidth / thumbnailWidth; 151 mMatrix.postScale(factor,factor,detector.getFocusX(),detector.getFocusY()); 152 153 thumbnailWidth = mBitmapWidth; 154 thumbnailHeight = mBitmapHeight; 155 mPercentage = 100.0f; 156 157 float matrix[] = new float[9]; 158 mMatrix.getValues(matrix); 159 mThumbnailLeft = matrix[2]; 160 mThumbnailTop = matrix[5]; 161 162 float left = mThumbnailLeft; 163 float top = mThumbnailTop; 164 float right = mThumbnailLeft + thumbnailWidth; 165 float bottom = mThumbnailTop + thumbnailHeight; 166 167 int viewWith = getWidth(); 168 int viewHeight = getHeight(); 169 170 mBitmapLeft = 0; 171 mBitmapTop = 0; 172 173 if (left > 0){ 174 mBitmapLeft = left; 175 mDecodeRegion.left = 0; 176 mDecodeRegion.right = (int) (viewWith - left); 177 if (top > 0){ 178 mBitmapTop = top; 179 mDecodeRegion.top = 0; 180 mDecodeRegion.bottom = (int) (viewHeight - top); 181 }else if(bottom < viewHeight){ 182 mDecodeRegion.bottom = mBitmapHeight; 183 mDecodeRegion.top = (int) (mBitmapHeight - bottom); 184 }else { 185 mDecodeRegion.top = (int) -top; 186 mDecodeRegion.bottom = mDecodeRegion.top + viewHeight; 187 } 188 }else if(top > 0){ 189 mBitmapTop = top; 190 mDecodeRegion.top = 0; 191 mDecodeRegion.bottom = (int) (viewHeight - top); 192 if (right < viewWith){ 193 mDecodeRegion.right = mBitmapWidth; 194 mDecodeRegion.left = (int) (mBitmapWidth - right); 195 }else{ 196 mDecodeRegion.left = (int) -left; 197 mDecodeRegion.right = mDecodeRegion.left + viewWith; 198 } 199 }else if(right < viewWith ){ 200 mDecodeRegion.right = mBitmapWidth; 201 mDecodeRegion.left = (int) (mBitmapWidth - right); 202 if(bottom < viewHeight){ 203 mDecodeRegion.bottom = mBitmapHeight; 204 mDecodeRegion.top = (int) (mBitmapHeight - bottom); 205 }else{ 206 mDecodeRegion.top = (int) -top; 207 mDecodeRegion.bottom = mDecodeRegion.top + viewHeight; 208 } 209 }else if(bottom < viewHeight){ 210 mDecodeRegion.left = (int) -left; 211 mDecodeRegion.right = mDecodeRegion.left + viewWith; 212 213 mDecodeRegion.bottom = mBitmapHeight; 214 mDecodeRegion.top = (int) (mBitmapHeight - bottom); 215 }else{ 216 mDecodeRegion.left = (int) -left; 217 mDecodeRegion.right = mDecodeRegion.left + viewWith; 218 mDecodeRegion.top = (int) -top; 219 mDecodeRegion.bottom = mDecodeRegion.top + viewHeight; 220 } 221 mOptions.inSampleSize = 1; 222 223 if (mDecodeRegion.width() > 0 && mDecodeRegion.height() > 0){ 224 mBitmap = mDecoder.decodeRegion(mDecodeRegion,mOptions); 225 }else{ 226 if (mBitmap != null && !mBitmap.isRecycled()){ 227 mBitmap.recycle(); 228 mBitmap = null; 229 } 230 } 231 232 } 233 BigDecimal bd = new BigDecimal(thumbnailWidth * 1.0f / mBitmapWidth * 100).setScale(1,BigDecimal.ROUND_HALF_UP); 234 mPercentage = bd.floatValue(); 235 Log.i(TAG, "onScale: bitmap.w = " + mThumbnailBitmap.getWidth() + " bitmap.h = " + mThumbnailBitmap.getHeight() 236 + " thumbnailWidth = " + thumbnailWidth + " thumbnailHeight = " + thumbnailHeight); 237 238 invalidate(); 239 return true; 240 } 241 242 @Override 243 public boolean onScaleBegin(ScaleGestureDetector detector) { 244 245 mScaling = true; 246 247 float focusX = detector.getFocusX(); 248 float focusY = detector.getFocusY(); 249 mFocus = new Point((int)focusX,(int)focusY); 250 251 return true; 252 } 253 254 @Override 255 public void onScaleEnd(ScaleGestureDetector detector) { 256 mScaling = false; 257 mFocus = null; 258 259 super.onScaleEnd(detector); 260 } 261 }; 262 263 private GestureDetectorCompat scrollDetector; 264 private GestureDetector.SimpleOnGestureListener simpleOnGestureListener = new GestureDetector.SimpleOnGestureListener() { 265 @Override 266 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 267 268 if (mScaling || Math.abs(distanceX) < 5 && Math.abs(distanceY) < 5) return true; 269 270 float matrix[] = new float[9]; 271 mMatrix.getValues(matrix); 272 mThumbnailLeft = matrix[2]; 273 mThumbnailTop = matrix[5]; 274 int viewWith = getWidth(); 275 int viewHeight = getHeight(); 276 277 if (mThumbnailLeft <= viewWith && mThumbnailLeft >= -thumbnailWidth 278 && mThumbnailTop <= viewHeight && mThumbnailTop >= -thumbnailHeight){ 279 if (mThumbnailLeft > 0 && viewWith - mThumbnailLeft < -distanceX){ 280 distanceX = -(viewWith - mThumbnailLeft); 281 }else if(mThumbnailLeft < 0 && mThumbnailLeft + thumbnailWidth < distanceX){ 282 distanceX = mThumbnailLeft + thumbnailWidth; 283 } 284 if (mThumbnailTop > 0 && viewHeight - mThumbnailTop < -distanceY){ 285 distanceY = -(viewHeight - mThumbnailTop); 286 }else if(mThumbnailTop < 0 && mThumbnailTop + thumbnailHeight < distanceY){ 287 distanceY = mThumbnailTop + thumbnailHeight; 288 } 289 290 mMatrix.postTranslate(-distanceX,-distanceY); 291 292 293 if (mPercentage == 100.0f){ 294 mMatrix.getValues(matrix); 295 mThumbnailLeft = matrix[2]; 296 mThumbnailTop = matrix[5]; 297 298 float left = mThumbnailLeft; 299 float top = mThumbnailTop; 300 float right = mThumbnailLeft + thumbnailWidth; 301 float bottom = mThumbnailTop + thumbnailHeight; 302 303 mBitmapLeft = 0; 304 mBitmapTop = 0; 305 306 if (left > 0){ 307 mBitmapLeft = left; 308 mDecodeRegion.left = 0; 309 mDecodeRegion.right = (int) (viewWith - left); 310 if (top > 0){ 311 mBitmapTop = top; 312 mDecodeRegion.top = 0; 313 mDecodeRegion.bottom = (int) (viewHeight - top); 314 }else if(bottom < viewHeight){ 315 mDecodeRegion.bottom = mBitmapHeight; 316 mDecodeRegion.top = (int) (mBitmapHeight - bottom); 317 }else { 318 mDecodeRegion.top = (int) -top; 319 mDecodeRegion.bottom = mDecodeRegion.top + viewHeight; 320 } 321 }else if(top > 0){ 322 mBitmapTop = top; 323 mDecodeRegion.top = 0; 324 mDecodeRegion.bottom = (int) (viewHeight - top); 325 if (right < viewWith){ 326 mDecodeRegion.right = mBitmapWidth; 327 mDecodeRegion.left = (int) (mBitmapWidth - right); 328 }else{ 329 mDecodeRegion.left = (int) -left; 330 mDecodeRegion.right = mDecodeRegion.left + viewWith; 331 } 332 }else if(right < viewWith ){ 333 mDecodeRegion.right = mBitmapWidth; 334 mDecodeRegion.left = (int) (mBitmapWidth - right); 335 if(bottom < viewHeight){ 336 mDecodeRegion.bottom = mBitmapHeight; 337 mDecodeRegion.top = (int) (mBitmapHeight - bottom); 338 }else{ 339 mDecodeRegion.top = (int) -top; 340 mDecodeRegion.bottom = mDecodeRegion.top + viewHeight; 341 } 342 }else if(bottom < viewHeight){ 343 mDecodeRegion.left = (int) -left; 344 mDecodeRegion.right = mDecodeRegion.left + viewWith; 345 346 mDecodeRegion.bottom = mBitmapHeight; 347 mDecodeRegion.top = (int) (mBitmapHeight - bottom); 348 }else{ 349 mDecodeRegion.left = (int) -left; 350 mDecodeRegion.right = mDecodeRegion.left + viewWith; 351 mDecodeRegion.top = (int) -top; 352 mDecodeRegion.bottom = mDecodeRegion.top + viewHeight; 353 } 354 mOptions.inSampleSize = 1; 355 356 Log.e(TAG, "onScroll: mDecodeRegion = " + mDecodeRegion ); 357 if (mDecodeRegion.width() > 0 && mDecodeRegion.height() > 0){ 358 mBitmap = mDecoder.decodeRegion(mDecodeRegion,mOptions); 359 }else{ 360 mBitmap = null; 361 } 362 } 363 invalidate(); 364 } 365 return true; 366 } 367 368 @Override 369 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 370 return true; 371 } 372 }; 373 private void initGestureDetector() { 374 scrollDetector = new GestureDetectorCompat(getContext(), simpleOnGestureListener); 375 scaleDetector = new ScaleGestureDetector(getContext(), scaleGestureListener); 376 } 377 378 @Override 379 public boolean onTouchEvent(MotionEvent event) { 380 boolean ret = scaleDetector.onTouchEvent(event); 381 ret |= scrollDetector.onTouchEvent(event); 382 return ret || true; 383 } 384 private static final int DISK_CACHE_SIZE = 1024 * 1024 * 256; // 256MB 385 private static final String DISK_CACHE_SUBDIR = "thumbnails"; 386 387 void initCache(){ 388 389 try { 390 Context context = getContext(); 391 File cacheDir = context.getCacheDir(); 392 393 cacheDir = context.getExternalCacheDir(); 394 395 final String cachePath = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||!isExternalStorageRemovable() 396 ? context.getExternalCacheDir().getPath() 397 : context.getCacheDir().getPath(); 398 cacheDir = new File(cachePath + File.separator + DISK_CACHE_SUBDIR); 399 400 PackageManager pm = context.getPackageManager(); 401 PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0); 402 403 int appVersion = pi.versionCode; 404 int valueCount = 1; 405 406 mDiskLruCache = DiskLruCache.open(cacheDir, appVersion,valueCount,DISK_CACHE_SIZE); 407 408 } catch (IOException e) { 409 e.printStackTrace(); 410 } catch (PackageManager.NameNotFoundException e) { 411 e.printStackTrace(); 412 } 413 414 } 415 void init(){ 416 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 417 mPaint.setStyle(Paint.Style.FILL); 418 mPaint.setTextSize(24); 419 setLayerType(View.LAYER_TYPE_SOFTWARE,mPaint); 420 421 // LinearGradient backGradient = new LinearGradient(0, 0, 0, mPaint.getTextSize(), new int[]{Color.BLACK, Color.GRAY ,Color.YELLOW}, null, Shader.TileMode.CLAMP); 422 // mPaint.setShader(backGradient); 423 424 mMatrix = new Matrix(); 425 426 // initCache(); 427 428 initGestureDetector(); 429 430 initRegionDecoder(); 431 432 } 433 void initRegionDecoder(){ 434 try { 435 InputStream fs = getResources().getAssets().open("world_map.jpg"); 436 mDecoder = BitmapRegionDecoder.newInstance(fs,false); 437 mBitmapWidth = mDecoder.getWidth(); 438 mBitmapHeight = mDecoder.getHeight(); 439 440 mOptions = new BitmapFactory.Options(); 441 mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888; 442 mOptions.inSampleSize = 1; 443 444 } catch (IOException e) { 445 446 e.printStackTrace(); 447 } 448 449 } 450 private void getAttrs(Context context, AttributeSet attrs) { 451 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LargeImageViewAttrs); 452 String file = ta.getString(R.styleable.LargeImageViewAttrs_src); 453 ta.recycle(); 454 } 455 456 457 public LargeImageView(Context context) { 458 super(context); 459 init(); 460 } 461 462 public LargeImageView(Context context, @Nullable AttributeSet attrs) { 463 super(context, attrs); 464 getAttrs(context,attrs); 465 init(); 466 } 467 468 public LargeImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 469 super(context, attrs, defStyleAttr); 470 getAttrs(context,attrs); 471 init(); 472 } 473 474 @SuppressLint("NewApi") 475 public LargeImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 476 super(context, attrs, defStyleAttr, defStyleRes); 477 getAttrs(context,attrs); 478 init(); 479 } 480 481 @Override 482 protected void onRestoreInstanceState(Parcelable state) { 483 super.onRestoreInstanceState(state); 484 } 485 486 @Override 487 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 488 super.onSizeChanged(w, h, oldw, oldh); 489 } 490 491 public static int calculateInSampleSize(int width, int height, int reqWidth, int reqHeight) { 492 // Raw height and width of image 493 int inSampleSize = 1; 494 495 if (height > reqHeight || width > reqWidth) { 496 497 final int halfHeight = height / 2; 498 final int halfWidth = width / 2; 499 500 if (halfHeight <= 0 || halfWidth <= 0) return inSampleSize; 501 502 if (width > height) { 503 // Calculate the largest inSampleSize value that is a power of 2 and keeps both 504 // height and width larger than the requested height and width. 505 while ((halfWidth / inSampleSize) >= reqWidth) { 506 inSampleSize *= 2; 507 } 508 } else { 509 while ((halfHeight / inSampleSize) >= reqHeight) { 510 inSampleSize *= 2; 511 } 512 } 513 } 514 return inSampleSize; 515 } 516 517 @Override 518 protected void onDraw(Canvas canvas) { 519 520 if (mPercentage < 100.0f && mThumbnailBitmap != null){ 521 canvas.drawBitmap(mThumbnailBitmap,mMatrix,mPaint); 522 }else if(mPercentage == 100.0f && mBitmap != null){ 523 canvas.drawBitmap(mBitmap,mBitmapLeft,mBitmapTop,mPaint); 524 } 525 if (mFocus != null){ 526 mPaint.setColor(Color.RED); 527 mPaint.setMaskFilter(new BlurMaskFilter(16, BlurMaskFilter.Blur.SOLID)); 528 mPaint.setColor(Color.parseColor("#ff0000")); 529 canvas.drawCircle(mFocus.x,mFocus.y,16,mPaint); 530 } 531 532 //draw percentage 533 String percentage = mPercentage + "%"; 534 535 float textSize = mPaint.getTextSize(); 536 float percentWidth = mPaint.measureText("100.0%"); 537 538 float circleX = getWidth() - percentWidth ; 539 float circleY = percentWidth ; 540 541 mPaint.setColor(Color.parseColor("#7f7A378B")); 542 mPaint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL)); 543 canvas.drawCircle(circleX ,circleY,percentWidth * 0.66f,mPaint); 544 545 mPaint.setColor(Color.WHITE); 546 mPaint.setMaskFilter(null); 547 percentWidth = mPaint.measureText(percentage); 548 circleY += textSize / 2; 549 circleX -= percentWidth / 2; 550 canvas.drawText(percentage,circleX ,circleY,mPaint); 551 552 } 553 @Override 554 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 555 556 Log.e(TAG, "onMeasure: " ); 557 558 int viewWidth = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth(); 559 560 if (viewWidth == 0) viewWidth = 56; 561 562 viewWidth = resolveSizeAndState(viewWidth, widthMeasureSpec, 0); 563 564 int viewHeight = getSuggestedMinimumHeight() + getPaddingBottom() + getPaddingTop(); 565 566 if (viewHeight == 0) viewHeight = 56; 567 568 //计算最佳值,在其中解析了 heightMeasureSpec 569 viewHeight = resolveSizeAndState(viewHeight, heightMeasureSpec, 0); 570 571 //将量算的结果保存到View的成员变量mMeasuredWidth 和mMeasuredHeight中。 572 setMeasuredDimension(viewWidth, viewHeight); 573 574 // 量算完成之后,View的父控件就可以通过调用 575 // getMeasuredWidth、getMeasuredState、getMeasuredWidthAndState 576 // 这三个方法获取View的量算结果。 577 578 579 mBitmapLeft = 0; 580 mBitmapTop = 0; 581 582 int left = mBitmapWidth / 2 - viewWidth / 2; 583 int top = mBitmapHeight / 2 - viewHeight / 2; 584 585 mDecodeRegion = new Rect(left,top,left + viewWidth ,top + viewHeight ); 586 // mDecodeRegion = new Rect(0,0,mBitmapWidth ,mBitmapHeight); 587 588 mOptions.inSampleSize = calculateInSampleSize(mDecodeRegion.width(),mDecodeRegion.height(),viewWidth,viewHeight); 589 // mOptions.inSampleSize = 2; 590 591 long begin,end; 592 begin = SystemClock.elapsedRealtime(); 593 mBitmap = mDecoder.decodeRegion(mDecodeRegion,mOptions); 594 end = SystemClock.elapsedRealtime(); 595 596 thumbnailWidth = mBitmapWidth; 597 thumbnailHeight = mBitmapHeight; 598 599 BigDecimal bd = new BigDecimal(thumbnailWidth * 1.0f / mBitmapWidth * 100).setScale(1,BigDecimal.ROUND_HALF_UP); 600 mPercentage = bd.floatValue(); 601 602 Log.e(TAG, "init region = " + mDecodeRegion + " ratio = " + (float) mDecodeRegion.width() / mDecodeRegion.height() 603 + " bitmap.w = " + mBitmap.getWidth() + " bitmap.h = " + mBitmap.getHeight() + " ratio = " + (float) mBitmap.getWidth() / mBitmap.getHeight() 604 + " need " + (end - begin) + " ms"); 605 if (mThumbnailBitmap != null){ 606 Log.e(TAG, "onMeasure : mThumbnailBitmap is ok."); 607 608 float width = mThumbnailBitmap.getWidth(); 609 float height = mThumbnailBitmap.getHeight(); 610 float dx = viewWidth / 2 - width / 2; 611 float dy = viewHeight / 2 - height / 2; 612 mMatrix.setTranslate(dx,dy); 613 614 float factor = mBitmapWidth / width; 615 mMatrix.postScale(factor,factor,viewWidth/ 2,viewHeight /2); 616 617 } 618 } 619 620 @Override 621 protected void onFinishInflate() { 622 super.onFinishInflate(); 623 Log.e(TAG, "onFinishInflate: " ); 624 createThumbnailBitmap(); 625 } 626 627 @Override 628 protected void onAttachedToWindow() { 629 super.onAttachedToWindow(); 630 Log.e(TAG, "onAttachedToWindow: " ); 631 } 632 @Override 633 protected void onDetachedFromWindow() { 634 super.onDetachedFromWindow(); 635 Log.e(TAG, "onDetachedFromWindow: " ); 636 637 } 638 639 @Override 640 protected void onWindowVisibilityChanged(int visibility) { 641 super.onWindowVisibilityChanged(visibility); 642 Log.e(TAG, "onWindowVisibilityChanged : " + visibility); 643 644 } 645 @Override 646 public void onWindowFocusChanged(boolean hasWindowFocus) { 647 super.onWindowFocusChanged(hasWindowFocus); 648 Log.e(TAG, "onWindowFocusChanged: hasWindowFocus = " + hasWindowFocus ); 649 } 650 651 Bitmap loadBitmapFromCache(String key){ 652 Bitmap bitmap = null; 653 try { 654 DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key); 655 if (snapshot != null) { 656 InputStream is = snapshot.getInputStream(0); 657 bitmap = BitmapFactory.decodeStream(is); 658 } 659 } catch (IOException e) { 660 e.printStackTrace(); 661 } 662 return bitmap; 663 } 664 void pushToCache(String key, Bitmap value){ 665 try { 666 DiskLruCache.Editor editor = mDiskLruCache.edit(key); 667 668 if (editor != null){ 669 OutputStream out = editor.newOutputStream(0); 670 value.compress(Bitmap.CompressFormat.JPEG, 100, out); 671 out.flush(); 672 editor.commit(); 673 mDiskLruCache.flush(); 674 } 675 676 } catch (IOException e) { 677 e.printStackTrace(); 678 } 679 } 680 }