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