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 17 package android.widget; 18 19 import android.annotation.DrawableRes; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.content.res.ColorStateList; 25 import android.content.res.Resources; 26 import android.content.res.TypedArray; 27 import android.graphics.Bitmap; 28 import android.graphics.Canvas; 29 import android.graphics.ColorFilter; 30 import android.graphics.Matrix; 31 import android.graphics.PixelFormat; 32 import android.graphics.PorterDuff; 33 import android.graphics.PorterDuffColorFilter; 34 import android.graphics.Rect; 35 import android.graphics.RectF; 36 import android.graphics.Xfermode; 37 import android.graphics.drawable.BitmapDrawable; 38 import android.graphics.drawable.Drawable; 39 import android.graphics.drawable.Icon; 40 import android.net.Uri; 41 import android.os.Build; 42 import android.os.Handler; 43 import android.text.TextUtils; 44 import android.util.AttributeSet; 45 import android.util.Log; 46 import android.view.RemotableViewMethod; 47 import android.view.View; 48 import android.view.ViewDebug; 49 import android.view.ViewHierarchyEncoder; 50 import android.view.accessibility.AccessibilityEvent; 51 import android.widget.RemoteViews.RemoteView; 52 53 import com.android.internal.R; 54 55 import java.io.IOException; 56 import java.io.InputStream; 57 58 /** 59 * Displays an arbitrary image, such as an icon. The ImageView class 60 * can load images from various sources (such as resources or content 61 * providers), takes care of computing its measurement from the image so that 62 * it can be used in any layout manager, and provides various display options 63 * such as scaling and tinting. 64 * 65 * @attr ref android.R.styleable#ImageView_adjustViewBounds 66 * @attr ref android.R.styleable#ImageView_src 67 * @attr ref android.R.styleable#ImageView_maxWidth 68 * @attr ref android.R.styleable#ImageView_maxHeight 69 * @attr ref android.R.styleable#ImageView_tint 70 * @attr ref android.R.styleable#ImageView_scaleType 71 * @attr ref android.R.styleable#ImageView_cropToPadding 72 */ 73 @RemoteView 74 public class ImageView extends View { 75 // settable by the client 76 private Uri mUri; 77 private int mResource = 0; 78 private Matrix mMatrix; 79 private ScaleType mScaleType; 80 private boolean mHaveFrame = false; 81 private boolean mAdjustViewBounds = false; 82 private int mMaxWidth = Integer.MAX_VALUE; 83 private int mMaxHeight = Integer.MAX_VALUE; 84 85 // these are applied to the drawable 86 private ColorFilter mColorFilter = null; 87 private boolean mHasColorFilter = false; 88 private Xfermode mXfermode; 89 private int mAlpha = 255; 90 private int mViewAlphaScale = 256; 91 private boolean mColorMod = false; 92 93 private Drawable mDrawable = null; 94 private ImageViewBitmapDrawable mRecycleableBitmapDrawable = null; 95 private ColorStateList mDrawableTintList = null; 96 private PorterDuff.Mode mDrawableTintMode = null; 97 private boolean mHasDrawableTint = false; 98 private boolean mHasDrawableTintMode = false; 99 100 private int[] mState = null; 101 private boolean mMergeState = false; 102 private int mLevel = 0; 103 private int mDrawableWidth; 104 private int mDrawableHeight; 105 private Matrix mDrawMatrix = null; 106 107 // Avoid allocations... 108 private RectF mTempSrc = new RectF(); 109 private RectF mTempDst = new RectF(); 110 111 private boolean mCropToPadding; 112 113 private int mBaseline = -1; 114 private boolean mBaselineAlignBottom = false; 115 116 // AdjustViewBounds behavior will be in compatibility mode for older apps. 117 private boolean mAdjustViewBoundsCompat = false; 118 119 private static final ScaleType[] sScaleTypeArray = { 120 ScaleType.MATRIX, 121 ScaleType.FIT_XY, 122 ScaleType.FIT_START, 123 ScaleType.FIT_CENTER, 124 ScaleType.FIT_END, 125 ScaleType.CENTER, 126 ScaleType.CENTER_CROP, 127 ScaleType.CENTER_INSIDE 128 }; 129 ImageView(Context context)130 public ImageView(Context context) { 131 super(context); 132 initImageView(); 133 } 134 ImageView(Context context, @Nullable AttributeSet attrs)135 public ImageView(Context context, @Nullable AttributeSet attrs) { 136 this(context, attrs, 0); 137 } 138 ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)139 public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 140 this(context, attrs, defStyleAttr, 0); 141 } 142 ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)143 public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, 144 int defStyleRes) { 145 super(context, attrs, defStyleAttr, defStyleRes); 146 147 initImageView(); 148 149 final TypedArray a = context.obtainStyledAttributes( 150 attrs, com.android.internal.R.styleable.ImageView, defStyleAttr, defStyleRes); 151 152 Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src); 153 if (d != null) { 154 setImageDrawable(d); 155 } 156 157 mBaselineAlignBottom = a.getBoolean( 158 com.android.internal.R.styleable.ImageView_baselineAlignBottom, false); 159 160 mBaseline = a.getDimensionPixelSize( 161 com.android.internal.R.styleable.ImageView_baseline, -1); 162 163 setAdjustViewBounds( 164 a.getBoolean(com.android.internal.R.styleable.ImageView_adjustViewBounds, 165 false)); 166 167 setMaxWidth(a.getDimensionPixelSize( 168 com.android.internal.R.styleable.ImageView_maxWidth, Integer.MAX_VALUE)); 169 170 setMaxHeight(a.getDimensionPixelSize( 171 com.android.internal.R.styleable.ImageView_maxHeight, Integer.MAX_VALUE)); 172 173 final int index = a.getInt(com.android.internal.R.styleable.ImageView_scaleType, -1); 174 if (index >= 0) { 175 setScaleType(sScaleTypeArray[index]); 176 } 177 178 if (a.hasValue(R.styleable.ImageView_tint)) { 179 mDrawableTintList = a.getColorStateList(R.styleable.ImageView_tint); 180 mHasDrawableTint = true; 181 182 // Prior to L, this attribute would always set a color filter with 183 // blending mode SRC_ATOP. Preserve that default behavior. 184 mDrawableTintMode = PorterDuff.Mode.SRC_ATOP; 185 mHasDrawableTintMode = true; 186 } 187 188 if (a.hasValue(R.styleable.ImageView_tintMode)) { 189 mDrawableTintMode = Drawable.parseTintMode(a.getInt( 190 R.styleable.ImageView_tintMode, -1), mDrawableTintMode); 191 mHasDrawableTintMode = true; 192 } 193 194 applyImageTint(); 195 196 final int alpha = a.getInt(com.android.internal.R.styleable.ImageView_drawableAlpha, 255); 197 if (alpha != 255) { 198 setAlpha(alpha); 199 } 200 201 mCropToPadding = a.getBoolean( 202 com.android.internal.R.styleable.ImageView_cropToPadding, false); 203 204 a.recycle(); 205 206 //need inflate syntax/reader for matrix 207 } 208 initImageView()209 private void initImageView() { 210 mMatrix = new Matrix(); 211 mScaleType = ScaleType.FIT_CENTER; 212 mAdjustViewBoundsCompat = mContext.getApplicationInfo().targetSdkVersion <= 213 Build.VERSION_CODES.JELLY_BEAN_MR1; 214 } 215 216 @Override verifyDrawable(Drawable dr)217 protected boolean verifyDrawable(Drawable dr) { 218 return mDrawable == dr || super.verifyDrawable(dr); 219 } 220 221 @Override jumpDrawablesToCurrentState()222 public void jumpDrawablesToCurrentState() { 223 super.jumpDrawablesToCurrentState(); 224 if (mDrawable != null) mDrawable.jumpToCurrentState(); 225 } 226 227 @Override invalidateDrawable(Drawable dr)228 public void invalidateDrawable(Drawable dr) { 229 if (dr == mDrawable) { 230 if (dr != null) { 231 // update cached drawable dimensions if they've changed 232 final int w = dr.getIntrinsicWidth(); 233 final int h = dr.getIntrinsicHeight(); 234 if (w != mDrawableWidth || h != mDrawableHeight) { 235 mDrawableWidth = w; 236 mDrawableHeight = h; 237 } 238 } 239 /* we invalidate the whole view in this case because it's very 240 * hard to know where the drawable actually is. This is made 241 * complicated because of the offsets and transformations that 242 * can be applied. In theory we could get the drawable's bounds 243 * and run them through the transformation and offsets, but this 244 * is probably not worth the effort. 245 */ 246 invalidate(); 247 } else { 248 super.invalidateDrawable(dr); 249 } 250 } 251 252 @Override hasOverlappingRendering()253 public boolean hasOverlappingRendering() { 254 return (getBackground() != null && getBackground().getCurrent() != null); 255 } 256 257 /** @hide */ 258 @Override onPopulateAccessibilityEventInternal(AccessibilityEvent event)259 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { 260 super.onPopulateAccessibilityEventInternal(event); 261 CharSequence contentDescription = getContentDescription(); 262 if (!TextUtils.isEmpty(contentDescription)) { 263 event.getText().add(contentDescription); 264 } 265 } 266 267 /** 268 * True when ImageView is adjusting its bounds 269 * to preserve the aspect ratio of its drawable 270 * 271 * @return whether to adjust the bounds of this view 272 * to presrve the original aspect ratio of the drawable 273 * 274 * @see #setAdjustViewBounds(boolean) 275 * 276 * @attr ref android.R.styleable#ImageView_adjustViewBounds 277 */ getAdjustViewBounds()278 public boolean getAdjustViewBounds() { 279 return mAdjustViewBounds; 280 } 281 282 /** 283 * Set this to true if you want the ImageView to adjust its bounds 284 * to preserve the aspect ratio of its drawable. 285 * 286 * <p><strong>Note:</strong> If the application targets API level 17 or lower, 287 * adjustViewBounds will allow the drawable to shrink the view bounds, but not grow 288 * to fill available measured space in all cases. This is for compatibility with 289 * legacy {@link android.view.View.MeasureSpec MeasureSpec} and 290 * {@link android.widget.RelativeLayout RelativeLayout} behavior.</p> 291 * 292 * @param adjustViewBounds Whether to adjust the bounds of this view 293 * to preserve the original aspect ratio of the drawable. 294 * 295 * @see #getAdjustViewBounds() 296 * 297 * @attr ref android.R.styleable#ImageView_adjustViewBounds 298 */ 299 @android.view.RemotableViewMethod setAdjustViewBounds(boolean adjustViewBounds)300 public void setAdjustViewBounds(boolean adjustViewBounds) { 301 mAdjustViewBounds = adjustViewBounds; 302 if (adjustViewBounds) { 303 setScaleType(ScaleType.FIT_CENTER); 304 } 305 } 306 307 /** 308 * The maximum width of this view. 309 * 310 * @return The maximum width of this view 311 * 312 * @see #setMaxWidth(int) 313 * 314 * @attr ref android.R.styleable#ImageView_maxWidth 315 */ getMaxWidth()316 public int getMaxWidth() { 317 return mMaxWidth; 318 } 319 320 /** 321 * An optional argument to supply a maximum width for this view. Only valid if 322 * {@link #setAdjustViewBounds(boolean)} has been set to true. To set an image to be a maximum 323 * of 100 x 100 while preserving the original aspect ratio, do the following: 1) set 324 * adjustViewBounds to true 2) set maxWidth and maxHeight to 100 3) set the height and width 325 * layout params to WRAP_CONTENT. 326 * 327 * <p> 328 * Note that this view could be still smaller than 100 x 100 using this approach if the original 329 * image is small. To set an image to a fixed size, specify that size in the layout params and 330 * then use {@link #setScaleType(android.widget.ImageView.ScaleType)} to determine how to fit 331 * the image within the bounds. 332 * </p> 333 * 334 * @param maxWidth maximum width for this view 335 * 336 * @see #getMaxWidth() 337 * 338 * @attr ref android.R.styleable#ImageView_maxWidth 339 */ 340 @android.view.RemotableViewMethod setMaxWidth(int maxWidth)341 public void setMaxWidth(int maxWidth) { 342 mMaxWidth = maxWidth; 343 } 344 345 /** 346 * The maximum height of this view. 347 * 348 * @return The maximum height of this view 349 * 350 * @see #setMaxHeight(int) 351 * 352 * @attr ref android.R.styleable#ImageView_maxHeight 353 */ getMaxHeight()354 public int getMaxHeight() { 355 return mMaxHeight; 356 } 357 358 /** 359 * An optional argument to supply a maximum height for this view. Only valid if 360 * {@link #setAdjustViewBounds(boolean)} has been set to true. To set an image to be a 361 * maximum of 100 x 100 while preserving the original aspect ratio, do the following: 1) set 362 * adjustViewBounds to true 2) set maxWidth and maxHeight to 100 3) set the height and width 363 * layout params to WRAP_CONTENT. 364 * 365 * <p> 366 * Note that this view could be still smaller than 100 x 100 using this approach if the original 367 * image is small. To set an image to a fixed size, specify that size in the layout params and 368 * then use {@link #setScaleType(android.widget.ImageView.ScaleType)} to determine how to fit 369 * the image within the bounds. 370 * </p> 371 * 372 * @param maxHeight maximum height for this view 373 * 374 * @see #getMaxHeight() 375 * 376 * @attr ref android.R.styleable#ImageView_maxHeight 377 */ 378 @android.view.RemotableViewMethod setMaxHeight(int maxHeight)379 public void setMaxHeight(int maxHeight) { 380 mMaxHeight = maxHeight; 381 } 382 383 /** Return the view's drawable, or null if no drawable has been 384 assigned. 385 */ getDrawable()386 public Drawable getDrawable() { 387 if (mDrawable == mRecycleableBitmapDrawable) { 388 // Consider our cached version dirty since app code now has a reference to it 389 mRecycleableBitmapDrawable = null; 390 } 391 return mDrawable; 392 } 393 394 /** 395 * Sets a drawable as the content of this ImageView. 396 * 397 * <p class="note">This does Bitmap reading and decoding on the UI 398 * thread, which can cause a latency hiccup. If that's a concern, 399 * consider using {@link #setImageDrawable(android.graphics.drawable.Drawable)} or 400 * {@link #setImageBitmap(android.graphics.Bitmap)} and 401 * {@link android.graphics.BitmapFactory} instead.</p> 402 * 403 * @param resId the resource identifier of the drawable 404 * 405 * @attr ref android.R.styleable#ImageView_src 406 */ 407 @android.view.RemotableViewMethod setImageResource(@rawableRes int resId)408 public void setImageResource(@DrawableRes int resId) { 409 // The resource configuration may have changed, so we should always 410 // try to load the resource even if the resId hasn't changed. 411 final int oldWidth = mDrawableWidth; 412 final int oldHeight = mDrawableHeight; 413 414 updateDrawable(null); 415 mResource = resId; 416 mUri = null; 417 418 resolveUri(); 419 420 if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) { 421 requestLayout(); 422 } 423 invalidate(); 424 } 425 426 /** 427 * Sets the content of this ImageView to the specified Uri. 428 * 429 * <p class="note">This does Bitmap reading and decoding on the UI 430 * thread, which can cause a latency hiccup. If that's a concern, 431 * consider using {@link #setImageDrawable(Drawable)} or 432 * {@link #setImageBitmap(android.graphics.Bitmap)} and 433 * {@link android.graphics.BitmapFactory} instead.</p> 434 * 435 * @param uri the Uri of an image, or {@code null} to clear the content 436 */ 437 @android.view.RemotableViewMethod setImageURI(@ullable Uri uri)438 public void setImageURI(@Nullable Uri uri) { 439 if (mResource != 0 || 440 (mUri != uri && 441 (uri == null || mUri == null || !uri.equals(mUri)))) { 442 updateDrawable(null); 443 mResource = 0; 444 mUri = uri; 445 446 final int oldWidth = mDrawableWidth; 447 final int oldHeight = mDrawableHeight; 448 449 resolveUri(); 450 451 if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) { 452 requestLayout(); 453 } 454 invalidate(); 455 } 456 } 457 458 /** 459 * Sets a drawable as the content of this ImageView. 460 * 461 * @param drawable the Drawable to set, or {@code null} to clear the 462 * content 463 */ setImageDrawable(@ullable Drawable drawable)464 public void setImageDrawable(@Nullable Drawable drawable) { 465 if (mDrawable != drawable) { 466 mResource = 0; 467 mUri = null; 468 469 final int oldWidth = mDrawableWidth; 470 final int oldHeight = mDrawableHeight; 471 472 updateDrawable(drawable); 473 474 if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) { 475 requestLayout(); 476 } 477 invalidate(); 478 } 479 } 480 481 /** 482 * Sets the content of this ImageView to the specified Icon. 483 * 484 * <p class="note">Depending on the Icon type, this may do Bitmap reading 485 * and decoding on the UI thread, which can cause UI jank. If that's a 486 * concern, consider using 487 * {@link Icon#loadDrawableAsync(Context, Icon.OnDrawableLoadedListener, Handler)} 488 * and then {@link #setImageDrawable(android.graphics.drawable.Drawable)} 489 * instead.</p> 490 * 491 * @param icon an Icon holding the desired image, or {@code null} to clear 492 * the content 493 */ 494 @android.view.RemotableViewMethod setImageIcon(@ullable Icon icon)495 public void setImageIcon(@Nullable Icon icon) { 496 setImageDrawable(icon == null ? null : icon.loadDrawable(mContext)); 497 } 498 499 /** 500 * Applies a tint to the image drawable. Does not modify the current tint 501 * mode, which is {@link PorterDuff.Mode#SRC_IN} by default. 502 * <p> 503 * Subsequent calls to {@link #setImageDrawable(Drawable)} will automatically 504 * mutate the drawable and apply the specified tint and tint mode using 505 * {@link Drawable#setTintList(ColorStateList)}. 506 * 507 * @param tint the tint to apply, may be {@code null} to clear tint 508 * 509 * @attr ref android.R.styleable#ImageView_tint 510 * @see #getImageTintList() 511 * @see Drawable#setTintList(ColorStateList) 512 */ setImageTintList(@ullable ColorStateList tint)513 public void setImageTintList(@Nullable ColorStateList tint) { 514 mDrawableTintList = tint; 515 mHasDrawableTint = true; 516 517 applyImageTint(); 518 } 519 520 /** 521 * @return the tint applied to the image drawable 522 * @attr ref android.R.styleable#ImageView_tint 523 * @see #setImageTintList(ColorStateList) 524 */ 525 @Nullable getImageTintList()526 public ColorStateList getImageTintList() { 527 return mDrawableTintList; 528 } 529 530 /** 531 * Specifies the blending mode used to apply the tint specified by 532 * {@link #setImageTintList(ColorStateList)}} to the image drawable. The default 533 * mode is {@link PorterDuff.Mode#SRC_IN}. 534 * 535 * @param tintMode the blending mode used to apply the tint, may be 536 * {@code null} to clear tint 537 * @attr ref android.R.styleable#ImageView_tintMode 538 * @see #getImageTintMode() 539 * @see Drawable#setTintMode(PorterDuff.Mode) 540 */ setImageTintMode(@ullable PorterDuff.Mode tintMode)541 public void setImageTintMode(@Nullable PorterDuff.Mode tintMode) { 542 mDrawableTintMode = tintMode; 543 mHasDrawableTintMode = true; 544 545 applyImageTint(); 546 } 547 548 /** 549 * @return the blending mode used to apply the tint to the image drawable 550 * @attr ref android.R.styleable#ImageView_tintMode 551 * @see #setImageTintMode(PorterDuff.Mode) 552 */ 553 @Nullable getImageTintMode()554 public PorterDuff.Mode getImageTintMode() { 555 return mDrawableTintMode; 556 } 557 applyImageTint()558 private void applyImageTint() { 559 if (mDrawable != null && (mHasDrawableTint || mHasDrawableTintMode)) { 560 mDrawable = mDrawable.mutate(); 561 562 if (mHasDrawableTint) { 563 mDrawable.setTintList(mDrawableTintList); 564 } 565 566 if (mHasDrawableTintMode) { 567 mDrawable.setTintMode(mDrawableTintMode); 568 } 569 570 // The drawable (or one of its children) may not have been 571 // stateful before applying the tint, so let's try again. 572 if (mDrawable.isStateful()) { 573 mDrawable.setState(getDrawableState()); 574 } 575 } 576 } 577 578 private static class ImageViewBitmapDrawable extends BitmapDrawable { ImageViewBitmapDrawable(Resources res, Bitmap bitmap)579 public ImageViewBitmapDrawable(Resources res, Bitmap bitmap) { 580 super(res, bitmap); 581 } 582 583 @Override setBitmap(Bitmap bitmap)584 public void setBitmap(Bitmap bitmap) { 585 super.setBitmap(bitmap); 586 } 587 }; 588 589 /** 590 * Sets a Bitmap as the content of this ImageView. 591 * 592 * @param bm The bitmap to set 593 */ 594 @android.view.RemotableViewMethod setImageBitmap(Bitmap bm)595 public void setImageBitmap(Bitmap bm) { 596 // Hacky fix to force setImageDrawable to do a full setImageDrawable 597 // instead of doing an object reference comparison 598 mDrawable = null; 599 if (mRecycleableBitmapDrawable == null) { 600 mRecycleableBitmapDrawable = new ImageViewBitmapDrawable( 601 mContext.getResources(), bm); 602 } else { 603 mRecycleableBitmapDrawable.setBitmap(bm); 604 } 605 setImageDrawable(mRecycleableBitmapDrawable); 606 } 607 setImageState(int[] state, boolean merge)608 public void setImageState(int[] state, boolean merge) { 609 mState = state; 610 mMergeState = merge; 611 if (mDrawable != null) { 612 refreshDrawableState(); 613 resizeFromDrawable(); 614 } 615 } 616 617 @Override setSelected(boolean selected)618 public void setSelected(boolean selected) { 619 super.setSelected(selected); 620 resizeFromDrawable(); 621 } 622 623 /** 624 * Sets the image level, when it is constructed from a 625 * {@link android.graphics.drawable.LevelListDrawable}. 626 * 627 * @param level The new level for the image. 628 */ 629 @android.view.RemotableViewMethod setImageLevel(int level)630 public void setImageLevel(int level) { 631 mLevel = level; 632 if (mDrawable != null) { 633 mDrawable.setLevel(level); 634 resizeFromDrawable(); 635 } 636 } 637 638 /** 639 * Options for scaling the bounds of an image to the bounds of this view. 640 */ 641 public enum ScaleType { 642 /** 643 * Scale using the image matrix when drawing. The image matrix can be set using 644 * {@link ImageView#setImageMatrix(Matrix)}. From XML, use this syntax: 645 * <code>android:scaleType="matrix"</code>. 646 */ 647 MATRIX (0), 648 /** 649 * Scale the image using {@link Matrix.ScaleToFit#FILL}. 650 * From XML, use this syntax: <code>android:scaleType="fitXY"</code>. 651 */ 652 FIT_XY (1), 653 /** 654 * Scale the image using {@link Matrix.ScaleToFit#START}. 655 * From XML, use this syntax: <code>android:scaleType="fitStart"</code>. 656 */ 657 FIT_START (2), 658 /** 659 * Scale the image using {@link Matrix.ScaleToFit#CENTER}. 660 * From XML, use this syntax: 661 * <code>android:scaleType="fitCenter"</code>. 662 */ 663 FIT_CENTER (3), 664 /** 665 * Scale the image using {@link Matrix.ScaleToFit#END}. 666 * From XML, use this syntax: <code>android:scaleType="fitEnd"</code>. 667 */ 668 FIT_END (4), 669 /** 670 * Center the image in the view, but perform no scaling. 671 * From XML, use this syntax: <code>android:scaleType="center"</code>. 672 */ 673 CENTER (5), 674 /** 675 * Scale the image uniformly (maintain the image's aspect ratio) so 676 * that both dimensions (width and height) of the image will be equal 677 * to or larger than the corresponding dimension of the view 678 * (minus padding). The image is then centered in the view. 679 * From XML, use this syntax: <code>android:scaleType="centerCrop"</code>. 680 */ 681 CENTER_CROP (6), 682 /** 683 * Scale the image uniformly (maintain the image's aspect ratio) so 684 * that both dimensions (width and height) of the image will be equal 685 * to or less than the corresponding dimension of the view 686 * (minus padding). The image is then centered in the view. 687 * From XML, use this syntax: <code>android:scaleType="centerInside"</code>. 688 */ 689 CENTER_INSIDE (7); 690 ScaleType(int ni)691 ScaleType(int ni) { 692 nativeInt = ni; 693 } 694 final int nativeInt; 695 } 696 697 /** 698 * Controls how the image should be resized or moved to match the size 699 * of this ImageView. 700 * 701 * @param scaleType The desired scaling mode. 702 * 703 * @attr ref android.R.styleable#ImageView_scaleType 704 */ setScaleType(ScaleType scaleType)705 public void setScaleType(ScaleType scaleType) { 706 if (scaleType == null) { 707 throw new NullPointerException(); 708 } 709 710 if (mScaleType != scaleType) { 711 mScaleType = scaleType; 712 713 setWillNotCacheDrawing(mScaleType == ScaleType.CENTER); 714 715 requestLayout(); 716 invalidate(); 717 } 718 } 719 720 /** 721 * Return the current scale type in use by this ImageView. 722 * 723 * @see ImageView.ScaleType 724 * 725 * @attr ref android.R.styleable#ImageView_scaleType 726 */ getScaleType()727 public ScaleType getScaleType() { 728 return mScaleType; 729 } 730 731 /** Return the view's optional matrix. This is applied to the 732 view's drawable when it is drawn. If there is no matrix, 733 this method will return an identity matrix. 734 Do not change this matrix in place but make a copy. 735 If you want a different matrix applied to the drawable, 736 be sure to call setImageMatrix(). 737 */ getImageMatrix()738 public Matrix getImageMatrix() { 739 if (mDrawMatrix == null) { 740 return new Matrix(Matrix.IDENTITY_MATRIX); 741 } 742 return mDrawMatrix; 743 } 744 745 /** 746 * Adds a transformation {@link Matrix} that is applied 747 * to the view's drawable when it is drawn. Allows custom scaling, 748 * translation, and perspective distortion. 749 * 750 * @param matrix the transformation parameters in matrix form 751 */ setImageMatrix(Matrix matrix)752 public void setImageMatrix(Matrix matrix) { 753 // collapse null and identity to just null 754 if (matrix != null && matrix.isIdentity()) { 755 matrix = null; 756 } 757 758 // don't invalidate unless we're actually changing our matrix 759 if (matrix == null && !mMatrix.isIdentity() || 760 matrix != null && !mMatrix.equals(matrix)) { 761 mMatrix.set(matrix); 762 configureBounds(); 763 invalidate(); 764 } 765 } 766 767 /** 768 * Return whether this ImageView crops to padding. 769 * 770 * @return whether this ImageView crops to padding 771 * 772 * @see #setCropToPadding(boolean) 773 * 774 * @attr ref android.R.styleable#ImageView_cropToPadding 775 */ getCropToPadding()776 public boolean getCropToPadding() { 777 return mCropToPadding; 778 } 779 780 /** 781 * Sets whether this ImageView will crop to padding. 782 * 783 * @param cropToPadding whether this ImageView will crop to padding 784 * 785 * @see #getCropToPadding() 786 * 787 * @attr ref android.R.styleable#ImageView_cropToPadding 788 */ setCropToPadding(boolean cropToPadding)789 public void setCropToPadding(boolean cropToPadding) { 790 if (mCropToPadding != cropToPadding) { 791 mCropToPadding = cropToPadding; 792 requestLayout(); 793 invalidate(); 794 } 795 } 796 resolveUri()797 private void resolveUri() { 798 if (mDrawable != null) { 799 return; 800 } 801 802 Resources rsrc = getResources(); 803 if (rsrc == null) { 804 return; 805 } 806 807 Drawable d = null; 808 809 if (mResource != 0) { 810 try { 811 d = mContext.getDrawable(mResource); 812 } catch (Exception e) { 813 Log.w("ImageView", "Unable to find resource: " + mResource, e); 814 // Don't try again. 815 mUri = null; 816 } 817 } else if (mUri != null) { 818 String scheme = mUri.getScheme(); 819 if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) { 820 try { 821 // Load drawable through Resources, to get the source density information 822 ContentResolver.OpenResourceIdResult r = 823 mContext.getContentResolver().getResourceId(mUri); 824 d = r.r.getDrawable(r.id, mContext.getTheme()); 825 } catch (Exception e) { 826 Log.w("ImageView", "Unable to open content: " + mUri, e); 827 } 828 } else if (ContentResolver.SCHEME_CONTENT.equals(scheme) 829 || ContentResolver.SCHEME_FILE.equals(scheme)) { 830 InputStream stream = null; 831 try { 832 stream = mContext.getContentResolver().openInputStream(mUri); 833 d = Drawable.createFromStream(stream, null); 834 } catch (Exception e) { 835 Log.w("ImageView", "Unable to open content: " + mUri, e); 836 } finally { 837 if (stream != null) { 838 try { 839 stream.close(); 840 } catch (IOException e) { 841 Log.w("ImageView", "Unable to close content: " + mUri, e); 842 } 843 } 844 } 845 } else { 846 d = Drawable.createFromPath(mUri.toString()); 847 } 848 849 if (d == null) { 850 System.out.println("resolveUri failed on bad bitmap uri: " + mUri); 851 // Don't try again. 852 mUri = null; 853 } 854 } else { 855 return; 856 } 857 858 updateDrawable(d); 859 } 860 861 @Override onCreateDrawableState(int extraSpace)862 public int[] onCreateDrawableState(int extraSpace) { 863 if (mState == null) { 864 return super.onCreateDrawableState(extraSpace); 865 } else if (!mMergeState) { 866 return mState; 867 } else { 868 return mergeDrawableStates( 869 super.onCreateDrawableState(extraSpace + mState.length), mState); 870 } 871 } 872 updateDrawable(Drawable d)873 private void updateDrawable(Drawable d) { 874 if (d != mRecycleableBitmapDrawable && mRecycleableBitmapDrawable != null) { 875 mRecycleableBitmapDrawable.setBitmap(null); 876 } 877 878 if (mDrawable != null) { 879 mDrawable.setCallback(null); 880 unscheduleDrawable(mDrawable); 881 } 882 883 mDrawable = d; 884 885 if (d != null) { 886 d.setCallback(this); 887 d.setLayoutDirection(getLayoutDirection()); 888 if (d.isStateful()) { 889 d.setState(getDrawableState()); 890 } 891 d.setVisible(getVisibility() == VISIBLE, true); 892 d.setLevel(mLevel); 893 mDrawableWidth = d.getIntrinsicWidth(); 894 mDrawableHeight = d.getIntrinsicHeight(); 895 applyImageTint(); 896 applyColorMod(); 897 898 configureBounds(); 899 } else { 900 mDrawableWidth = mDrawableHeight = -1; 901 } 902 } 903 resizeFromDrawable()904 private void resizeFromDrawable() { 905 Drawable d = mDrawable; 906 if (d != null) { 907 int w = d.getIntrinsicWidth(); 908 if (w < 0) w = mDrawableWidth; 909 int h = d.getIntrinsicHeight(); 910 if (h < 0) h = mDrawableHeight; 911 if (w != mDrawableWidth || h != mDrawableHeight) { 912 mDrawableWidth = w; 913 mDrawableHeight = h; 914 requestLayout(); 915 } 916 } 917 } 918 919 @Override onRtlPropertiesChanged(int layoutDirection)920 public void onRtlPropertiesChanged(int layoutDirection) { 921 super.onRtlPropertiesChanged(layoutDirection); 922 923 if (mDrawable != null) { 924 mDrawable.setLayoutDirection(layoutDirection); 925 } 926 } 927 928 private static final Matrix.ScaleToFit[] sS2FArray = { 929 Matrix.ScaleToFit.FILL, 930 Matrix.ScaleToFit.START, 931 Matrix.ScaleToFit.CENTER, 932 Matrix.ScaleToFit.END 933 }; 934 scaleTypeToScaleToFit(ScaleType st)935 private static Matrix.ScaleToFit scaleTypeToScaleToFit(ScaleType st) { 936 // ScaleToFit enum to their corresponding Matrix.ScaleToFit values 937 return sS2FArray[st.nativeInt - 1]; 938 } 939 940 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)941 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 942 resolveUri(); 943 int w; 944 int h; 945 946 // Desired aspect ratio of the view's contents (not including padding) 947 float desiredAspect = 0.0f; 948 949 // We are allowed to change the view's width 950 boolean resizeWidth = false; 951 952 // We are allowed to change the view's height 953 boolean resizeHeight = false; 954 955 final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 956 final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 957 958 if (mDrawable == null) { 959 // If no drawable, its intrinsic size is 0. 960 mDrawableWidth = -1; 961 mDrawableHeight = -1; 962 w = h = 0; 963 } else { 964 w = mDrawableWidth; 965 h = mDrawableHeight; 966 if (w <= 0) w = 1; 967 if (h <= 0) h = 1; 968 969 // We are supposed to adjust view bounds to match the aspect 970 // ratio of our drawable. See if that is possible. 971 if (mAdjustViewBounds) { 972 resizeWidth = widthSpecMode != MeasureSpec.EXACTLY; 973 resizeHeight = heightSpecMode != MeasureSpec.EXACTLY; 974 975 desiredAspect = (float) w / (float) h; 976 } 977 } 978 979 int pleft = mPaddingLeft; 980 int pright = mPaddingRight; 981 int ptop = mPaddingTop; 982 int pbottom = mPaddingBottom; 983 984 int widthSize; 985 int heightSize; 986 987 if (resizeWidth || resizeHeight) { 988 /* If we get here, it means we want to resize to match the 989 drawables aspect ratio, and we have the freedom to change at 990 least one dimension. 991 */ 992 993 // Get the max possible width given our constraints 994 widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec); 995 996 // Get the max possible height given our constraints 997 heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec); 998 999 if (desiredAspect != 0.0f) { 1000 // See what our actual aspect ratio is 1001 float actualAspect = (float)(widthSize - pleft - pright) / 1002 (heightSize - ptop - pbottom); 1003 1004 if (Math.abs(actualAspect - desiredAspect) > 0.0000001) { 1005 1006 boolean done = false; 1007 1008 // Try adjusting width to be proportional to height 1009 if (resizeWidth) { 1010 int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) + 1011 pleft + pright; 1012 1013 // Allow the width to outgrow its original estimate if height is fixed. 1014 if (!resizeHeight && !mAdjustViewBoundsCompat) { 1015 widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec); 1016 } 1017 1018 if (newWidth <= widthSize) { 1019 widthSize = newWidth; 1020 done = true; 1021 } 1022 } 1023 1024 // Try adjusting height to be proportional to width 1025 if (!done && resizeHeight) { 1026 int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) + 1027 ptop + pbottom; 1028 1029 // Allow the height to outgrow its original estimate if width is fixed. 1030 if (!resizeWidth && !mAdjustViewBoundsCompat) { 1031 heightSize = resolveAdjustedSize(newHeight, mMaxHeight, 1032 heightMeasureSpec); 1033 } 1034 1035 if (newHeight <= heightSize) { 1036 heightSize = newHeight; 1037 } 1038 } 1039 } 1040 } 1041 } else { 1042 /* We are either don't want to preserve the drawables aspect ratio, 1043 or we are not allowed to change view dimensions. Just measure in 1044 the normal way. 1045 */ 1046 w += pleft + pright; 1047 h += ptop + pbottom; 1048 1049 w = Math.max(w, getSuggestedMinimumWidth()); 1050 h = Math.max(h, getSuggestedMinimumHeight()); 1051 1052 widthSize = resolveSizeAndState(w, widthMeasureSpec, 0); 1053 heightSize = resolveSizeAndState(h, heightMeasureSpec, 0); 1054 } 1055 1056 setMeasuredDimension(widthSize, heightSize); 1057 } 1058 resolveAdjustedSize(int desiredSize, int maxSize, int measureSpec)1059 private int resolveAdjustedSize(int desiredSize, int maxSize, 1060 int measureSpec) { 1061 int result = desiredSize; 1062 int specMode = MeasureSpec.getMode(measureSpec); 1063 int specSize = MeasureSpec.getSize(measureSpec); 1064 switch (specMode) { 1065 case MeasureSpec.UNSPECIFIED: 1066 /* Parent says we can be as big as we want. Just don't be larger 1067 than max size imposed on ourselves. 1068 */ 1069 result = Math.min(desiredSize, maxSize); 1070 break; 1071 case MeasureSpec.AT_MOST: 1072 // Parent says we can be as big as we want, up to specSize. 1073 // Don't be larger than specSize, and don't be larger than 1074 // the max size imposed on ourselves. 1075 result = Math.min(Math.min(desiredSize, specSize), maxSize); 1076 break; 1077 case MeasureSpec.EXACTLY: 1078 // No choice. Do what we are told. 1079 result = specSize; 1080 break; 1081 } 1082 return result; 1083 } 1084 1085 @Override setFrame(int l, int t, int r, int b)1086 protected boolean setFrame(int l, int t, int r, int b) { 1087 boolean changed = super.setFrame(l, t, r, b); 1088 mHaveFrame = true; 1089 configureBounds(); 1090 return changed; 1091 } 1092 configureBounds()1093 private void configureBounds() { 1094 if (mDrawable == null || !mHaveFrame) { 1095 return; 1096 } 1097 1098 int dwidth = mDrawableWidth; 1099 int dheight = mDrawableHeight; 1100 1101 int vwidth = getWidth() - mPaddingLeft - mPaddingRight; 1102 int vheight = getHeight() - mPaddingTop - mPaddingBottom; 1103 1104 boolean fits = (dwidth < 0 || vwidth == dwidth) && 1105 (dheight < 0 || vheight == dheight); 1106 1107 if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) { 1108 /* If the drawable has no intrinsic size, or we're told to 1109 scaletofit, then we just fill our entire view. 1110 */ 1111 mDrawable.setBounds(0, 0, vwidth, vheight); 1112 mDrawMatrix = null; 1113 } else { 1114 // We need to do the scaling ourself, so have the drawable 1115 // use its native size. 1116 mDrawable.setBounds(0, 0, dwidth, dheight); 1117 1118 if (ScaleType.MATRIX == mScaleType) { 1119 // Use the specified matrix as-is. 1120 if (mMatrix.isIdentity()) { 1121 mDrawMatrix = null; 1122 } else { 1123 mDrawMatrix = mMatrix; 1124 } 1125 } else if (fits) { 1126 // The bitmap fits exactly, no transform needed. 1127 mDrawMatrix = null; 1128 } else if (ScaleType.CENTER == mScaleType) { 1129 // Center bitmap in view, no scaling. 1130 mDrawMatrix = mMatrix; 1131 mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f), 1132 Math.round((vheight - dheight) * 0.5f)); 1133 } else if (ScaleType.CENTER_CROP == mScaleType) { 1134 mDrawMatrix = mMatrix; 1135 1136 float scale; 1137 float dx = 0, dy = 0; 1138 1139 if (dwidth * vheight > vwidth * dheight) { 1140 scale = (float) vheight / (float) dheight; 1141 dx = (vwidth - dwidth * scale) * 0.5f; 1142 } else { 1143 scale = (float) vwidth / (float) dwidth; 1144 dy = (vheight - dheight * scale) * 0.5f; 1145 } 1146 1147 mDrawMatrix.setScale(scale, scale); 1148 mDrawMatrix.postTranslate(Math.round(dx), Math.round(dy)); 1149 } else if (ScaleType.CENTER_INSIDE == mScaleType) { 1150 mDrawMatrix = mMatrix; 1151 float scale; 1152 float dx; 1153 float dy; 1154 1155 if (dwidth <= vwidth && dheight <= vheight) { 1156 scale = 1.0f; 1157 } else { 1158 scale = Math.min((float) vwidth / (float) dwidth, 1159 (float) vheight / (float) dheight); 1160 } 1161 1162 dx = Math.round((vwidth - dwidth * scale) * 0.5f); 1163 dy = Math.round((vheight - dheight * scale) * 0.5f); 1164 1165 mDrawMatrix.setScale(scale, scale); 1166 mDrawMatrix.postTranslate(dx, dy); 1167 } else { 1168 // Generate the required transform. 1169 mTempSrc.set(0, 0, dwidth, dheight); 1170 mTempDst.set(0, 0, vwidth, vheight); 1171 1172 mDrawMatrix = mMatrix; 1173 mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType)); 1174 } 1175 } 1176 } 1177 1178 @Override drawableStateChanged()1179 protected void drawableStateChanged() { 1180 super.drawableStateChanged(); 1181 Drawable d = mDrawable; 1182 if (d != null && d.isStateful()) { 1183 d.setState(getDrawableState()); 1184 } 1185 } 1186 1187 @Override drawableHotspotChanged(float x, float y)1188 public void drawableHotspotChanged(float x, float y) { 1189 super.drawableHotspotChanged(x, y); 1190 1191 if (mDrawable != null) { 1192 mDrawable.setHotspot(x, y); 1193 } 1194 } 1195 1196 /** @hide */ animateTransform(Matrix matrix)1197 public void animateTransform(Matrix matrix) { 1198 if (mDrawable == null) { 1199 return; 1200 } 1201 if (matrix == null) { 1202 mDrawable.setBounds(0, 0, getWidth(), getHeight()); 1203 } else { 1204 mDrawable.setBounds(0, 0, mDrawableWidth, mDrawableHeight); 1205 if (mDrawMatrix == null) { 1206 mDrawMatrix = new Matrix(); 1207 } 1208 mDrawMatrix.set(matrix); 1209 } 1210 invalidate(); 1211 } 1212 1213 @Override onDraw(Canvas canvas)1214 protected void onDraw(Canvas canvas) { 1215 super.onDraw(canvas); 1216 1217 if (mDrawable == null) { 1218 return; // couldn't resolve the URI 1219 } 1220 1221 if (mDrawableWidth == 0 || mDrawableHeight == 0) { 1222 return; // nothing to draw (empty bounds) 1223 } 1224 1225 if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) { 1226 mDrawable.draw(canvas); 1227 } else { 1228 int saveCount = canvas.getSaveCount(); 1229 canvas.save(); 1230 1231 if (mCropToPadding) { 1232 final int scrollX = mScrollX; 1233 final int scrollY = mScrollY; 1234 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop, 1235 scrollX + mRight - mLeft - mPaddingRight, 1236 scrollY + mBottom - mTop - mPaddingBottom); 1237 } 1238 1239 canvas.translate(mPaddingLeft, mPaddingTop); 1240 1241 if (mDrawMatrix != null) { 1242 canvas.concat(mDrawMatrix); 1243 } 1244 mDrawable.draw(canvas); 1245 canvas.restoreToCount(saveCount); 1246 } 1247 } 1248 1249 /** 1250 * <p>Return the offset of the widget's text baseline from the widget's top 1251 * boundary. </p> 1252 * 1253 * @return the offset of the baseline within the widget's bounds or -1 1254 * if baseline alignment is not supported. 1255 */ 1256 @Override 1257 @ViewDebug.ExportedProperty(category = "layout") getBaseline()1258 public int getBaseline() { 1259 if (mBaselineAlignBottom) { 1260 return getMeasuredHeight(); 1261 } else { 1262 return mBaseline; 1263 } 1264 } 1265 1266 /** 1267 * <p>Set the offset of the widget's text baseline from the widget's top 1268 * boundary. This value is overridden by the {@link #setBaselineAlignBottom(boolean)} 1269 * property.</p> 1270 * 1271 * @param baseline The baseline to use, or -1 if none is to be provided. 1272 * 1273 * @see #setBaseline(int) 1274 * @attr ref android.R.styleable#ImageView_baseline 1275 */ setBaseline(int baseline)1276 public void setBaseline(int baseline) { 1277 if (mBaseline != baseline) { 1278 mBaseline = baseline; 1279 requestLayout(); 1280 } 1281 } 1282 1283 /** 1284 * Set whether to set the baseline of this view to the bottom of the view. 1285 * Setting this value overrides any calls to setBaseline. 1286 * 1287 * @param aligned If true, the image view will be baseline aligned with 1288 * based on its bottom edge. 1289 * 1290 * @attr ref android.R.styleable#ImageView_baselineAlignBottom 1291 */ setBaselineAlignBottom(boolean aligned)1292 public void setBaselineAlignBottom(boolean aligned) { 1293 if (mBaselineAlignBottom != aligned) { 1294 mBaselineAlignBottom = aligned; 1295 requestLayout(); 1296 } 1297 } 1298 1299 /** 1300 * Return whether this view's baseline will be considered the bottom of the view. 1301 * 1302 * @see #setBaselineAlignBottom(boolean) 1303 */ getBaselineAlignBottom()1304 public boolean getBaselineAlignBottom() { 1305 return mBaselineAlignBottom; 1306 } 1307 1308 /** 1309 * Set a tinting option for the image. 1310 * 1311 * @param color Color tint to apply. 1312 * @param mode How to apply the color. The standard mode is 1313 * {@link PorterDuff.Mode#SRC_ATOP} 1314 * 1315 * @attr ref android.R.styleable#ImageView_tint 1316 */ setColorFilter(int color, PorterDuff.Mode mode)1317 public final void setColorFilter(int color, PorterDuff.Mode mode) { 1318 setColorFilter(new PorterDuffColorFilter(color, mode)); 1319 } 1320 1321 /** 1322 * Set a tinting option for the image. Assumes 1323 * {@link PorterDuff.Mode#SRC_ATOP} blending mode. 1324 * 1325 * @param color Color tint to apply. 1326 * @attr ref android.R.styleable#ImageView_tint 1327 */ 1328 @RemotableViewMethod setColorFilter(int color)1329 public final void setColorFilter(int color) { 1330 setColorFilter(color, PorterDuff.Mode.SRC_ATOP); 1331 } 1332 clearColorFilter()1333 public final void clearColorFilter() { 1334 setColorFilter(null); 1335 } 1336 1337 /** 1338 * @hide Candidate for future API inclusion 1339 */ setXfermode(Xfermode mode)1340 public final void setXfermode(Xfermode mode) { 1341 if (mXfermode != mode) { 1342 mXfermode = mode; 1343 mColorMod = true; 1344 applyColorMod(); 1345 invalidate(); 1346 } 1347 } 1348 1349 /** 1350 * Returns the active color filter for this ImageView. 1351 * 1352 * @return the active color filter for this ImageView 1353 * 1354 * @see #setColorFilter(android.graphics.ColorFilter) 1355 */ getColorFilter()1356 public ColorFilter getColorFilter() { 1357 return mColorFilter; 1358 } 1359 1360 /** 1361 * Apply an arbitrary colorfilter to the image. 1362 * 1363 * @param cf the colorfilter to apply (may be null) 1364 * 1365 * @see #getColorFilter() 1366 */ setColorFilter(ColorFilter cf)1367 public void setColorFilter(ColorFilter cf) { 1368 if (mColorFilter != cf) { 1369 mColorFilter = cf; 1370 mHasColorFilter = true; 1371 mColorMod = true; 1372 applyColorMod(); 1373 invalidate(); 1374 } 1375 } 1376 1377 /** 1378 * Returns the alpha that will be applied to the drawable of this ImageView. 1379 * 1380 * @return the alpha that will be applied to the drawable of this ImageView 1381 * 1382 * @see #setImageAlpha(int) 1383 */ getImageAlpha()1384 public int getImageAlpha() { 1385 return mAlpha; 1386 } 1387 1388 /** 1389 * Sets the alpha value that should be applied to the image. 1390 * 1391 * @param alpha the alpha value that should be applied to the image 1392 * 1393 * @see #getImageAlpha() 1394 */ 1395 @RemotableViewMethod setImageAlpha(int alpha)1396 public void setImageAlpha(int alpha) { 1397 setAlpha(alpha); 1398 } 1399 1400 /** 1401 * Sets the alpha value that should be applied to the image. 1402 * 1403 * @param alpha the alpha value that should be applied to the image 1404 * 1405 * @deprecated use #setImageAlpha(int) instead 1406 */ 1407 @Deprecated 1408 @RemotableViewMethod setAlpha(int alpha)1409 public void setAlpha(int alpha) { 1410 alpha &= 0xFF; // keep it legal 1411 if (mAlpha != alpha) { 1412 mAlpha = alpha; 1413 mColorMod = true; 1414 applyColorMod(); 1415 invalidate(); 1416 } 1417 } 1418 applyColorMod()1419 private void applyColorMod() { 1420 // Only mutate and apply when modifications have occurred. This should 1421 // not reset the mColorMod flag, since these filters need to be 1422 // re-applied if the Drawable is changed. 1423 if (mDrawable != null && mColorMod) { 1424 mDrawable = mDrawable.mutate(); 1425 if (mHasColorFilter) { 1426 mDrawable.setColorFilter(mColorFilter); 1427 } 1428 mDrawable.setXfermode(mXfermode); 1429 mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8); 1430 } 1431 } 1432 1433 @Override isOpaque()1434 public boolean isOpaque() { 1435 return super.isOpaque() || mDrawable != null && mXfermode == null 1436 && mDrawable.getOpacity() == PixelFormat.OPAQUE 1437 && mAlpha * mViewAlphaScale >> 8 == 255 1438 && isFilledByImage(); 1439 } 1440 isFilledByImage()1441 private boolean isFilledByImage() { 1442 if (mDrawable == null) { 1443 return false; 1444 } 1445 1446 final Rect bounds = mDrawable.getBounds(); 1447 final Matrix matrix = mDrawMatrix; 1448 if (matrix == null) { 1449 return bounds.left <= 0 && bounds.top <= 0 && bounds.right >= getWidth() 1450 && bounds.bottom >= getHeight(); 1451 } else if (matrix.rectStaysRect()) { 1452 final RectF boundsSrc = mTempSrc; 1453 final RectF boundsDst = mTempDst; 1454 boundsSrc.set(bounds); 1455 matrix.mapRect(boundsDst, boundsSrc); 1456 return boundsDst.left <= 0 && boundsDst.top <= 0 && boundsDst.right >= getWidth() 1457 && boundsDst.bottom >= getHeight(); 1458 } else { 1459 // If the matrix doesn't map to a rectangle, assume the worst. 1460 return false; 1461 } 1462 } 1463 1464 @RemotableViewMethod 1465 @Override setVisibility(int visibility)1466 public void setVisibility(int visibility) { 1467 super.setVisibility(visibility); 1468 if (mDrawable != null) { 1469 mDrawable.setVisible(visibility == VISIBLE, false); 1470 } 1471 } 1472 1473 @Override onAttachedToWindow()1474 protected void onAttachedToWindow() { 1475 super.onAttachedToWindow(); 1476 if (mDrawable != null) { 1477 mDrawable.setVisible(getVisibility() == VISIBLE, false); 1478 } 1479 } 1480 1481 @Override onDetachedFromWindow()1482 protected void onDetachedFromWindow() { 1483 super.onDetachedFromWindow(); 1484 if (mDrawable != null) { 1485 mDrawable.setVisible(false, false); 1486 } 1487 } 1488 1489 @Override getAccessibilityClassName()1490 public CharSequence getAccessibilityClassName() { 1491 return ImageView.class.getName(); 1492 } 1493 1494 /** @hide */ 1495 @Override encodeProperties(@onNull ViewHierarchyEncoder stream)1496 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { 1497 super.encodeProperties(stream); 1498 stream.addProperty("layout:baseline", getBaseline()); 1499 } 1500 } 1501