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