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