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