947 lines
32 KiB
Java
947 lines
32 KiB
Java
/*
|
|
* 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.util.AttributeSet;
|
|
import android.util.Log;
|
|
import android.view.RemotableViewMethod;
|
|
import android.view.View;
|
|
import android.view.accessibility.AccessibilityEvent;
|
|
import android.view.accessibility.AccessibilityManager;
|
|
import android.widget.RemoteViews.RemoteView;
|
|
|
|
|
|
/**
|
|
* Displays an arbitrary image, such as an icon. The ImageView class
|
|
* can load images from various sources (such as resources or content
|
|
* providers), takes care of computing its measurement from the image so that
|
|
* it can be used in any layout manager, and provides various display options
|
|
* such as scaling and tinting.
|
|
*
|
|
* @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 boolean mBaselineAligned = 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);
|
|
}
|
|
|
|
mBaselineAligned = a.getBoolean(
|
|
com.android.internal.R.styleable.ImageView_baselineAlignBottom, false);
|
|
|
|
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);
|
|
}
|
|
|
|
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 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
|
|
protected boolean onSetAlpha(int alpha) {
|
|
if (getBackground() == null) {
|
|
int scale = alpha + (alpha >> 7);
|
|
if (mViewAlphaScale != scale) {
|
|
mViewAlphaScale = scale;
|
|
mColorMod = true;
|
|
applyColorMod();
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Set this to true if you want the ImageView to adjust its bounds
|
|
* to preserve the aspect ratio of its drawable.
|
|
* @param adjustViewBounds Whether to adjust the bounds of this view
|
|
* to presrve the original aspect ratio of the drawable
|
|
*
|
|
* @attr ref android.R.styleable#ImageView_adjustViewBounds
|
|
*/
|
|
@android.view.RemotableViewMethod
|
|
public void setAdjustViewBounds(boolean adjustViewBounds) {
|
|
mAdjustViewBounds = adjustViewBounds;
|
|
if (adjustViewBounds) {
|
|
setScaleType(ScaleType.FIT_CENTER);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An optional argument to supply a maximum width for this view. Only valid if
|
|
* {@link #setAdjustViewBounds} has been set to true. To set an image to be a maximum of 100 x
|
|
* 100 while preserving the original aspect ratio, do the following: 1) set adjustViewBounds to
|
|
* true 2) set maxWidth and maxHeight to 100 3) set the height and width layout params to
|
|
* WRAP_CONTENT.
|
|
*
|
|
* <p>
|
|
* Note that this view could be still smaller than 100 x 100 using this approach if the original
|
|
* image is small. To set an image to a fixed size, specify that size in the layout params and
|
|
* then use {@link #setScaleType} to determine how to fit the image within the bounds.
|
|
* </p>
|
|
*
|
|
* @param maxWidth maximum width for this view
|
|
*
|
|
* @attr ref android.R.styleable#ImageView_maxWidth
|
|
*/
|
|
@android.view.RemotableViewMethod
|
|
public void setMaxWidth(int maxWidth) {
|
|
mMaxWidth = maxWidth;
|
|
}
|
|
|
|
/**
|
|
* An optional argument to supply a maximum height for this view. Only valid if
|
|
* {@link #setAdjustViewBounds} has been set to true. To set an image to be a maximum of 100 x
|
|
* 100 while preserving the original aspect ratio, do the following: 1) set adjustViewBounds to
|
|
* true 2) set maxWidth and maxHeight to 100 3) set the height and width layout params to
|
|
* WRAP_CONTENT.
|
|
*
|
|
* <p>
|
|
* Note that this view could be still smaller than 100 x 100 using this approach if the original
|
|
* image is small. To set an image to a fixed size, specify that size in the layout params and
|
|
* then use {@link #setScaleType} to determine how to fit the image within the bounds.
|
|
* </p>
|
|
*
|
|
* @param maxHeight maximum height for this view
|
|
*
|
|
* @attr ref android.R.styleable#ImageView_maxHeight
|
|
*/
|
|
@android.view.RemotableViewMethod
|
|
public void setMaxHeight(int maxHeight) {
|
|
mMaxHeight = maxHeight;
|
|
}
|
|
|
|
/** Return the view's drawable, or null if no drawable has been
|
|
assigned.
|
|
*/
|
|
public Drawable getDrawable() {
|
|
return mDrawable;
|
|
}
|
|
|
|
/**
|
|
* Sets a drawable as the content of this ImageView.
|
|
*
|
|
* <p class="note">This does Bitmap reading and decoding on the UI
|
|
* thread, which can cause a latency hiccup. If that's a concern,
|
|
* consider using {@link #setImageDrawable} or
|
|
* {@link #setImageBitmap} and
|
|
* {@link android.graphics.BitmapFactory} instead.</p>
|
|
*
|
|
* @param resId the resource identifier of the the drawable
|
|
*
|
|
* @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;
|
|
resolveUri();
|
|
requestLayout();
|
|
invalidate();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the content of this ImageView to the specified Uri.
|
|
*
|
|
* <p class="note">This does Bitmap reading and decoding on the UI
|
|
* thread, which can cause a latency hiccup. If that's a concern,
|
|
* consider using {@link #setImageDrawable} or
|
|
* {@link #setImageBitmap} and
|
|
* {@link android.graphics.BitmapFactory} instead.</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;
|
|
resolveUri();
|
|
requestLayout();
|
|
invalidate();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets a drawable as the content of this ImageView.
|
|
*
|
|
* @param drawable The drawable to set
|
|
*/
|
|
public void setImageDrawable(Drawable drawable) {
|
|
if (mDrawable != drawable) {
|
|
mResource = 0;
|
|
mUri = null;
|
|
updateDrawable(drawable);
|
|
requestLayout();
|
|
invalidate();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets a Bitmap as the content of this ImageView.
|
|
*
|
|
* @param bm The bitmap to set
|
|
*/
|
|
@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();
|
|
}
|
|
|
|
/**
|
|
* Sets the image level, when it is constructed from a
|
|
* {@link android.graphics.drawable.LevelListDrawable}.
|
|
*
|
|
* @param level The new level for the image.
|
|
*/
|
|
@android.view.RemotableViewMethod
|
|
public void setImageLevel(int level) {
|
|
mLevel = level;
|
|
if (mDrawable != null) {
|
|
mDrawable.setLevel(level);
|
|
resizeFromDrawable();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Options for scaling the bounds of an image to the bounds of this view.
|
|
*/
|
|
public enum ScaleType {
|
|
/**
|
|
* Scale using the image matrix when drawing. The image matrix can be set using
|
|
* {@link ImageView#setImageMatrix(Matrix)}. From XML, use this syntax:
|
|
* <code>android:scaleType="matrix"</code>.
|
|
*/
|
|
MATRIX (0),
|
|
/**
|
|
* Scale the image using {@link Matrix.ScaleToFit#FILL}.
|
|
* From XML, use this syntax: <code>android:scaleType="fitXY"</code>.
|
|
*/
|
|
FIT_XY (1),
|
|
/**
|
|
* Scale the image using {@link Matrix.ScaleToFit#START}.
|
|
* From XML, use this syntax: <code>android:scaleType="fitStart"</code>.
|
|
*/
|
|
FIT_START (2),
|
|
/**
|
|
* Scale the image using {@link Matrix.ScaleToFit#CENTER}.
|
|
* From XML, use this syntax:
|
|
* <code>android:scaleType="fitCenter"</code>.
|
|
*/
|
|
FIT_CENTER (3),
|
|
/**
|
|
* Scale the image using {@link Matrix.ScaleToFit#END}.
|
|
* From XML, use this syntax: <code>android:scaleType="fitEnd"</code>.
|
|
*/
|
|
FIT_END (4),
|
|
/**
|
|
* Center the image in the view, but perform no scaling.
|
|
* From XML, use this syntax: <code>android:scaleType="center"</code>.
|
|
*/
|
|
CENTER (5),
|
|
/**
|
|
* Scale the image uniformly (maintain the image's aspect ratio) so
|
|
* that both dimensions (width and height) of the image will be equal
|
|
* to or larger than the corresponding dimension of the view
|
|
* (minus padding). The image is then centered in the view.
|
|
* From XML, use this syntax: <code>android:scaleType="centerCrop"</code>.
|
|
*/
|
|
CENTER_CROP (6),
|
|
/**
|
|
* Scale the image uniformly (maintain the image's aspect ratio) so
|
|
* that both dimensions (width and height) of the image will be equal
|
|
* to or less than the corresponding dimension of the view
|
|
* (minus padding). The image is then centered in the view.
|
|
* From XML, use this syntax: <code>android:scaleType="centerInside"</code>.
|
|
*/
|
|
CENTER_INSIDE (7);
|
|
|
|
ScaleType(int ni) {
|
|
nativeInt = ni;
|
|
}
|
|
final int nativeInt;
|
|
}
|
|
|
|
/**
|
|
* Controls how the image should be resized or moved to match the size
|
|
* of this ImageView.
|
|
*
|
|
* @param scaleType The desired scaling mode.
|
|
*
|
|
* @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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the current scale type in use by this ImageView.
|
|
*
|
|
* @see ImageView.ScaleType
|
|
*
|
|
* @attr ref android.R.styleable#ImageView_scaleType
|
|
*/
|
|
public ScaleType getScaleType() {
|
|
return mScaleType;
|
|
}
|
|
|
|
/** Return the view's optional matrix. This is applied to the
|
|
view's drawable when it is drawn. If there is not matrix,
|
|
this method will return null.
|
|
Do not change this matrix in place. If you want a different matrix
|
|
applied to the drawable, be sure to call setImageMatrix().
|
|
*/
|
|
public Matrix getImageMatrix() {
|
|
return mMatrix;
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
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);
|
|
mDrawableWidth = d.getIntrinsicWidth();
|
|
mDrawableHeight = d.getIntrinsicHeight();
|
|
applyColorMod();
|
|
configureBounds();
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
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) {
|
|
|
|
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
|
|
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
|
|
|
|
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 = resolveSize(w, widthMeasureSpec);
|
|
heightSize = resolveSize(h, heightMeasureSpec);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getBaseline() {
|
|
return mBaselineAligned ? getMeasuredHeight() : -1;
|
|
}
|
|
|
|
/**
|
|
* Set a tinting option for the image.
|
|
*
|
|
* @param color Color tint to apply.
|
|
* @param mode How to apply the color. The standard mode is
|
|
* {@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));
|
|
}
|
|
|
|
/**
|
|
* Set a tinting option for the image. Assumes
|
|
* {@link PorterDuff.Mode#SRC_ATOP} blending mode.
|
|
*
|
|
* @param color Color tint to apply.
|
|
* @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);
|
|
}
|
|
|
|
/**
|
|
* Apply an arbitrary colorfilter to the image.
|
|
*
|
|
* @param cf the colorfilter to apply (may be null)
|
|
*/
|
|
public void setColorFilter(ColorFilter cf) {
|
|
if (mColorFilter != cf) {
|
|
mColorFilter = cf;
|
|
mColorMod = true;
|
|
applyColorMod();
|
|
invalidate();
|
|
}
|
|
}
|
|
|
|
@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);
|
|
}
|
|
}
|
|
}
|