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