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