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