ImageView.java revision b4938201bad1050a5e3821d60ea43e71861cc733
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.util.AttributeSet; 34import android.util.Log; 35import android.view.RemotableViewMethod; 36import android.view.View; 37import android.view.accessibility.AccessibilityEvent; 38import android.view.accessibility.AccessibilityManager; 39import android.widget.RemoteViews.RemoteView; 40 41 42/** 43 * Displays an arbitrary image, such as an icon. The ImageView class 44 * can load images from various sources (such as resources or content 45 * providers), takes care of computing its measurement from the image so that 46 * it can be used in any layout manager, and provides various display options 47 * such as scaling and tinting. 48 * 49 * @attr ref android.R.styleable#ImageView_adjustViewBounds 50 * @attr ref android.R.styleable#ImageView_src 51 * @attr ref android.R.styleable#ImageView_maxWidth 52 * @attr ref android.R.styleable#ImageView_maxHeight 53 * @attr ref android.R.styleable#ImageView_tint 54 * @attr ref android.R.styleable#ImageView_scaleType 55 * @attr ref android.R.styleable#ImageView_cropToPadding 56 */ 57@RemoteView 58public class ImageView extends View { 59 // settable by the client 60 private Uri mUri; 61 private int mResource = 0; 62 private Matrix mMatrix; 63 private ScaleType mScaleType; 64 private boolean mHaveFrame = false; 65 private boolean mAdjustViewBounds = false; 66 private int mMaxWidth = Integer.MAX_VALUE; 67 private int mMaxHeight = Integer.MAX_VALUE; 68 69 // these are applied to the drawable 70 private ColorFilter mColorFilter; 71 private int mAlpha = 255; 72 private int mViewAlphaScale = 256; 73 private boolean mColorMod = false; 74 75 private Drawable mDrawable = null; 76 private int[] mState = null; 77 private boolean mMergeState = false; 78 private int mLevel = 0; 79 private int mDrawableWidth; 80 private int mDrawableHeight; 81 private Matrix mDrawMatrix = null; 82 83 // Avoid allocations... 84 private RectF mTempSrc = new RectF(); 85 private RectF mTempDst = new RectF(); 86 87 private boolean mCropToPadding; 88 89 private boolean mBaselineAligned = false; 90 91 private static final ScaleType[] sScaleTypeArray = { 92 ScaleType.MATRIX, 93 ScaleType.FIT_XY, 94 ScaleType.FIT_START, 95 ScaleType.FIT_CENTER, 96 ScaleType.FIT_END, 97 ScaleType.CENTER, 98 ScaleType.CENTER_CROP, 99 ScaleType.CENTER_INSIDE 100 }; 101 102 public ImageView(Context context) { 103 super(context); 104 initImageView(); 105 } 106 107 public ImageView(Context context, AttributeSet attrs) { 108 this(context, attrs, 0); 109 } 110 111 public ImageView(Context context, AttributeSet attrs, int defStyle) { 112 super(context, attrs, defStyle); 113 initImageView(); 114 115 TypedArray a = context.obtainStyledAttributes(attrs, 116 com.android.internal.R.styleable.ImageView, defStyle, 0); 117 118 Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src); 119 if (d != null) { 120 setImageDrawable(d); 121 } 122 123 mBaselineAligned = a.getBoolean( 124 com.android.internal.R.styleable.ImageView_baselineAlignBottom, false); 125 126 setAdjustViewBounds( 127 a.getBoolean(com.android.internal.R.styleable.ImageView_adjustViewBounds, 128 false)); 129 130 setMaxWidth(a.getDimensionPixelSize( 131 com.android.internal.R.styleable.ImageView_maxWidth, Integer.MAX_VALUE)); 132 133 setMaxHeight(a.getDimensionPixelSize( 134 com.android.internal.R.styleable.ImageView_maxHeight, Integer.MAX_VALUE)); 135 136 int index = a.getInt(com.android.internal.R.styleable.ImageView_scaleType, -1); 137 if (index >= 0) { 138 setScaleType(sScaleTypeArray[index]); 139 } 140 141 int tint = a.getInt(com.android.internal.R.styleable.ImageView_tint, 0); 142 if (tint != 0) { 143 setColorFilter(tint); 144 } 145 146 mCropToPadding = a.getBoolean( 147 com.android.internal.R.styleable.ImageView_cropToPadding, false); 148 149 a.recycle(); 150 151 //need inflate syntax/reader for matrix 152 } 153 154 private void initImageView() { 155 mMatrix = new Matrix(); 156 mScaleType = ScaleType.FIT_CENTER; 157 } 158 159 @Override 160 protected boolean verifyDrawable(Drawable dr) { 161 return mDrawable == dr || super.verifyDrawable(dr); 162 } 163 164 @Override 165 public void invalidateDrawable(Drawable dr) { 166 if (dr == mDrawable) { 167 /* we invalidate the whole view in this case because it's very 168 * hard to know where the drawable actually is. This is made 169 * complicated because of the offsets and transformations that 170 * can be applied. In theory we could get the drawable's bounds 171 * and run them through the transformation and offsets, but this 172 * is probably not worth the effort. 173 */ 174 invalidate(); 175 } else { 176 super.invalidateDrawable(dr); 177 } 178 } 179 180 @Override 181 protected boolean onSetAlpha(int alpha) { 182 if (getBackground() == null) { 183 int scale = alpha + (alpha >> 7); 184 if (mViewAlphaScale != scale) { 185 mViewAlphaScale = scale; 186 mColorMod = true; 187 applyColorMod(); 188 } 189 return true; 190 } 191 return false; 192 } 193 194 /** 195 * Set this to true if you want the ImageView to adjust its bounds 196 * to preserve the aspect ratio of its drawable. 197 * @param adjustViewBounds Whether to adjust the bounds of this view 198 * to presrve the original aspect ratio of the drawable 199 * 200 * @attr ref android.R.styleable#ImageView_adjustViewBounds 201 */ 202 @android.view.RemotableViewMethod 203 public void setAdjustViewBounds(boolean adjustViewBounds) { 204 mAdjustViewBounds = adjustViewBounds; 205 if (adjustViewBounds) { 206 setScaleType(ScaleType.FIT_CENTER); 207 } 208 } 209 210 /** 211 * An optional argument to supply a maximum width for this view. Only valid if 212 * {@link #setAdjustViewBounds} has been set to true. To set an image to be a maximum of 100 x 213 * 100 while preserving the original aspect ratio, do the following: 1) set adjustViewBounds to 214 * true 2) set maxWidth and maxHeight to 100 3) set the height and width layout params to 215 * WRAP_CONTENT. 216 * 217 * <p> 218 * Note that this view could be still smaller than 100 x 100 using this approach if the original 219 * image is small. To set an image to a fixed size, specify that size in the layout params and 220 * then use {@link #setScaleType} to determine how to fit the image within the bounds. 221 * </p> 222 * 223 * @param maxWidth maximum width for this view 224 * 225 * @attr ref android.R.styleable#ImageView_maxWidth 226 */ 227 @android.view.RemotableViewMethod 228 public void setMaxWidth(int maxWidth) { 229 mMaxWidth = maxWidth; 230 } 231 232 /** 233 * An optional argument to supply a maximum height for this view. Only valid if 234 * {@link #setAdjustViewBounds} has been set to true. To set an image to be a maximum of 100 x 235 * 100 while preserving the original aspect ratio, do the following: 1) set adjustViewBounds to 236 * true 2) set maxWidth and maxHeight to 100 3) set the height and width layout params to 237 * WRAP_CONTENT. 238 * 239 * <p> 240 * Note that this view could be still smaller than 100 x 100 using this approach if the original 241 * image is small. To set an image to a fixed size, specify that size in the layout params and 242 * then use {@link #setScaleType} to determine how to fit the image within the bounds. 243 * </p> 244 * 245 * @param maxHeight maximum height for this view 246 * 247 * @attr ref android.R.styleable#ImageView_maxHeight 248 */ 249 @android.view.RemotableViewMethod 250 public void setMaxHeight(int maxHeight) { 251 mMaxHeight = maxHeight; 252 } 253 254 /** Return the view's drawable, or null if no drawable has been 255 assigned. 256 */ 257 public Drawable getDrawable() { 258 return mDrawable; 259 } 260 261 /** 262 * Sets a drawable as the content of this ImageView. 263 * 264 * @param resId the resource identifier of the the drawable 265 * 266 * @attr ref android.R.styleable#ImageView_src 267 */ 268 @android.view.RemotableViewMethod 269 public void setImageResource(int resId) { 270 if (mUri != null || mResource != resId) { 271 updateDrawable(null); 272 mResource = resId; 273 mUri = null; 274 resolveUri(); 275 requestLayout(); 276 invalidate(); 277 } 278 } 279 280 /** 281 * Sets the content of this ImageView to the specified Uri. 282 * 283 * @param uri The Uri of an image 284 */ 285 @android.view.RemotableViewMethod 286 public void setImageURI(Uri uri) { 287 if (mResource != 0 || 288 (mUri != uri && 289 (uri == null || mUri == null || !uri.equals(mUri)))) { 290 updateDrawable(null); 291 mResource = 0; 292 mUri = uri; 293 resolveUri(); 294 requestLayout(); 295 invalidate(); 296 } 297 } 298 299 300 /** 301 * Sets a drawable as the content of this ImageView. 302 * 303 * @param drawable The drawable to set 304 */ 305 public void setImageDrawable(Drawable drawable) { 306 if (mDrawable != drawable) { 307 mResource = 0; 308 mUri = null; 309 updateDrawable(drawable); 310 requestLayout(); 311 invalidate(); 312 } 313 } 314 315 /** 316 * Sets a Bitmap as the content of this ImageView. 317 * 318 * @param bm The bitmap to set 319 */ 320 @android.view.RemotableViewMethod 321 public void setImageBitmap(Bitmap bm) { 322 // if this is used frequently, may handle bitmaps explicitly 323 // to reduce the intermediate drawable object 324 setImageDrawable(new BitmapDrawable(mContext.getResources(), bm)); 325 } 326 327 public void setImageState(int[] state, boolean merge) { 328 mState = state; 329 mMergeState = merge; 330 if (mDrawable != null) { 331 refreshDrawableState(); 332 resizeFromDrawable(); 333 } 334 } 335 336 @Override 337 public void setSelected(boolean selected) { 338 super.setSelected(selected); 339 resizeFromDrawable(); 340 } 341 342 /** 343 * Sets the image level, when it is constructed from a 344 * {@link android.graphics.drawable.LevelListDrawable}. 345 * 346 * @param level The new level for the image. 347 */ 348 @android.view.RemotableViewMethod 349 public void setImageLevel(int level) { 350 mLevel = level; 351 if (mDrawable != null) { 352 mDrawable.setLevel(level); 353 resizeFromDrawable(); 354 } 355 } 356 357 /** 358 * Options for scaling the bounds of an image to the bounds of this view. 359 */ 360 public enum ScaleType { 361 /** 362 * Scale using the image matrix when drawing. The image matrix can be set using 363 * {@link ImageView#setImageMatrix(Matrix)}. From XML, use this syntax: 364 * <code>android:scaleType="matrix"</code>. 365 */ 366 MATRIX (0), 367 /** 368 * Scale the image using {@link Matrix.ScaleToFit#FILL}. 369 * From XML, use this syntax: <code>android:scaleType="fitXY"</code>. 370 */ 371 FIT_XY (1), 372 /** 373 * Scale the image using {@link Matrix.ScaleToFit#START}. 374 * From XML, use this syntax: <code>android:scaleType="fitStart"</code>. 375 */ 376 FIT_START (2), 377 /** 378 * Scale the image using {@link Matrix.ScaleToFit#CENTER}. 379 * From XML, use this syntax: 380 * <code>android:scaleType="fitCenter"</code>. 381 */ 382 FIT_CENTER (3), 383 /** 384 * Scale the image using {@link Matrix.ScaleToFit#END}. 385 * From XML, use this syntax: <code>android:scaleType="fitEnd"</code>. 386 */ 387 FIT_END (4), 388 /** 389 * Center the image in the view, but perform no scaling. 390 * From XML, use this syntax: <code>android:scaleType="center"</code>. 391 */ 392 CENTER (5), 393 /** 394 * Scale the image uniformly (maintain the image's aspect ratio) so 395 * that both dimensions (width and height) of the image will be equal 396 * to or larger than the corresponding dimension of the view 397 * (minus padding). The image is then centered in the view. 398 * From XML, use this syntax: <code>android:scaleType="centerCrop"</code>. 399 */ 400 CENTER_CROP (6), 401 /** 402 * Scale the image uniformly (maintain the image's aspect ratio) so 403 * that both dimensions (width and height) of the image will be equal 404 * to or less than the corresponding dimension of the view 405 * (minus padding). The image is then centered in the view. 406 * From XML, use this syntax: <code>android:scaleType="centerInside"</code>. 407 */ 408 CENTER_INSIDE (7); 409 410 ScaleType(int ni) { 411 nativeInt = ni; 412 } 413 final int nativeInt; 414 } 415 416 /** 417 * Controls how the image should be resized or moved to match the size 418 * of this ImageView. 419 * 420 * @param scaleType The desired scaling mode. 421 * 422 * @attr ref android.R.styleable#ImageView_scaleType 423 */ 424 public void setScaleType(ScaleType scaleType) { 425 if (scaleType == null) { 426 throw new NullPointerException(); 427 } 428 429 if (mScaleType != scaleType) { 430 mScaleType = scaleType; 431 432 setWillNotCacheDrawing(mScaleType == ScaleType.CENTER); 433 434 requestLayout(); 435 invalidate(); 436 } 437 } 438 439 /** 440 * Return the current scale type in use by this ImageView. 441 * 442 * @see ImageView.ScaleType 443 * 444 * @attr ref android.R.styleable#ImageView_scaleType 445 */ 446 public ScaleType getScaleType() { 447 return mScaleType; 448 } 449 450 /** Return the view's optional matrix. This is applied to the 451 view's drawable when it is drawn. If there is not matrix, 452 this method will return null. 453 Do not change this matrix in place. If you want a different matrix 454 applied to the drawable, be sure to call setImageMatrix(). 455 */ 456 public Matrix getImageMatrix() { 457 return mMatrix; 458 } 459 460 public void setImageMatrix(Matrix matrix) { 461 // collaps null and identity to just null 462 if (matrix != null && matrix.isIdentity()) { 463 matrix = null; 464 } 465 466 // don't invalidate unless we're actually changing our matrix 467 if (matrix == null && !mMatrix.isIdentity() || 468 matrix != null && !mMatrix.equals(matrix)) { 469 mMatrix.set(matrix); 470 configureBounds(); 471 invalidate(); 472 } 473 } 474 475 private void resolveUri() { 476 if (mDrawable != null) { 477 return; 478 } 479 480 Resources rsrc = getResources(); 481 if (rsrc == null) { 482 return; 483 } 484 485 Drawable d = null; 486 487 if (mResource != 0) { 488 try { 489 d = rsrc.getDrawable(mResource); 490 } catch (Exception e) { 491 Log.w("ImageView", "Unable to find resource: " + mResource, e); 492 // Don't try again. 493 mUri = null; 494 } 495 } else if (mUri != null) { 496 String scheme = mUri.getScheme(); 497 if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) { 498 try { 499 // Load drawable through Resources, to get the source density information 500 ContentResolver.OpenResourceIdResult r = 501 mContext.getContentResolver().getResourceId(mUri); 502 d = r.r.getDrawable(r.id); 503 } catch (Exception e) { 504 Log.w("ImageView", "Unable to open content: " + mUri, e); 505 } 506 } else if (ContentResolver.SCHEME_CONTENT.equals(scheme) 507 || ContentResolver.SCHEME_FILE.equals(scheme)) { 508 try { 509 d = Drawable.createFromStream( 510 mContext.getContentResolver().openInputStream(mUri), 511 null); 512 } catch (Exception e) { 513 Log.w("ImageView", "Unable to open content: " + mUri, e); 514 } 515 } else { 516 d = Drawable.createFromPath(mUri.toString()); 517 } 518 519 if (d == null) { 520 System.out.println("resolveUri failed on bad bitmap uri: " 521 + mUri); 522 // Don't try again. 523 mUri = null; 524 } 525 } else { 526 return; 527 } 528 529 updateDrawable(d); 530 } 531 532 @Override 533 public int[] onCreateDrawableState(int extraSpace) { 534 if (mState == null) { 535 return super.onCreateDrawableState(extraSpace); 536 } else if (!mMergeState) { 537 return mState; 538 } else { 539 return mergeDrawableStates( 540 super.onCreateDrawableState(extraSpace + mState.length), mState); 541 } 542 } 543 544 private void updateDrawable(Drawable d) { 545 if (mDrawable != null) { 546 mDrawable.setCallback(null); 547 unscheduleDrawable(mDrawable); 548 } 549 mDrawable = d; 550 if (d != null) { 551 d.setCallback(this); 552 if (d.isStateful()) { 553 d.setState(getDrawableState()); 554 } 555 d.setLevel(mLevel); 556 mDrawableWidth = d.getIntrinsicWidth(); 557 mDrawableHeight = d.getIntrinsicHeight(); 558 applyColorMod(); 559 configureBounds(); 560 } 561 } 562 563 private void resizeFromDrawable() { 564 Drawable d = mDrawable; 565 if (d != null) { 566 int w = d.getIntrinsicWidth(); 567 if (w < 0) w = mDrawableWidth; 568 int h = d.getIntrinsicHeight(); 569 if (h < 0) h = mDrawableHeight; 570 if (w != mDrawableWidth || h != mDrawableHeight) { 571 mDrawableWidth = w; 572 mDrawableHeight = h; 573 requestLayout(); 574 } 575 } 576 } 577 578 private static final Matrix.ScaleToFit[] sS2FArray = { 579 Matrix.ScaleToFit.FILL, 580 Matrix.ScaleToFit.START, 581 Matrix.ScaleToFit.CENTER, 582 Matrix.ScaleToFit.END 583 }; 584 585 private static Matrix.ScaleToFit scaleTypeToScaleToFit(ScaleType st) { 586 // ScaleToFit enum to their corresponding Matrix.ScaleToFit values 587 return sS2FArray[st.nativeInt - 1]; 588 } 589 590 @Override 591 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 592 resolveUri(); 593 int w; 594 int h; 595 596 // Desired aspect ratio of the view's contents (not including padding) 597 float desiredAspect = 0.0f; 598 599 // We are allowed to change the view's width 600 boolean resizeWidth = false; 601 602 // We are allowed to change the view's height 603 boolean resizeHeight = false; 604 605 if (mDrawable == null) { 606 // If no drawable, its intrinsic size is 0. 607 mDrawableWidth = -1; 608 mDrawableHeight = -1; 609 w = h = 0; 610 } else { 611 w = mDrawableWidth; 612 h = mDrawableHeight; 613 if (w <= 0) w = 1; 614 if (h <= 0) h = 1; 615 616 // We are supposed to adjust view bounds to match the aspect 617 // ratio of our drawable. See if that is possible. 618 if (mAdjustViewBounds) { 619 620 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 621 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 622 623 resizeWidth = widthSpecMode != MeasureSpec.EXACTLY; 624 resizeHeight = heightSpecMode != MeasureSpec.EXACTLY; 625 626 desiredAspect = (float)w/(float)h; 627 } 628 } 629 630 int pleft = mPaddingLeft; 631 int pright = mPaddingRight; 632 int ptop = mPaddingTop; 633 int pbottom = mPaddingBottom; 634 635 int widthSize; 636 int heightSize; 637 638 if (resizeWidth || resizeHeight) { 639 /* If we get here, it means we want to resize to match the 640 drawables aspect ratio, and we have the freedom to change at 641 least one dimension. 642 */ 643 644 // Get the max possible width given our constraints 645 widthSize = resolveAdjustedSize(w + pleft + pright, 646 mMaxWidth, widthMeasureSpec); 647 648 // Get the max possible height given our constraints 649 heightSize = resolveAdjustedSize(h + ptop + pbottom, 650 mMaxHeight, heightMeasureSpec); 651 652 if (desiredAspect != 0.0f) { 653 // See what our actual aspect ratio is 654 float actualAspect = (float)(widthSize - pleft - pright) / 655 (heightSize - ptop - pbottom); 656 657 if (Math.abs(actualAspect - desiredAspect) > 0.0000001) { 658 659 boolean done = false; 660 661 // Try adjusting width to be proportional to height 662 if (resizeWidth) { 663 int newWidth = (int)(desiredAspect * 664 (heightSize - ptop - pbottom)) 665 + pleft + pright; 666 if (newWidth <= widthSize) { 667 widthSize = newWidth; 668 done = true; 669 } 670 } 671 672 // Try adjusting height to be proportional to width 673 if (!done && resizeHeight) { 674 int newHeight = (int)((widthSize - pleft - pright) 675 / desiredAspect) + ptop + pbottom; 676 if (newHeight <= heightSize) { 677 heightSize = newHeight; 678 } 679 } 680 } 681 } 682 } else { 683 /* We are either don't want to preserve the drawables aspect ratio, 684 or we are not allowed to change view dimensions. Just measure in 685 the normal way. 686 */ 687 w += pleft + pright; 688 h += ptop + pbottom; 689 690 w = Math.max(w, getSuggestedMinimumWidth()); 691 h = Math.max(h, getSuggestedMinimumHeight()); 692 693 widthSize = resolveSize(w, widthMeasureSpec); 694 heightSize = resolveSize(h, heightMeasureSpec); 695 } 696 697 setMeasuredDimension(widthSize, heightSize); 698 } 699 700 private int resolveAdjustedSize(int desiredSize, int maxSize, 701 int measureSpec) { 702 int result = desiredSize; 703 int specMode = MeasureSpec.getMode(measureSpec); 704 int specSize = MeasureSpec.getSize(measureSpec); 705 switch (specMode) { 706 case MeasureSpec.UNSPECIFIED: 707 /* Parent says we can be as big as we want. Just don't be larger 708 than max size imposed on ourselves. 709 */ 710 result = Math.min(desiredSize, maxSize); 711 break; 712 case MeasureSpec.AT_MOST: 713 // Parent says we can be as big as we want, up to specSize. 714 // Don't be larger than specSize, and don't be larger than 715 // the max size imposed on ourselves. 716 result = Math.min(Math.min(desiredSize, specSize), maxSize); 717 break; 718 case MeasureSpec.EXACTLY: 719 // No choice. Do what we are told. 720 result = specSize; 721 break; 722 } 723 return result; 724 } 725 726 @Override 727 protected boolean setFrame(int l, int t, int r, int b) { 728 boolean changed = super.setFrame(l, t, r, b); 729 mHaveFrame = true; 730 configureBounds(); 731 return changed; 732 } 733 734 private void configureBounds() { 735 if (mDrawable == null || !mHaveFrame) { 736 return; 737 } 738 739 int dwidth = mDrawableWidth; 740 int dheight = mDrawableHeight; 741 742 int vwidth = getWidth() - mPaddingLeft - mPaddingRight; 743 int vheight = getHeight() - mPaddingTop - mPaddingBottom; 744 745 boolean fits = (dwidth < 0 || vwidth == dwidth) && 746 (dheight < 0 || vheight == dheight); 747 748 if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) { 749 /* If the drawable has no intrinsic size, or we're told to 750 scaletofit, then we just fill our entire view. 751 */ 752 mDrawable.setBounds(0, 0, vwidth, vheight); 753 mDrawMatrix = null; 754 } else { 755 // We need to do the scaling ourself, so have the drawable 756 // use its native size. 757 mDrawable.setBounds(0, 0, dwidth, dheight); 758 759 if (ScaleType.MATRIX == mScaleType) { 760 // Use the specified matrix as-is. 761 if (mMatrix.isIdentity()) { 762 mDrawMatrix = null; 763 } else { 764 mDrawMatrix = mMatrix; 765 } 766 } else if (fits) { 767 // The bitmap fits exactly, no transform needed. 768 mDrawMatrix = null; 769 } else if (ScaleType.CENTER == mScaleType) { 770 // Center bitmap in view, no scaling. 771 mDrawMatrix = mMatrix; 772 mDrawMatrix.setTranslate((int) ((vwidth - dwidth) * 0.5f + 0.5f), 773 (int) ((vheight - dheight) * 0.5f + 0.5f)); 774 } else if (ScaleType.CENTER_CROP == mScaleType) { 775 mDrawMatrix = mMatrix; 776 777 float scale; 778 float dx = 0, dy = 0; 779 780 if (dwidth * vheight > vwidth * dheight) { 781 scale = (float) vheight / (float) dheight; 782 dx = (vwidth - dwidth * scale) * 0.5f; 783 } else { 784 scale = (float) vwidth / (float) dwidth; 785 dy = (vheight - dheight * scale) * 0.5f; 786 } 787 788 mDrawMatrix.setScale(scale, scale); 789 mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); 790 } else if (ScaleType.CENTER_INSIDE == mScaleType) { 791 mDrawMatrix = mMatrix; 792 float scale; 793 float dx; 794 float dy; 795 796 if (dwidth <= vwidth && dheight <= vheight) { 797 scale = 1.0f; 798 } else { 799 scale = Math.min((float) vwidth / (float) dwidth, 800 (float) vheight / (float) dheight); 801 } 802 803 dx = (int) ((vwidth - dwidth * scale) * 0.5f + 0.5f); 804 dy = (int) ((vheight - dheight * scale) * 0.5f + 0.5f); 805 806 mDrawMatrix.setScale(scale, scale); 807 mDrawMatrix.postTranslate(dx, dy); 808 } else { 809 // Generate the required transform. 810 mTempSrc.set(0, 0, dwidth, dheight); 811 mTempDst.set(0, 0, vwidth, vheight); 812 813 mDrawMatrix = mMatrix; 814 mDrawMatrix.setRectToRect(mTempSrc, mTempDst, 815 scaleTypeToScaleToFit(mScaleType)); 816 } 817 } 818 } 819 820 @Override 821 protected void drawableStateChanged() { 822 super.drawableStateChanged(); 823 Drawable d = mDrawable; 824 if (d != null && d.isStateful()) { 825 d.setState(getDrawableState()); 826 } 827 } 828 829 @Override 830 protected void onDraw(Canvas canvas) { 831 super.onDraw(canvas); 832 833 if (mDrawable == null) { 834 return; // couldn't resolve the URI 835 } 836 837 if (mDrawableWidth == 0 || mDrawableHeight == 0) { 838 return; // nothing to draw (empty bounds) 839 } 840 841 if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) { 842 mDrawable.draw(canvas); 843 } else { 844 int saveCount = canvas.getSaveCount(); 845 canvas.save(); 846 847 if (mCropToPadding) { 848 final int scrollX = mScrollX; 849 final int scrollY = mScrollY; 850 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop, 851 scrollX + mRight - mLeft - mPaddingRight, 852 scrollY + mBottom - mTop - mPaddingBottom); 853 } 854 855 canvas.translate(mPaddingLeft, mPaddingTop); 856 857 if (mDrawMatrix != null) { 858 canvas.concat(mDrawMatrix); 859 } 860 mDrawable.draw(canvas); 861 canvas.restoreToCount(saveCount); 862 } 863 } 864 865 @Override 866 public int getBaseline() { 867 return mBaselineAligned ? getMeasuredHeight() : -1; 868 } 869 870 /** 871 * Set a tinting option for the image. 872 * 873 * @param color Color tint to apply. 874 * @param mode How to apply the color. The standard mode is 875 * {@link PorterDuff.Mode#SRC_ATOP} 876 * 877 * @attr ref android.R.styleable#ImageView_tint 878 */ 879 public final void setColorFilter(int color, PorterDuff.Mode mode) { 880 setColorFilter(new PorterDuffColorFilter(color, mode)); 881 } 882 883 /** 884 * Set a tinting option for the image. Assumes 885 * {@link PorterDuff.Mode#SRC_ATOP} blending mode. 886 * 887 * @param color Color tint to apply. 888 * @attr ref android.R.styleable#ImageView_tint 889 */ 890 @RemotableViewMethod 891 public final void setColorFilter(int color) { 892 setColorFilter(color, PorterDuff.Mode.SRC_ATOP); 893 } 894 895 public final void clearColorFilter() { 896 setColorFilter(null); 897 } 898 899 /** 900 * Apply an arbitrary colorfilter to the image. 901 * 902 * @param cf the colorfilter to apply (may be null) 903 */ 904 public void setColorFilter(ColorFilter cf) { 905 if (mColorFilter != cf) { 906 mColorFilter = cf; 907 mColorMod = true; 908 applyColorMod(); 909 invalidate(); 910 } 911 } 912 913 @RemotableViewMethod 914 public void setAlpha(int alpha) { 915 alpha &= 0xFF; // keep it legal 916 if (mAlpha != alpha) { 917 mAlpha = alpha; 918 mColorMod = true; 919 applyColorMod(); 920 invalidate(); 921 } 922 } 923 924 private void applyColorMod() { 925 // Only mutate and apply when modifications have occurred. This should 926 // not reset the mColorMod flag, since these filters need to be 927 // re-applied if the Drawable is changed. 928 if (mDrawable != null && mColorMod) { 929 mDrawable = mDrawable.mutate(); 930 mDrawable.setColorFilter(mColorFilter); 931 mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8); 932 } 933 } 934} 935