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