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