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