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