ImageView.java revision d5edc7721791ad807b9a8fbd923b8d6e73c399cc
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 793 // Allow the width to outgrow its original estimate if height is fixed. 794 if (!resizeHeight) { 795 widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec); 796 } 797 798 if (newWidth <= widthSize) { 799 widthSize = newWidth; 800 done = true; 801 } 802 } 803 804 // Try adjusting height to be proportional to width 805 if (!done && resizeHeight) { 806 int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) + 807 ptop + pbottom; 808 809 // Allow the height to outgrow its original estimate if width is fixed. 810 if (!resizeWidth) { 811 heightSize = resolveAdjustedSize(newHeight, mMaxHeight, 812 heightMeasureSpec); 813 } 814 815 if (newHeight <= heightSize) { 816 heightSize = newHeight; 817 } 818 } 819 } 820 } 821 } else { 822 /* We are either don't want to preserve the drawables aspect ratio, 823 or we are not allowed to change view dimensions. Just measure in 824 the normal way. 825 */ 826 w += pleft + pright; 827 h += ptop + pbottom; 828 829 w = Math.max(w, getSuggestedMinimumWidth()); 830 h = Math.max(h, getSuggestedMinimumHeight()); 831 832 widthSize = resolveSizeAndState(w, widthMeasureSpec, 0); 833 heightSize = resolveSizeAndState(h, heightMeasureSpec, 0); 834 } 835 836 setMeasuredDimension(widthSize, heightSize); 837 } 838 839 private int resolveAdjustedSize(int desiredSize, int maxSize, 840 int measureSpec) { 841 int result = desiredSize; 842 int specMode = MeasureSpec.getMode(measureSpec); 843 int specSize = MeasureSpec.getSize(measureSpec); 844 switch (specMode) { 845 case MeasureSpec.UNSPECIFIED: 846 /* Parent says we can be as big as we want. Just don't be larger 847 than max size imposed on ourselves. 848 */ 849 result = Math.min(desiredSize, maxSize); 850 break; 851 case MeasureSpec.AT_MOST: 852 // Parent says we can be as big as we want, up to specSize. 853 // Don't be larger than specSize, and don't be larger than 854 // the max size imposed on ourselves. 855 result = Math.min(Math.min(desiredSize, specSize), maxSize); 856 break; 857 case MeasureSpec.EXACTLY: 858 // No choice. Do what we are told. 859 result = specSize; 860 break; 861 } 862 return result; 863 } 864 865 @Override 866 protected boolean setFrame(int l, int t, int r, int b) { 867 boolean changed = super.setFrame(l, t, r, b); 868 mHaveFrame = true; 869 configureBounds(); 870 return changed; 871 } 872 873 private void configureBounds() { 874 if (mDrawable == null || !mHaveFrame) { 875 return; 876 } 877 878 int dwidth = mDrawableWidth; 879 int dheight = mDrawableHeight; 880 881 int vwidth = getWidth() - mPaddingLeft - mPaddingRight; 882 int vheight = getHeight() - mPaddingTop - mPaddingBottom; 883 884 boolean fits = (dwidth < 0 || vwidth == dwidth) && 885 (dheight < 0 || vheight == dheight); 886 887 if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) { 888 /* If the drawable has no intrinsic size, or we're told to 889 scaletofit, then we just fill our entire view. 890 */ 891 mDrawable.setBounds(0, 0, vwidth, vheight); 892 mDrawMatrix = null; 893 } else { 894 // We need to do the scaling ourself, so have the drawable 895 // use its native size. 896 mDrawable.setBounds(0, 0, dwidth, dheight); 897 898 if (ScaleType.MATRIX == mScaleType) { 899 // Use the specified matrix as-is. 900 if (mMatrix.isIdentity()) { 901 mDrawMatrix = null; 902 } else { 903 mDrawMatrix = mMatrix; 904 } 905 } else if (fits) { 906 // The bitmap fits exactly, no transform needed. 907 mDrawMatrix = null; 908 } else if (ScaleType.CENTER == mScaleType) { 909 // Center bitmap in view, no scaling. 910 mDrawMatrix = mMatrix; 911 mDrawMatrix.setTranslate((int) ((vwidth - dwidth) * 0.5f + 0.5f), 912 (int) ((vheight - dheight) * 0.5f + 0.5f)); 913 } else if (ScaleType.CENTER_CROP == mScaleType) { 914 mDrawMatrix = mMatrix; 915 916 float scale; 917 float dx = 0, dy = 0; 918 919 if (dwidth * vheight > vwidth * dheight) { 920 scale = (float) vheight / (float) dheight; 921 dx = (vwidth - dwidth * scale) * 0.5f; 922 } else { 923 scale = (float) vwidth / (float) dwidth; 924 dy = (vheight - dheight * scale) * 0.5f; 925 } 926 927 mDrawMatrix.setScale(scale, scale); 928 mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); 929 } else if (ScaleType.CENTER_INSIDE == mScaleType) { 930 mDrawMatrix = mMatrix; 931 float scale; 932 float dx; 933 float dy; 934 935 if (dwidth <= vwidth && dheight <= vheight) { 936 scale = 1.0f; 937 } else { 938 scale = Math.min((float) vwidth / (float) dwidth, 939 (float) vheight / (float) dheight); 940 } 941 942 dx = (int) ((vwidth - dwidth * scale) * 0.5f + 0.5f); 943 dy = (int) ((vheight - dheight * scale) * 0.5f + 0.5f); 944 945 mDrawMatrix.setScale(scale, scale); 946 mDrawMatrix.postTranslate(dx, dy); 947 } else { 948 // Generate the required transform. 949 mTempSrc.set(0, 0, dwidth, dheight); 950 mTempDst.set(0, 0, vwidth, vheight); 951 952 mDrawMatrix = mMatrix; 953 mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType)); 954 } 955 } 956 } 957 958 @Override 959 protected void drawableStateChanged() { 960 super.drawableStateChanged(); 961 Drawable d = mDrawable; 962 if (d != null && d.isStateful()) { 963 d.setState(getDrawableState()); 964 } 965 } 966 967 @Override 968 protected void onDraw(Canvas canvas) { 969 super.onDraw(canvas); 970 971 if (mDrawable == null) { 972 return; // couldn't resolve the URI 973 } 974 975 if (mDrawableWidth == 0 || mDrawableHeight == 0) { 976 return; // nothing to draw (empty bounds) 977 } 978 979 if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) { 980 mDrawable.draw(canvas); 981 } else { 982 int saveCount = canvas.getSaveCount(); 983 canvas.save(); 984 985 if (mCropToPadding) { 986 final int scrollX = mScrollX; 987 final int scrollY = mScrollY; 988 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop, 989 scrollX + mRight - mLeft - mPaddingRight, 990 scrollY + mBottom - mTop - mPaddingBottom); 991 } 992 993 canvas.translate(mPaddingLeft, mPaddingTop); 994 995 if (mDrawMatrix != null) { 996 canvas.concat(mDrawMatrix); 997 } 998 mDrawable.draw(canvas); 999 canvas.restoreToCount(saveCount); 1000 } 1001 } 1002 1003 /** 1004 * <p>Return the offset of the widget's text baseline from the widget's top 1005 * boundary. </p> 1006 * 1007 * @return the offset of the baseline within the widget's bounds or -1 1008 * if baseline alignment is not supported. 1009 */ 1010 @Override 1011 @ViewDebug.ExportedProperty(category = "layout") 1012 public int getBaseline() { 1013 if (mBaselineAlignBottom) { 1014 return getMeasuredHeight(); 1015 } else { 1016 return mBaseline; 1017 } 1018 } 1019 1020 /** 1021 * <p>Set the offset of the widget's text baseline from the widget's top 1022 * boundary. This value is overridden by the {@link #setBaselineAlignBottom(boolean)} 1023 * property.</p> 1024 * 1025 * @param baseline The baseline to use, or -1 if none is to be provided. 1026 * 1027 * @see #setBaseline(int) 1028 * @attr ref android.R.styleable#ImageView_baseline 1029 */ 1030 public void setBaseline(int baseline) { 1031 if (mBaseline != baseline) { 1032 mBaseline = baseline; 1033 requestLayout(); 1034 } 1035 } 1036 1037 /** 1038 * Set whether to set the baseline of this view to the bottom of the view. 1039 * Setting this value overrides any calls to setBaseline. 1040 * 1041 * @param aligned If true, the image view will be baseline aligned with 1042 * based on its bottom edge. 1043 * 1044 * @attr ref android.R.styleable#ImageView_baselineAlignBottom 1045 */ 1046 public void setBaselineAlignBottom(boolean aligned) { 1047 if (mBaselineAlignBottom != aligned) { 1048 mBaselineAlignBottom = aligned; 1049 requestLayout(); 1050 } 1051 } 1052 1053 /** 1054 * Return whether this view's baseline will be considered the bottom of the view. 1055 * 1056 * @see #setBaselineAlignBottom(boolean) 1057 */ 1058 public boolean getBaselineAlignBottom() { 1059 return mBaselineAlignBottom; 1060 } 1061 1062 /** 1063 * Set a tinting option for the image. 1064 * 1065 * @param color Color tint to apply. 1066 * @param mode How to apply the color. The standard mode is 1067 * {@link PorterDuff.Mode#SRC_ATOP} 1068 * 1069 * @attr ref android.R.styleable#ImageView_tint 1070 */ 1071 public final void setColorFilter(int color, PorterDuff.Mode mode) { 1072 setColorFilter(new PorterDuffColorFilter(color, mode)); 1073 } 1074 1075 /** 1076 * Set a tinting option for the image. Assumes 1077 * {@link PorterDuff.Mode#SRC_ATOP} blending mode. 1078 * 1079 * @param color Color tint to apply. 1080 * @attr ref android.R.styleable#ImageView_tint 1081 */ 1082 @RemotableViewMethod 1083 public final void setColorFilter(int color) { 1084 setColorFilter(color, PorterDuff.Mode.SRC_ATOP); 1085 } 1086 1087 public final void clearColorFilter() { 1088 setColorFilter(null); 1089 } 1090 1091 /** 1092 * Returns the active color filter for this ImageView. 1093 * 1094 * @return the active color filter for this ImageView 1095 * 1096 * @see #setColorFilter(android.graphics.ColorFilter) 1097 */ 1098 public ColorFilter getColorFilter() { 1099 return mColorFilter; 1100 } 1101 1102 /** 1103 * Apply an arbitrary colorfilter to the image. 1104 * 1105 * @param cf the colorfilter to apply (may be null) 1106 * 1107 * @see #getColorFilter() 1108 */ 1109 public void setColorFilter(ColorFilter cf) { 1110 if (mColorFilter != cf) { 1111 mColorFilter = cf; 1112 mColorMod = true; 1113 applyColorMod(); 1114 invalidate(); 1115 } 1116 } 1117 1118 /** 1119 * Returns the alpha that will be applied to the drawable of this ImageView. 1120 * 1121 * @return the alpha that will be applied to the drawable of this ImageView 1122 * 1123 * @see #setImageAlpha(int) 1124 */ 1125 public int getImageAlpha() { 1126 return mAlpha; 1127 } 1128 1129 /** 1130 * Sets the alpha value that should be applied to the image. 1131 * 1132 * @param alpha the alpha value that should be applied to the image 1133 * 1134 * @see #getImageAlpha() 1135 */ 1136 @RemotableViewMethod 1137 public void setImageAlpha(int alpha) { 1138 setAlpha(alpha); 1139 } 1140 1141 /** 1142 * Sets the alpha value that should be applied to the image. 1143 * 1144 * @param alpha the alpha value that should be applied to the image 1145 * 1146 * @deprecated use #setImageAlpha(int) instead 1147 */ 1148 @Deprecated 1149 @RemotableViewMethod 1150 public void setAlpha(int alpha) { 1151 alpha &= 0xFF; // keep it legal 1152 if (mAlpha != alpha) { 1153 mAlpha = alpha; 1154 mColorMod = true; 1155 applyColorMod(); 1156 invalidate(); 1157 } 1158 } 1159 1160 private void applyColorMod() { 1161 // Only mutate and apply when modifications have occurred. This should 1162 // not reset the mColorMod flag, since these filters need to be 1163 // re-applied if the Drawable is changed. 1164 if (mDrawable != null && mColorMod) { 1165 mDrawable = mDrawable.mutate(); 1166 mDrawable.setColorFilter(mColorFilter); 1167 mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8); 1168 } 1169 } 1170 1171 @RemotableViewMethod 1172 @Override 1173 public void setVisibility(int visibility) { 1174 super.setVisibility(visibility); 1175 if (mDrawable != null) { 1176 mDrawable.setVisible(visibility == VISIBLE, false); 1177 } 1178 } 1179 1180 @Override 1181 protected void onAttachedToWindow() { 1182 super.onAttachedToWindow(); 1183 if (mDrawable != null) { 1184 mDrawable.setVisible(getVisibility() == VISIBLE, false); 1185 } 1186 } 1187 1188 @Override 1189 protected void onDetachedFromWindow() { 1190 super.onDetachedFromWindow(); 1191 if (mDrawable != null) { 1192 mDrawable.setVisible(false, false); 1193 } 1194 } 1195 1196 @Override 1197 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1198 super.onInitializeAccessibilityEvent(event); 1199 event.setClassName(ImageView.class.getName()); 1200 } 1201 1202 @Override 1203 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1204 super.onInitializeAccessibilityNodeInfo(info); 1205 info.setClassName(ImageView.class.getName()); 1206 } 1207} 1208