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