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