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