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