/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.widget;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.RemotableViewMethod;
import android.view.View;
import android.view.ViewDebug;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
/**
* 显示任意图像,例如图标. ImageView 类可以加载各种来源的图片(如资源或图片库),
* 需要计算图像的尺寸,以便它可以在其他布局中使用,并提供例如缩放和着色(渲染) 各种显示选项。
*
* @attr ref android.R.styleable#ImageView_adjustViewBounds
* @attr ref android.R.styleable#ImageView_src
* @attr ref android.R.styleable#ImageView_maxWidth
* @attr ref android.R.styleable#ImageView_maxHeight
* @attr ref android.R.styleable#ImageView_tint
* @attr ref android.R.styleable#ImageView_scaleType
* @attr ref android.R.styleable#ImageView_cropToPadding
*/
@RemoteView
public class ImageView extends View {
// settable by the client
private Uri mUri;
private int mResource = 0;
private Matrix mMatrix;
private ScaleType mScaleType;
private boolean mHaveFrame = false;
private boolean mAdjustViewBounds = false;
private int mMaxWidth = Integer.MAX_VALUE;
private int mMaxHeight = Integer.MAX_VALUE;
// these are applied to the drawable
private ColorFilter mColorFilter;
private int mAlpha = 255;
private int mViewAlphaScale = 256;
private boolean mColorMod = false;
private Drawable mDrawable = null;
private int[] mState = null;
private boolean mMergeState = false;
private int mLevel = 0;
private int mDrawableWidth;
private int mDrawableHeight;
private Matrix mDrawMatrix = null;
// Avoid allocations...
private RectF mTempSrc = new RectF();
private RectF mTempDst = new RectF();
private boolean mCropToPadding;
private int mBaseline = -1;
private boolean mBaselineAlignBottom = false;
private static final ScaleType[] sScaleTypeArray = {
ScaleType.MATRIX,
ScaleType.FIT_XY,
ScaleType.FIT_START,
ScaleType.FIT_CENTER,
ScaleType.FIT_END,
ScaleType.CENTER,
ScaleType.CENTER_CROP,
ScaleType.CENTER_INSIDE
};
public ImageView(Context context) {
super(context);
initImageView();
}
public ImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initImageView();
TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.ImageView, defStyle, 0);
Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src);
if (d != null) {
setImageDrawable(d);
}
mBaselineAlignBottom = a.getBoolean(
com.android.internal.R.styleable.ImageView_baselineAlignBottom, false);
mBaseline = a.getDimensionPixelSize(
com.android.internal.R.styleable.ImageView_baseline, -1);
setAdjustViewBounds(
a.getBoolean(com.android.internal.R.styleable.ImageView_adjustViewBounds,
false));
setMaxWidth(a.getDimensionPixelSize(
com.android.internal.R.styleable.ImageView_maxWidth, Integer.MAX_VALUE));
setMaxHeight(a.getDimensionPixelSize(
com.android.internal.R.styleable.ImageView_maxHeight, Integer.MAX_VALUE));
int index = a.getInt(com.android.internal.R.styleable.ImageView_scaleType, -1);
if (index >= 0) {
setScaleType(sScaleTypeArray[index]);
}
int tint = a.getInt(com.android.internal.R.styleable.ImageView_tint, 0);
if (tint != 0) {
setColorFilter(tint);
}
int alpha = a.getInt(com.android.internal.R.styleable.ImageView_drawableAlpha, 255);
if (alpha != 255) {
setAlpha(alpha);
}
mCropToPadding = a.getBoolean(
com.android.internal.R.styleable.ImageView_cropToPadding, false);
a.recycle();
//need inflate syntax/reader for matrix
}
private void initImageView() {
mMatrix = new Matrix();
mScaleType = ScaleType.FIT_CENTER;
}
@Override
protected boolean verifyDrawable(Drawable dr) {
return mDrawable == dr || super.verifyDrawable(dr);
}
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
if (mDrawable != null) mDrawable.jumpToCurrentState();
}
@Override
public void invalidateDrawable(Drawable dr) {
if (dr == mDrawable) {
/* we invalidate the whole view in this case because it's very
* hard to know where the drawable actually is. This is made
* complicated because of the offsets and transformations that
* can be applied. In theory we could get the drawable's bounds
* and run them through the transformation and offsets, but this
* is probably not worth the effort.
*/
invalidate();
} else {
super.invalidateDrawable(dr);
}
}
@Override
public boolean hasOverlappingRendering() {
return (getBackground() != null);
}
@Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
super.onPopulateAccessibilityEvent(event);
CharSequence contentDescription = getContentDescription();
if (!TextUtils.isEmpty(contentDescription)) {
event.getText().add(contentDescription);
}
}
/**
* True when ImageView is adjusting its bounds
* to preserve the aspect ratio of its drawable
*
* @return whether to adjust the bounds of this view
* to presrve the original aspect ratio of the drawable
*
* @see #setAdjustViewBounds(boolean)
*
* @attr ref android.R.styleable#ImageView_adjustViewBounds
*/
public boolean getAdjustViewBounds() {
return mAdjustViewBounds;
}
/**
* 当你需要在 ImageView 调整边框时保持可绘制对象的比例时,将该值设为真。
*
* @param adjustViewBounds 是否调整边框,以保持可绘制对象的原始比例。
*
* @see #getAdjustViewBounds()
*
* @attr ref android.R.styleable#ImageView_adjustViewBounds
*/
@android.view.RemotableViewMethod
public void setAdjustViewBounds(boolean adjustViewBounds) {
mAdjustViewBounds = adjustViewBounds;
if (adjustViewBounds) {
setScaleType(ScaleType.FIT_CENTER);
}
}
/**
* The maximum width of this view.
*
* @return The maximum width of this view
*
* @see #setMaxWidth(int)
*
* @attr ref android.R.styleable#ImageView_maxWidth
*/
public int getMaxWidth() {
return mMaxWidth;
}
/**
* 用于设置该视图支持的最大宽度的可选参数. 只有 {@link #setAdjustViewBounds(boolean)} 为真时有效。
* 要设置图像最大尺寸为 100×100,并保持原始比率,做法如下:1) 设置 adjustViewBounds 为真;
* 2) 设置 maxWidth 和 maxHeight 为 100; 3) 设置宽、高的布局参数为 WRAP_CONTENT。
*
* <p>
* 注意,如果原始图像较小,即使设置了该参数,图像仍然要比 100×100 小。如果要设置图片为 固定大小,
* 需要在布局参数中指定大小,并使用 {@link #setScaleType(android.widget.ImageView.ScaleType)}
* 函数来检测,如何 将其调整到适当的大小。
* </p>
*
* @param maxWidth 该视图的最大宽度。
*
* @see #getMaxWidth()
*
* @attr ref android.R.styleable#ImageView_maxWidth
*/
@android.view.RemotableViewMethod
public void setMaxWidth(int maxWidth) {
mMaxWidth = maxWidth;
}
/**
* The maximum height of this view.
*
* @return The maximum height of this view
*
* @see #setMaxHeight(int)
*
* @attr ref android.R.styleable#ImageView_maxHeight
*/
public int getMaxHeight() {
return mMaxHeight;
}
/**
* 用于设置该视图支持的最大高度的可选参数。只有 {@link #setAdjustViewBounds(boolean)} 为真时有效。
* 要设置图像最大尺寸为 100×100,并保持原始比率,做法如下: 1) 设置 adjustViewBounds 为真;
* 2) 设置 maxWidth 和 maxHeight 为 100; 3) 设置宽、高的布局参数为 WRAP_CONTENT。
*
* <p>
* 注意,如果原始图像较小,即使设置了该参数,图像仍然要比 100×100 小。如果要设置图片为 固定大小,
* 需要在布局参数中指定大小,并使用 {@link #setScaleType(android.widget.ImageView.ScaleType)}
* 函数来检测,如何 将其调整到适当的大小。
* </p>
*
* @param maxHeight 该视图的最大高度。
*
* @see #getMaxHeight()
*
* @attr ref android.R.styleable#ImageView_maxHeight
*/
@android.view.RemotableViewMethod
public void setMaxHeight(int maxHeight) {
mMaxHeight = maxHeight;
}
/**
* 返回视图的可绘制对象;如果没有关联可绘制对象,返回空。
*/
public Drawable getDrawable() {
return mDrawable;
}
/**
* 通过资源ID设置可绘制对象为该 ImageView 显示的内容。
*
* <p class="note">该操作读取位图,并在 UI 线程中解码,因此可能导致反应迟缓。 如果反应迟缓,
* 可以考虑用 {@link #setImageDrawable(android.graphics.drawable.Drawable)}、
* {@link #setImageBitmap(android.graphics.Bitmap)}
* 或者 {@link android.graphics.BitmapFactory} 代替。</p>
*
* @param resId 可绘制对象的资源标识。
*
* @attr ref android.R.styleable#ImageView_src
*/
@android.view.RemotableViewMethod
public void setImageResource(int resId) {
if (mUri != null || mResource != resId) {
updateDrawable(null);
mResource = resId;
mUri = null;
final int oldWidth = mDrawableWidth;
final int oldHeight = mDrawableHeight;
resolveUri();
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
invalidate();
}
}
/**
* 设置指定的 URI 为该 ImageView 显示的内容。
*
* <p class="note">该操作读取位图,并在 UI 线程中解码,因此可能导致反应迟缓。 如果反应迟缓,
* 可以考虑用 {@link #setImageDrawable(android.graphics.drawable.Drawable)}、
* {@link #setImageBitmap(android.graphics.Bitmap)}
* 或者 {@link android.graphics.BitmapFactory} 代替。</p>
*
* @param uri The Uri of an image
*/
@android.view.RemotableViewMethod
public void setImageURI(Uri uri) {
if (mResource != 0 ||
(mUri != uri &&
(uri == null || mUri == null || !uri.equals(mUri)))) {
updateDrawable(null);
mResource = 0;
mUri = uri;
final int oldWidth = mDrawableWidth;
final int oldHeight = mDrawableHeight;
resolveUri();
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
invalidate();
}
}
/**
* 设置可绘制对象为该 ImageView 显示的内容。
*
* @param drawable 设置的可绘制对象。
*/
public void setImageDrawable(Drawable drawable) {
if (mDrawable != drawable) {
mResource = 0;
mUri = null;
final int oldWidth = mDrawableWidth;
final int oldHeight = mDrawableHeight;
updateDrawable(drawable);
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
invalidate();
}
}
/**
* 设置位图作为该 ImageView 的内容。
*
* @param bm 设置的位图。
*/
@android.view.RemotableViewMethod
public void setImageBitmap(Bitmap bm) {
// if this is used frequently, may handle bitmaps explicitly
// to reduce the intermediate drawable object
setImageDrawable(new BitmapDrawable(mContext.getResources(), bm));
}
public void setImageState(int[] state, boolean merge) {
mState = state;
mMergeState = merge;
if (mDrawable != null) {
refreshDrawableState();
resizeFromDrawable();
}
}
@Override
public void setSelected(boolean selected) {
super.setSelected(selected);
resizeFromDrawable();
}
/**
* 设置图片的等级,当图片来自于 {@link android.graphics.drawable.LevelListDrawable} 时。
*
* @param level 图片的新的等级。
*/
@android.view.RemotableViewMethod
public void setImageLevel(int level) {
mLevel = level;
if (mDrawable != null) {
mDrawable.setLevel(level);
resizeFromDrawable();
}
}
/**
* 将图片边界缩放,以适应视图边界时的可选项.
*/
public enum ScaleType {
/**
* 绘制时,使用图像矩阵方式缩放。图像矩阵可以通过 {@link ImageView#setImageMatrix(Matrix)} 设置。
* 在 XML 中可以使用的语法: <code>android:scaleType="matrix"</code>。
*/
MATRIX (0),
/**
* 使用 {@link Matrix.ScaleToFit#FILL} 方式缩放图像。 在 XML 中可以使用的语法:
* <code>android:scaleType="fitXY"</code>.
*/
FIT_XY (1),
/**
* 使用 {@link Matrix.ScaleToFit#START} 方式缩放图像。 在 XML 中可以使用的语法:
* <code>android:scaleType="fitStart"</code>。
*/
FIT_START (2),
/**
* 使用 {@link Matrix.ScaleToFit#CENTER} 方式缩放图像。 在 XML 中可以使用的语法:
* <code>android:scaleType="fitCenter"</code>。
*/
FIT_CENTER (3),
/**
* 使用 {@link Matrix.ScaleToFit#END} 方式缩放图像。 在 XML 中可以使用的语法:
* <code>android:scaleType="fitEnd"</code>.
*/
FIT_END (4),
/**
* 在视图中使图像居中,不执行缩放。 在 XML 中可以使用的语法:
* <code>android:scaleType="center"</code>.
*/
CENTER (5),
/**
* 均衡的缩放图像(保持图像原始比例),使图片的两个坐标(宽、高)都大于等于 相应的视图坐标(负的内边距)。
* 图像则位于视图的中央。 在 XML 中可以使用的语法:<code>android:scaleType="centerCrop"</code>。
*/
CENTER_CROP (6),
/**
* 均衡的缩放图像(保持图像原始比例),使图片的两个坐标(宽、高)都小于等于 相应的视图坐标(负的内边距)。
* 图像则位于视图的中央。 在 XML 中可以使用的语法:<code>android:scaleType="centerInside"</code>.
*/
CENTER_INSIDE (7);
ScaleType(int ni) {
nativeInt = ni;
}
final int nativeInt;
}
/**
* 控制图像应该如何缩放和移动,以使图像与 ImageView 一致。
*
* @param scaleType 需要的缩放方式。
*
* @attr ref android.R.styleable#ImageView_scaleType
*/
public void setScaleType(ScaleType scaleType) {
if (scaleType == null) {
throw new NullPointerException();
}
if (mScaleType != scaleType) {
mScaleType = scaleType;
setWillNotCacheDrawing(mScaleType == ScaleType.CENTER);
requestLayout();
invalidate();
}
}
/**
* 返回当前 ImageView 使用的缩放类型。
*
* @see ImageView.ScaleType
*
* @attr ref android.R.styleable#ImageView_scaleType
*/
public ScaleType getScaleType() {
return mScaleType;
}
/**
* 返回视图的选项矩阵。当绘制时,应用于视图的可绘制对象。如果没有矩阵, 函数返回空。
* 不要更改这个矩阵。如果你要为可绘制对象设置不同的矩阵, 请调用 setImageMatrix()。
*/
public Matrix getImageMatrix() {
if (mDrawMatrix == null) {
return Matrix.IDENTITY_MATRIX;
}
return mDrawMatrix;
}
public void setImageMatrix(Matrix matrix) {
// collaps null and identity to just null
if (matrix != null && matrix.isIdentity()) {
matrix = null;
}
// don't invalidate unless we're actually changing our matrix
if (matrix == null && !mMatrix.isIdentity() ||
matrix != null && !mMatrix.equals(matrix)) {
mMatrix.set(matrix);
configureBounds();
invalidate();
}
}
/**
* Return whether this ImageView crops to padding.
*
* @return whether this ImageView crops to padding
*
* @see #setCropToPadding(boolean)
*
* @attr ref android.R.styleable#ImageView_cropToPadding
*/
public boolean getCropToPadding() {
return mCropToPadding;
}
/**
* Sets whether this ImageView will crop to padding.
*
* @param cropToPadding whether this ImageView will crop to padding
*
* @see #getCropToPadding()
*
* @attr ref android.R.styleable#ImageView_cropToPadding
*/
public void setCropToPadding(boolean cropToPadding) {
if (mCropToPadding != cropToPadding) {
mCropToPadding = cropToPadding;
requestLayout();
invalidate();
}
}
private void resolveUri() {
if (mDrawable != null) {
return;
}
Resources rsrc = getResources();
if (rsrc == null) {
return;
}
Drawable d = null;
if (mResource != 0) {
try {
d = rsrc.getDrawable(mResource);
} catch (Exception e) {
Log.w("ImageView", "Unable to find resource: " + mResource, e);
// Don't try again.
mUri = null;
}
} else if (mUri != null) {
String scheme = mUri.getScheme();
if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
try {
// Load drawable through Resources, to get the source density information
ContentResolver.OpenResourceIdResult r =
mContext.getContentResolver().getResourceId(mUri);
d = r.r.getDrawable(r.id);
} catch (Exception e) {
Log.w("ImageView", "Unable to open content: " + mUri, e);
}
} else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
|| ContentResolver.SCHEME_FILE.equals(scheme)) {
try {
d = Drawable.createFromStream(
mContext.getContentResolver().openInputStream(mUri),
null);
} catch (Exception e) {
Log.w("ImageView", "Unable to open content: " + mUri, e);
}
} else {
d = Drawable.createFromPath(mUri.toString());
}
if (d == null) {
System.out.println("resolveUri failed on bad bitmap uri: "
+ mUri);
// Don't try again.
mUri = null;
}
} else {
return;
}
updateDrawable(d);
}
@Override
public int[] onCreateDrawableState(int extraSpace) {
if (mState == null) {
return super.onCreateDrawableState(extraSpace);
} else if (!mMergeState) {
return mState;
} else {
return mergeDrawableStates(
super.onCreateDrawableState(extraSpace + mState.length), mState);
}
}
private void updateDrawable(Drawable d) {
if (mDrawable != null) {
mDrawable.setCallback(null);
unscheduleDrawable(mDrawable);
}
mDrawable = d;
if (d != null) {
d.setCallback(this);
if (d.isStateful()) {
d.setState(getDrawableState());
}
d.setLevel(mLevel);
d.setLayoutDirection(getLayoutDirection());
mDrawableWidth = d.getIntrinsicWidth();
mDrawableHeight = d.getIntrinsicHeight();
applyColorMod();
configureBounds();
} else {
mDrawableWidth = mDrawableHeight = -1;
}
}
private void resizeFromDrawable() {
Drawable d = mDrawable;
if (d != null) {
int w = d.getIntrinsicWidth();
if (w < 0) w = mDrawableWidth;
int h = d.getIntrinsicHeight();
if (h < 0) h = mDrawableHeight;
if (w != mDrawableWidth || h != mDrawableHeight) {
mDrawableWidth = w;
mDrawableHeight = h;
requestLayout();
}
}
}
private static final Matrix.ScaleToFit[] sS2FArray = {
Matrix.ScaleToFit.FILL,
Matrix.ScaleToFit.START,
Matrix.ScaleToFit.CENTER,
Matrix.ScaleToFit.END
};
private static Matrix.ScaleToFit scaleTypeToScaleToFit(ScaleType st) {
// ScaleToFit enum to their corresponding Matrix.ScaleToFit values
return sS2FArray[st.nativeInt - 1];
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
resolveUri();
int w;
int h;
// Desired aspect ratio of the view's contents (not including padding)
float desiredAspect = 0.0f;
// We are allowed to change the view's width
boolean resizeWidth = false;
// We are allowed to change the view's height
boolean resizeHeight = false;
final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (mDrawable == null) {
// If no drawable, its intrinsic size is 0.
mDrawableWidth = -1;
mDrawableHeight = -1;
w = h = 0;
} else {
w = mDrawableWidth;
h = mDrawableHeight;
if (w <= 0) w = 1;
if (h <= 0) h = 1;
// We are supposed to adjust view bounds to match the aspect
// ratio of our drawable. See if that is possible.
if (mAdjustViewBounds) {
resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
desiredAspect = (float) w / (float) h;
}
}
int pleft = mPaddingLeft;
int pright = mPaddingRight;
int ptop = mPaddingTop;
int pbottom = mPaddingBottom;
int widthSize;
int heightSize;
if (resizeWidth || resizeHeight) {
/* If we get here, it means we want to resize to match the
drawables aspect ratio, and we have the freedom to change at
least one dimension.
*/
// Get the max possible width given our constraints
widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);
// Get the max possible height given our constraints
heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);
if (desiredAspect != 0.0f) {
// See what our actual aspect ratio is
float actualAspect = (float)(widthSize - pleft - pright) /
(heightSize - ptop - pbottom);
if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {
boolean done = false;
// Try adjusting width to be proportional to height
if (resizeWidth) {
int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +
pleft + pright;
if (newWidth <= widthSize) {
widthSize = newWidth;
done = true;
}
}
// Try adjusting height to be proportional to width
if (!done && resizeHeight) {
int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
ptop + pbottom;
if (newHeight <= heightSize) {
heightSize = newHeight;
}
}
}
}
} else {
/* We are either don't want to preserve the drawables aspect ratio,
or we are not allowed to change view dimensions. Just measure in
the normal way.
*/
w += pleft + pright;
h += ptop + pbottom;
w = Math.max(w, getSuggestedMinimumWidth());
h = Math.max(h, getSuggestedMinimumHeight());
widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
}
setMeasuredDimension(widthSize, heightSize);
}
private int resolveAdjustedSize(int desiredSize, int maxSize,
int measureSpec) {
int result = desiredSize;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
/* Parent says we can be as big as we want. Just don't be larger
than max size imposed on ourselves.
*/
result = Math.min(desiredSize, maxSize);
break;
case MeasureSpec.AT_MOST:
// Parent says we can be as big as we want, up to specSize.
// Don't be larger than specSize, and don't be larger than
// the max size imposed on ourselves.
result = Math.min(Math.min(desiredSize, specSize), maxSize);
break;
case MeasureSpec.EXACTLY:
// No choice. Do what we are told.
result = specSize;
break;
}
return result;
}
@Override
protected boolean setFrame(int l, int t, int r, int b) {
boolean changed = super.setFrame(l, t, r, b);
mHaveFrame = true;
configureBounds();
return changed;
}
private void configureBounds() {
if (mDrawable == null || !mHaveFrame) {
return;
}
int dwidth = mDrawableWidth;
int dheight = mDrawableHeight;
int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
int vheight = getHeight() - mPaddingTop - mPaddingBottom;
boolean fits = (dwidth < 0 || vwidth == dwidth) &&
(dheight < 0 || vheight == dheight);
if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
/* If the drawable has no intrinsic size, or we're told to
scaletofit, then we just fill our entire view.
*/
mDrawable.setBounds(0, 0, vwidth, vheight);
mDrawMatrix = null;
} else {
// We need to do the scaling ourself, so have the drawable
// use its native size.
mDrawable.setBounds(0, 0, dwidth, dheight);
if (ScaleType.MATRIX == mScaleType) {
// Use the specified matrix as-is.
if (mMatrix.isIdentity()) {
mDrawMatrix = null;
} else {
mDrawMatrix = mMatrix;
}
} else if (fits) {
// The bitmap fits exactly, no transform needed.
mDrawMatrix = null;
} else if (ScaleType.CENTER == mScaleType) {
// Center bitmap in view, no scaling.
mDrawMatrix = mMatrix;
mDrawMatrix.setTranslate((int) ((vwidth - dwidth) * 0.5f + 0.5f),
(int) ((vheight - dheight) * 0.5f + 0.5f));
} else if (ScaleType.CENTER_CROP == mScaleType) {
mDrawMatrix = mMatrix;
float scale;
float dx = 0, dy = 0;
if (dwidth * vheight > vwidth * dheight) {
scale = (float) vheight / (float) dheight;
dx = (vwidth - dwidth * scale) * 0.5f;
} else {
scale = (float) vwidth / (float) dwidth;
dy = (vheight - dheight * scale) * 0.5f;
}
mDrawMatrix.setScale(scale, scale);
mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
} else if (ScaleType.CENTER_INSIDE == mScaleType) {
mDrawMatrix = mMatrix;
float scale;
float dx;
float dy;
if (dwidth <= vwidth && dheight <= vheight) {
scale = 1.0f;
} else {
scale = Math.min((float) vwidth / (float) dwidth,
(float) vheight / (float) dheight);
}
dx = (int) ((vwidth - dwidth * scale) * 0.5f + 0.5f);
dy = (int) ((vheight - dheight * scale) * 0.5f + 0.5f);
mDrawMatrix.setScale(scale, scale);
mDrawMatrix.postTranslate(dx, dy);
} else {
// Generate the required transform.
mTempSrc.set(0, 0, dwidth, dheight);
mTempDst.set(0, 0, vwidth, vheight);
mDrawMatrix = mMatrix;
mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
}
}
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
Drawable d = mDrawable;
if (d != null && d.isStateful()) {
d.setState(getDrawableState());
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mDrawable == null) {
return; // couldn't resolve the URI
}
if (mDrawableWidth == 0 || mDrawableHeight == 0) {
return; // nothing to draw (empty bounds)
}
if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
mDrawable.draw(canvas);
} else {
int saveCount = canvas.getSaveCount();
canvas.save();
if (mCropToPadding) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
scrollX + mRight - mLeft - mPaddingRight,
scrollY + mBottom - mTop - mPaddingBottom);
}
canvas.translate(mPaddingLeft, mPaddingTop);
if (mDrawMatrix != null) {
canvas.concat(mDrawMatrix);
}
mDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
/**
* <p>返回小部件顶端到文本基线的偏移量.如果小部件不支持基线对齐,该方法返回 -1.</p>
*
* @return 小部件顶端到文本基线的偏移量;或者是 -1 当小部件不支持基线对齐时.
*/
@Override
@ViewDebug.ExportedProperty(category = "layout")
public int getBaseline() {
if (mBaselineAlignBottom) {
return getMeasuredHeight();
} else {
return mBaseline;
}
}
/**
* <p>设置小部件中文本的基线与小部件顶边的偏移量.
* 该值可以由 {@link #setBaselineAlignBottom(boolean)} 属性的值覆盖.</p>
*
* @param baseline 使用的基线;为 -1 表示不使用基线。
*
* @see #setBaseline(int)
* @attr ref android.R.styleable#ImageView_baseline
*/
public void setBaseline(int baseline) {
if (mBaseline != baseline) {
mBaseline = baseline;
requestLayout();
}
}
/**
* 设置是否设置该视图的底边为基线. 设置该值会覆盖任何对 setBaseline 的调用。
*
* @param aligned 为真,图像视图会使用其底边作为基线来对齐。
*
* @attr ref android.R.styleable#ImageView_baselineAlignBottom
*/
public void setBaselineAlignBottom(boolean aligned) {
if (mBaselineAlignBottom != aligned) {
mBaselineAlignBottom = aligned;
requestLayout();
}
}
/**
* 返回该视图的基线是否为其底边。
*
* @see #setBaselineAlignBottom(boolean)
*/
public boolean getBaselineAlignBottom() {
return mBaselineAlignBottom;
}
/**
* 为图片设置着色选项。
*
* @param color 应用的着色颜色。
* @param mode 如何着色。标准模式为 {@link PorterDuff.Mode#SRC_ATOP}。
*
* @attr ref android.R.styleable#ImageView_tint
*/
public final void setColorFilter(int color, PorterDuff.Mode mode) {
setColorFilter(new PorterDuffColorFilter(color, mode));
}
/**
* 为图片设置着色选项。采用{@link PorterDuff.Mode#SRC_ATOP} 合成模式。
*
* @param color 应用的着色颜色。
* @attr ref android.R.styleable#ImageView_tint
*/
@RemotableViewMethod
public final void setColorFilter(int color) {
setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
}
public final void clearColorFilter() {
setColorFilter(null);
}
/**
* Returns the active color filter for this ImageView.
*
* @return the active color filter for this ImageView
*
* @see #setColorFilter(android.graphics.ColorFilter)
*/
public ColorFilter getColorFilter() {
return mColorFilter;
}
/**
* 为图片应用任意颜色滤镜。
*
* @param cf 要应用的颜色滤镜(可能为空)
*
* @see #getColorFilter()
*/
public void setColorFilter(ColorFilter cf) {
if (mColorFilter != cf) {
mColorFilter = cf;
mColorMod = true;
applyColorMod();
invalidate();
}
}
/**
* Returns the alpha that will be applied to the drawable of this ImageView.
*
* @return the alpha that will be applied to the drawable of this ImageView
*
* @see #setImageAlpha(int)
*/
public int getImageAlpha() {
return mAlpha;
}
/**
* Sets the alpha value that should be applied to the image.
*
* @param alpha the alpha value that should be applied to the image
*
* @see #getImageAlpha()
*/
@RemotableViewMethod
public void setImageAlpha(int alpha) {
setAlpha(alpha);
}
/**
* Sets the alpha value that should be applied to the image.
*
* @param alpha the alpha value that should be applied to the image
*
* @deprecated use #setImageAlpha(int) instead
*/
@Deprecated
@RemotableViewMethod
public void setAlpha(int alpha) {
alpha &= 0xFF; // keep it legal
if (mAlpha != alpha) {
mAlpha = alpha;
mColorMod = true;
applyColorMod();
invalidate();
}
}
private void applyColorMod() {
// Only mutate and apply when modifications have occurred. This should
// not reset the mColorMod flag, since these filters need to be
// re-applied if the Drawable is changed.
if (mDrawable != null && mColorMod) {
mDrawable = mDrawable.mutate();
mDrawable.setColorFilter(mColorFilter);
mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
}
}
@RemotableViewMethod
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
if (mDrawable != null) {
mDrawable.setVisible(visibility == VISIBLE, false);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mDrawable != null) {
mDrawable.setVisible(getVisibility() == VISIBLE, false);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mDrawable != null) {
mDrawable.setVisible(false, false);
}
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(ImageView.class.getName());
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(ImageView.class.getName());
}
}