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