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