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