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