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