ImageView.java revision 076357b8567458d4b6dfdcf839ef751634cd2bfb
1/* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.widget; 18 19import android.content.Context; 20import android.content.res.Resources; 21import android.content.res.TypedArray; 22import android.graphics.Bitmap; 23import android.graphics.Canvas; 24import android.graphics.ColorFilter; 25import android.graphics.Matrix; 26import android.graphics.PorterDuff; 27import android.graphics.PorterDuffColorFilter; 28import android.graphics.RectF; 29import android.graphics.drawable.BitmapDrawable; 30import android.graphics.drawable.Drawable; 31import android.net.Uri; 32import android.util.AttributeSet; 33import android.util.Log; 34import android.view.View; 35import android.widget.RemoteViews.RemoteView; 36 37 38/** 39 * Displays an arbitrary image, such as an icon. The ImageView class 40 * can load images from various sources (such as resources or content 41 * providers), takes care of computing its measurement from the image so that 42 * it can be used in any layout manager, and provides various display options 43 * such as scaling and tinting. 44 * 45 * @attr ref android.R.styleable#ImageView_adjustViewBounds 46 * @attr ref android.R.styleable#ImageView_src 47 * @attr ref android.R.styleable#ImageView_maxWidth 48 * @attr ref android.R.styleable#ImageView_maxHeight 49 * @attr ref android.R.styleable#ImageView_tint 50 * @attr ref android.R.styleable#ImageView_scaleType 51 * @attr ref android.R.styleable#ImageView_cropToPadding 52 */ 53@RemoteView 54public class ImageView extends View { 55 // settable by the client 56 private Uri mUri; 57 private int mResource = 0; 58 private Matrix mMatrix; 59 private ScaleType mScaleType; 60 private boolean mHaveFrame = false; 61 private boolean mAdjustViewBounds = false; 62 private int mMaxWidth = Integer.MAX_VALUE; 63 private int mMaxHeight = Integer.MAX_VALUE; 64 65 // these are applied to the drawable 66 private ColorFilter mColorFilter; 67 private int mAlpha = 255; 68 private int mViewAlphaScale = 256; 69 70 private Drawable mDrawable = null; 71 private int[] mState = null; 72 private boolean mMergeState = false; 73 private int mLevel = 0; 74 private int mDrawableWidth; 75 private int mDrawableHeight; 76 private Matrix mDrawMatrix = null; 77 78 // Avoid allocations... 79 private RectF mTempSrc = new RectF(); 80 private RectF mTempDst = new RectF(); 81 82 private boolean mCropToPadding; 83 84 private boolean mBaselineAligned = false; 85 86 private static final ScaleType[] sScaleTypeArray = { 87 ScaleType.MATRIX, 88 ScaleType.FIT_XY, 89 ScaleType.FIT_START, 90 ScaleType.FIT_CENTER, 91 ScaleType.FIT_END, 92 ScaleType.CENTER, 93 ScaleType.CENTER_CROP, 94 ScaleType.CENTER_INSIDE 95 }; 96 97 public ImageView(Context context) { 98 super(context); 99 initImageView(); 100 } 101 102 public ImageView(Context context, AttributeSet attrs) { 103 this(context, attrs, 0); 104 } 105 106 public ImageView(Context context, AttributeSet attrs, int defStyle) { 107 super(context, attrs, defStyle); 108 initImageView(); 109 110 TypedArray a = context.obtainStyledAttributes(attrs, 111 com.android.internal.R.styleable.ImageView, defStyle, 0); 112 113 Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src); 114 if (d != null) { 115 setImageDrawable(d); 116 } 117 118 mBaselineAligned = a.getBoolean( 119 com.android.internal.R.styleable.ImageView_baselineAlignBottom, false); 120 121 setAdjustViewBounds( 122 a.getBoolean(com.android.internal.R.styleable.ImageView_adjustViewBounds, 123 false)); 124 125 setMaxWidth(a.getDimensionPixelSize( 126 com.android.internal.R.styleable.ImageView_maxWidth, Integer.MAX_VALUE)); 127 128 setMaxHeight(a.getDimensionPixelSize( 129 com.android.internal.R.styleable.ImageView_maxHeight, Integer.MAX_VALUE)); 130 131 int index = a.getInt(com.android.internal.R.styleable.ImageView_scaleType, -1); 132 if (index >= 0) { 133 setScaleType(sScaleTypeArray[index]); 134 } 135 136 int tint = a.getInt(com.android.internal.R.styleable.ImageView_tint, 0); 137 if (tint != 0) { 138 setColorFilter(tint, PorterDuff.Mode.SRC_ATOP); 139 } 140 141 mCropToPadding = a.getBoolean( 142 com.android.internal.R.styleable.ImageView_cropToPadding, false); 143 144 a.recycle(); 145 146 //need inflate syntax/reader for matrix 147 } 148 149 private void initImageView() { 150 mMatrix = new Matrix(); 151 mScaleType = ScaleType.FIT_CENTER; 152 } 153 154 @Override 155 protected boolean verifyDrawable(Drawable dr) { 156 return mDrawable == dr || super.verifyDrawable(dr); 157 } 158 159 @Override 160 public void invalidateDrawable(Drawable dr) { 161 if (dr == mDrawable) { 162 /* we invalidate the whole view in this case because it's very 163 * hard to know where the drawable actually is. This is made 164 * complicated because of the offsets and transformations that 165 * can be applied. In theory we could get the drawable's bounds 166 * and run them through the transformation and offsets, but this 167 * is probably not worth the effort. 168 */ 169 invalidate(); 170 } else { 171 super.invalidateDrawable(dr); 172 } 173 } 174 175 @Override 176 protected boolean onSetAlpha(int alpha) { 177 if (getBackground() == null) { 178 int scale = alpha + (alpha >> 7); 179 if (mViewAlphaScale != scale) { 180 mViewAlphaScale = scale; 181 applyColorMod(); 182 } 183 return true; 184 } 185 return false; 186 } 187 188 /** 189 * Set this to true if you want the ImageView to adjust its bounds 190 * to preserve the aspect ratio of its drawable. 191 * @param adjustViewBounds Whether to adjust the bounds of this view 192 * to presrve the original aspect ratio of the drawable 193 * 194 * @attr ref android.R.styleable#ImageView_adjustViewBounds 195 */ 196 @android.view.RemotableViewMethod 197 public void setAdjustViewBounds(boolean adjustViewBounds) { 198 mAdjustViewBounds = adjustViewBounds; 199 if (adjustViewBounds) { 200 setScaleType(ScaleType.FIT_CENTER); 201 } 202 } 203 204 /** 205 * An optional argument to supply a maximum width for this view. Only valid if 206 * {@link #setAdjustViewBounds} has been set to true. To set an image to be a maximum of 100 x 207 * 100 while preserving the original aspect ratio, do the following: 1) set adjustViewBounds to 208 * true 2) set maxWidth and maxHeight to 100 3) set the height and width layout params to 209 * WRAP_CONTENT. 210 * 211 * <p> 212 * Note that this view could be still smaller than 100 x 100 using this approach if the original 213 * image is small. To set an image to a fixed size, specify that size in the layout params and 214 * then use {@link #setScaleType} to determine how to fit the image within the bounds. 215 * </p> 216 * 217 * @param maxWidth maximum width for this view 218 * 219 * @attr ref android.R.styleable#ImageView_maxWidth 220 */ 221 @android.view.RemotableViewMethod 222 public void setMaxWidth(int maxWidth) { 223 mMaxWidth = maxWidth; 224 } 225 226 /** 227 * An optional argument to supply a maximum height for this view. Only valid if 228 * {@link #setAdjustViewBounds} has been set to true. To set an image to be a maximum of 100 x 229 * 100 while preserving the original aspect ratio, do the following: 1) set adjustViewBounds to 230 * true 2) set maxWidth and maxHeight to 100 3) set the height and width layout params to 231 * WRAP_CONTENT. 232 * 233 * <p> 234 * Note that this view could be still smaller than 100 x 100 using this approach if the original 235 * image is small. To set an image to a fixed size, specify that size in the layout params and 236 * then use {@link #setScaleType} to determine how to fit the image within the bounds. 237 * </p> 238 * 239 * @param maxHeight maximum height for this view 240 * 241 * @attr ref android.R.styleable#ImageView_maxHeight 242 */ 243 @android.view.RemotableViewMethod 244 public void setMaxHeight(int maxHeight) { 245 mMaxHeight = maxHeight; 246 } 247 248 /** Return the view's drawable, or null if no drawable has been 249 assigned. 250 */ 251 public Drawable getDrawable() { 252 return mDrawable; 253 } 254 255 /** 256 * Sets a drawable as the content of this ImageView. 257 * 258 * @param resId the resource identifier of the the drawable 259 * 260 * @attr ref android.R.styleable#ImageView_src 261 */ 262 @android.view.RemotableViewMethod 263 public void setImageResource(int resId) { 264 if (mUri != null || mResource != resId) { 265 updateDrawable(null); 266 mResource = resId; 267 mUri = null; 268 resolveUri(); 269 requestLayout(); 270 invalidate(); 271 } 272 } 273 274 /** 275 * Sets the content of this ImageView to the specified Uri. 276 * 277 * @param uri The Uri of an image 278 */ 279 @android.view.RemotableViewMethod 280 public void setImageURI(Uri uri) { 281 if (mResource != 0 || 282 (mUri != uri && 283 (uri == null || mUri == null || !uri.equals(mUri)))) { 284 updateDrawable(null); 285 mResource = 0; 286 mUri = uri; 287 resolveUri(); 288 requestLayout(); 289 invalidate(); 290 } 291 } 292 293 294 /** 295 * Sets a drawable as the content of this ImageView. 296 * 297 * @param drawable The drawable to set 298 */ 299 public void setImageDrawable(Drawable drawable) { 300 if (mDrawable != drawable) { 301 mResource = 0; 302 mUri = null; 303 updateDrawable(drawable); 304 requestLayout(); 305 invalidate(); 306 } 307 } 308 309 /** 310 * Sets a Bitmap as the content of this ImageView. 311 * 312 * @param bm The bitmap to set 313 */ 314 @android.view.RemotableViewMethod 315 public void setImageBitmap(Bitmap bm) { 316 // if this is used frequently, may handle bitmaps explicitly 317 // to reduce the intermediate drawable object 318 setImageDrawable(new BitmapDrawable(bm)); 319 } 320 321 public void setImageState(int[] state, boolean merge) { 322 mState = state; 323 mMergeState = merge; 324 if (mDrawable != null) { 325 refreshDrawableState(); 326 resizeFromDrawable(); 327 } 328 } 329 330 @Override 331 public void setSelected(boolean selected) { 332 super.setSelected(selected); 333 resizeFromDrawable(); 334 } 335 336 @android.view.RemotableViewMethod 337 public void setImageLevel(int level) { 338 mLevel = level; 339 if (mDrawable != null) { 340 mDrawable.setLevel(level); 341 resizeFromDrawable(); 342 } 343 } 344 345 /** 346 * Options for scaling the bounds of an image to the bounds of this view. 347 */ 348 public enum ScaleType { 349 /** 350 * Scale using the image matrix when drawing. The image matrix can be set using 351 * {@link ImageView#setImageMatrix(Matrix)}. From XML, use this syntax: 352 * <code>android:scaleType="matrix"</code>. 353 */ 354 MATRIX (0), 355 /** 356 * Scale the image using {@link Matrix.ScaleToFit#FILL}. 357 * From XML, use this syntax: <code>android:scaleType="fitXY"</code>. 358 */ 359 FIT_XY (1), 360 /** 361 * Scale the image using {@link Matrix.ScaleToFit#START}. 362 * From XML, use this syntax: <code>android:scaleType="fitStart"</code>. 363 */ 364 FIT_START (2), 365 /** 366 * Scale the image using {@link Matrix.ScaleToFit#CENTER}. 367 * From XML, use this syntax: 368 * <code>android:scaleType="fitCenter"</code>. 369 */ 370 FIT_CENTER (3), 371 /** 372 * Scale the image using {@link Matrix.ScaleToFit#END}. 373 * From XML, use this syntax: <code>android:scaleType="fitEnd"</code>. 374 */ 375 FIT_END (4), 376 /** 377 * Center the image in the view, but perform no scaling. 378 * From XML, use this syntax: <code>android:scaleType="center"</code>. 379 */ 380 CENTER (5), 381 /** 382 * Scale the image uniformly (maintain the image's aspect ratio) so 383 * that both dimensions (width and height) of the image will be equal 384 * to or larger than the corresponding dimension of the view 385 * (minus padding). The image is then centered in the view. 386 * From XML, use this syntax: <code>android:scaleType="centerCrop"</code>. 387 */ 388 CENTER_CROP (6), 389 /** 390 * Scale the image uniformly (maintain the image's aspect ratio) so 391 * that both dimensions (width and height) of the image will be equal 392 * to or less than the corresponding dimension of the view 393 * (minus padding). The image is then centered in the view. 394 * From XML, use this syntax: <code>android:scaleType="centerInside"</code>. 395 */ 396 CENTER_INSIDE (7); 397 398 ScaleType(int ni) { 399 nativeInt = ni; 400 } 401 final int nativeInt; 402 } 403 404 /** 405 * Controls how the image should be resized or moved to match the size 406 * of this ImageView. 407 * 408 * @param scaleType The desired scaling mode. 409 * 410 * @attr ref android.R.styleable#ImageView_scaleType 411 */ 412 public void setScaleType(ScaleType scaleType) { 413 if (scaleType == null) { 414 throw new NullPointerException(); 415 } 416 417 if (mScaleType != scaleType) { 418 mScaleType = scaleType; 419 420 setWillNotCacheDrawing(mScaleType == ScaleType.CENTER); 421 422 requestLayout(); 423 invalidate(); 424 } 425 } 426 427 /** 428 * Return the current scale type in use by this ImageView. 429 * 430 * @see ImageView.ScaleType 431 * 432 * @attr ref android.R.styleable#ImageView_scaleType 433 */ 434 public ScaleType getScaleType() { 435 return mScaleType; 436 } 437 438 /** Return the view's optional matrix. This is applied to the 439 view's drawable when it is drawn. If there is not matrix, 440 this method will return null. 441 Do not change this matrix in place. If you want a different matrix 442 applied to the drawable, be sure to call setImageMatrix(). 443 */ 444 public Matrix getImageMatrix() { 445 return mMatrix; 446 } 447 448 public void setImageMatrix(Matrix matrix) { 449 // collaps null and identity to just null 450 if (matrix != null && matrix.isIdentity()) { 451 matrix = null; 452 } 453 454 // don't invalidate unless we're actually changing our matrix 455 if (matrix == null && !mMatrix.isIdentity() || 456 matrix != null && !mMatrix.equals(matrix)) { 457 mMatrix.set(matrix); 458 invalidate(); 459 } 460 } 461 462 private void resolveUri() { 463 if (mDrawable != null) { 464 return; 465 } 466 467 Resources rsrc = getResources(); 468 if (rsrc == null) { 469 return; 470 } 471 472 Drawable d = null; 473 474 if (mResource != 0) { 475 try { 476 d = rsrc.getDrawable(mResource); 477 } catch (Exception e) { 478 Log.w("ImageView", "Unable to find resource: " + mResource, e); 479 // Don't try again. 480 mUri = null; 481 } 482 } else if (mUri != null) { 483 if ("content".equals(mUri.getScheme())) { 484 try { 485 d = Drawable.createFromStream( 486 mContext.getContentResolver().openInputStream(mUri), 487 null); 488 } catch (Exception e) { 489 Log.w("ImageView", "Unable to open content: " + mUri, e); 490 } 491 } else { 492 d = Drawable.createFromPath(mUri.toString()); 493 } 494 495 if (d == null) { 496 System.out.println("resolveUri failed on bad bitmap uri: " 497 + mUri); 498 // Don't try again. 499 mUri = null; 500 } 501 } else { 502 return; 503 } 504 505 updateDrawable(d); 506 } 507 508 @Override 509 public int[] onCreateDrawableState(int extraSpace) { 510 if (mState == null) { 511 return super.onCreateDrawableState(extraSpace); 512 } else if (!mMergeState) { 513 return mState; 514 } else { 515 return mergeDrawableStates( 516 super.onCreateDrawableState(extraSpace + mState.length), mState); 517 } 518 } 519 520 private void updateDrawable(Drawable d) { 521 if (mDrawable != null) { 522 mDrawable.setCallback(null); 523 unscheduleDrawable(mDrawable); 524 } 525 mDrawable = d; 526 if (d != null) { 527 d.setCallback(this); 528 if (d.isStateful()) { 529 d.setState(getDrawableState()); 530 } 531 d.setLevel(mLevel); 532 mDrawableWidth = d.getIntrinsicWidth(); 533 mDrawableHeight = d.getIntrinsicHeight(); 534 applyColorMod(); 535 configureBounds(); 536 } 537 } 538 539 private void resizeFromDrawable() { 540 Drawable d = mDrawable; 541 if (d != null) { 542 int w = d.getIntrinsicWidth(); 543 if (w < 0) w = mDrawableWidth; 544 int h = d.getIntrinsicHeight(); 545 if (h < 0) h = mDrawableHeight; 546 if (w != mDrawableWidth || h != mDrawableHeight) { 547 mDrawableWidth = w; 548 mDrawableHeight = h; 549 requestLayout(); 550 } 551 } 552 } 553 554 private static final Matrix.ScaleToFit[] sS2FArray = { 555 Matrix.ScaleToFit.FILL, 556 Matrix.ScaleToFit.START, 557 Matrix.ScaleToFit.CENTER, 558 Matrix.ScaleToFit.END 559 }; 560 561 private static Matrix.ScaleToFit scaleTypeToScaleToFit(ScaleType st) { 562 // ScaleToFit enum to their corresponding Matrix.ScaleToFit values 563 return sS2FArray[st.nativeInt - 1]; 564 } 565 566 @Override 567 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 568 resolveUri(); 569 int w; 570 int h; 571 572 // Desired aspect ratio of the view's contents (not including padding) 573 float desiredAspect = 0.0f; 574 575 // We are allowed to change the view's width 576 boolean resizeWidth = false; 577 578 // We are allowed to change the view's height 579 boolean resizeHeight = false; 580 581 if (mDrawable == null) { 582 // If no drawable, its intrinsic size is 0. 583 mDrawableWidth = -1; 584 mDrawableHeight = -1; 585 w = h = 0; 586 } else { 587 w = mDrawableWidth; 588 h = mDrawableHeight; 589 if (w <= 0) w = 1; 590 if (h <= 0) h = 1; 591 592 // We are supposed to adjust view bounds to match the aspect 593 // ratio of our drawable. See if that is possible. 594 if (mAdjustViewBounds) { 595 596 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 597 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 598 599 resizeWidth = widthSpecMode != MeasureSpec.EXACTLY; 600 resizeHeight = heightSpecMode != MeasureSpec.EXACTLY; 601 602 desiredAspect = (float)w/(float)h; 603 } 604 } 605 606 int pleft = mPaddingLeft; 607 int pright = mPaddingRight; 608 int ptop = mPaddingTop; 609 int pbottom = mPaddingBottom; 610 611 int widthSize; 612 int heightSize; 613 614 if (resizeWidth || resizeHeight) { 615 /* If we get here, it means we want to resize to match the 616 drawables aspect ratio, and we have the freedom to change at 617 least one dimension. 618 */ 619 620 // Get the max possible width given our constraints 621 widthSize = resolveAdjustedSize(w + pleft + pright, 622 mMaxWidth, widthMeasureSpec); 623 624 // Get the max possible height given our constraints 625 heightSize = resolveAdjustedSize(h + ptop + pbottom, 626 mMaxHeight, heightMeasureSpec); 627 628 if (desiredAspect != 0.0f) { 629 // See what our actual aspect ratio is 630 float actualAspect = (float)(widthSize - pleft - pright) / 631 (heightSize - ptop - pbottom); 632 633 if (Math.abs(actualAspect - desiredAspect) > 0.0000001) { 634 635 boolean done = false; 636 637 // Try adjusting width to be proportional to height 638 if (resizeWidth) { 639 int newWidth = (int)(desiredAspect * 640 (heightSize - ptop - pbottom)) 641 + pleft + pright; 642 if (newWidth <= widthSize) { 643 widthSize = newWidth; 644 done = true; 645 } 646 } 647 648 // Try adjusting height to be proportional to width 649 if (!done && resizeHeight) { 650 int newHeight = (int)((widthSize - pleft - pright) 651 / desiredAspect) + ptop + pbottom; 652 if (newHeight <= heightSize) { 653 heightSize = newHeight; 654 } 655 } 656 } 657 } 658 } else { 659 /* We are either don't want to preserve the drawables aspect ratio, 660 or we are not allowed to change view dimensions. Just measure in 661 the normal way. 662 */ 663 w += pleft + pright; 664 h += ptop + pbottom; 665 666 w = Math.max(w, getSuggestedMinimumWidth()); 667 h = Math.max(h, getSuggestedMinimumHeight()); 668 669 widthSize = resolveSize(w, widthMeasureSpec); 670 heightSize = resolveSize(h, heightMeasureSpec); 671 } 672 673 setMeasuredDimension(widthSize, heightSize); 674 } 675 676 private int resolveAdjustedSize(int desiredSize, int maxSize, 677 int measureSpec) { 678 int result = desiredSize; 679 int specMode = MeasureSpec.getMode(measureSpec); 680 int specSize = MeasureSpec.getSize(measureSpec); 681 switch (specMode) { 682 case MeasureSpec.UNSPECIFIED: 683 /* Parent says we can be as big as we want. Just don't be larger 684 than max size imposed on ourselves. 685 */ 686 result = Math.min(desiredSize, maxSize); 687 break; 688 case MeasureSpec.AT_MOST: 689 // Parent says we can be as big as we want, up to specSize. 690 // Don't be larger than specSize, and don't be larger than 691 // the max size imposed on ourselves. 692 result = Math.min(Math.min(desiredSize, specSize), maxSize); 693 break; 694 case MeasureSpec.EXACTLY: 695 // No choice. Do what we are told. 696 result = specSize; 697 break; 698 } 699 return result; 700 } 701 702 @Override 703 protected boolean setFrame(int l, int t, int r, int b) { 704 boolean changed = super.setFrame(l, t, r, b); 705 mHaveFrame = true; 706 configureBounds(); 707 return changed; 708 } 709 710 private void configureBounds() { 711 if (mDrawable == null || !mHaveFrame) { 712 return; 713 } 714 715 int dwidth = mDrawableWidth; 716 int dheight = mDrawableHeight; 717 718 int vwidth = getWidth() - mPaddingLeft - mPaddingRight; 719 int vheight = getHeight() - mPaddingTop - mPaddingBottom; 720 721 boolean fits = (dwidth < 0 || vwidth == dwidth) && 722 (dheight < 0 || vheight == dheight); 723 724 if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) { 725 /* If the drawable has no intrinsic size, or we're told to 726 scaletofit, then we just fill our entire view. 727 */ 728 mDrawable.setBounds(0, 0, vwidth, vheight); 729 mDrawMatrix = null; 730 } else { 731 // We need to do the scaling ourself, so have the drawable 732 // use its native size. 733 mDrawable.setBounds(0, 0, dwidth, dheight); 734 735 if (ScaleType.MATRIX == mScaleType) { 736 // Use the specified matrix as-is. 737 if (mMatrix.isIdentity()) { 738 mDrawMatrix = null; 739 } else { 740 mDrawMatrix = mMatrix; 741 } 742 } else if (fits) { 743 // The bitmap fits exactly, no transform needed. 744 mDrawMatrix = null; 745 } else if (ScaleType.CENTER == mScaleType) { 746 // Center bitmap in view, no scaling. 747 mDrawMatrix = mMatrix; 748 mDrawMatrix.setTranslate((vwidth - dwidth) * 0.5f, 749 (vheight - dheight) * 0.5f); 750 } else if (ScaleType.CENTER_CROP == mScaleType) { 751 mDrawMatrix = mMatrix; 752 753 float scale; 754 float dx = 0, dy = 0; 755 756 if (dwidth * vheight > vwidth * dheight) { 757 scale = (float) vheight / (float) dheight; 758 dx = (vwidth - dwidth * scale) * 0.5f; 759 } else { 760 scale = (float) vwidth / (float) dwidth; 761 dy = (vheight - dheight * scale) * 0.5f; 762 } 763 764 mDrawMatrix.setScale(scale, scale); 765 mDrawMatrix.postTranslate(dx, dy); 766 } else if (ScaleType.CENTER_INSIDE == mScaleType) { 767 mDrawMatrix = mMatrix; 768 float scale; 769 float dx; 770 float dy; 771 772 if (dwidth <= vwidth && dheight <= vheight) { 773 scale = 1.0f; 774 } else { 775 scale = Math.min((float) vwidth / (float) dwidth, 776 (float) vheight / (float) dheight); 777 } 778 779 dx = (vwidth - dwidth * scale) * 0.5f; 780 dy = (vheight - dheight * scale) * 0.5f; 781 782 mDrawMatrix.setScale(scale, scale); 783 mDrawMatrix.postTranslate(dx, dy); 784 } else { 785 // Generate the required transform. 786 mTempSrc.set(0, 0, dwidth, dheight); 787 mTempDst.set(0, 0, vwidth, vheight); 788 789 mDrawMatrix = mMatrix; 790 mDrawMatrix.setRectToRect(mTempSrc, mTempDst, 791 scaleTypeToScaleToFit(mScaleType)); 792 } 793 } 794 } 795 796 @Override 797 protected void drawableStateChanged() { 798 super.drawableStateChanged(); 799 Drawable d = mDrawable; 800 if (d != null && d.isStateful()) { 801 d.setState(getDrawableState()); 802 } 803 } 804 805 @Override 806 protected void onDraw(Canvas canvas) { 807 super.onDraw(canvas); 808 809 if (mDrawable == null) { 810 return; // couldn't resolve the URI 811 } 812 813 if (mDrawableWidth == 0 || mDrawableHeight == 0) { 814 return; // nothing to draw (empty bounds) 815 } 816 817 if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) { 818 mDrawable.draw(canvas); 819 } else { 820 int saveCount = canvas.getSaveCount(); 821 canvas.save(); 822 823 if (mCropToPadding) { 824 final int scrollX = mScrollX; 825 final int scrollY = mScrollY; 826 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop, 827 scrollX + mRight - mLeft - mPaddingRight, 828 scrollY + mBottom - mTop - mPaddingBottom); 829 } 830 831 canvas.translate(mPaddingLeft, mPaddingTop); 832 833 if (mDrawMatrix != null) { 834 canvas.concat(mDrawMatrix); 835 } 836 mDrawable.draw(canvas); 837 canvas.restoreToCount(saveCount); 838 } 839 } 840 841 @Override 842 public int getBaseline() { 843 return mBaselineAligned ? getHeight() : -1; 844 } 845 846 /** 847 * Set a tinting option for the image. 848 * 849 * @param color Color tint to apply. 850 * @param mode How to apply the color. The standard mode is 851 * {@link PorterDuff.Mode#SRC_ATOP} 852 * 853 * @attr ref android.R.styleable#ImageView_tint 854 */ 855 public final void setColorFilter(int color, PorterDuff.Mode mode) { 856 setColorFilter(new PorterDuffColorFilter(color, mode)); 857 } 858 859 public final void clearColorFilter() { 860 setColorFilter(null); 861 } 862 863 /** 864 * Apply an arbitrary colorfilter to the image. 865 * 866 * @param cf the colorfilter to apply (may be null) 867 */ 868 public void setColorFilter(ColorFilter cf) { 869 if (mColorFilter != cf) { 870 mColorFilter = cf; 871 applyColorMod(); 872 invalidate(); 873 } 874 } 875 876 public void setAlpha(int alpha) { 877 alpha &= 0xFF; // keep it legal 878 if (mAlpha != alpha) { 879 mAlpha = alpha; 880 applyColorMod(); 881 invalidate(); 882 } 883 } 884 885 private void applyColorMod() { 886 if (mDrawable != null) { 887 mDrawable.setColorFilter(mColorFilter); 888 mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8); 889 } 890 } 891} 892