1741 lines
60 KiB
Java
1741 lines
60 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.annotation.DrawableRes;
|
||
|
import android.annotation.NonNull;
|
||
|
import android.annotation.Nullable;
|
||
|
import android.annotation.TestApi;
|
||
|
import android.compat.annotation.UnsupportedAppUsage;
|
||
|
import android.content.ContentResolver;
|
||
|
import android.content.Context;
|
||
|
import android.content.res.ColorStateList;
|
||
|
import android.content.res.Resources;
|
||
|
import android.content.res.TypedArray;
|
||
|
import android.graphics.Bitmap;
|
||
|
import android.graphics.BlendMode;
|
||
|
import android.graphics.Canvas;
|
||
|
import android.graphics.ColorFilter;
|
||
|
import android.graphics.ImageDecoder;
|
||
|
import android.graphics.Matrix;
|
||
|
import android.graphics.PixelFormat;
|
||
|
import android.graphics.PorterDuff;
|
||
|
import android.graphics.PorterDuffColorFilter;
|
||
|
import android.graphics.Rect;
|
||
|
import android.graphics.RectF;
|
||
|
import android.graphics.Xfermode;
|
||
|
import android.graphics.drawable.BitmapDrawable;
|
||
|
import android.graphics.drawable.Drawable;
|
||
|
import android.graphics.drawable.Icon;
|
||
|
import android.net.Uri;
|
||
|
import android.os.Build;
|
||
|
import android.os.Handler;
|
||
|
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.ViewHierarchyEncoder;
|
||
|
import android.view.accessibility.AccessibilityEvent;
|
||
|
import android.view.inspector.InspectableProperty;
|
||
|
import android.widget.RemoteViews.RemoteView;
|
||
|
|
||
|
import com.android.internal.R;
|
||
|
|
||
|
import java.io.IOException;
|
||
|
|
||
|
/**
|
||
|
* Displays image resources, for example {@link android.graphics.Bitmap}
|
||
|
* or {@link android.graphics.drawable.Drawable} resources.
|
||
|
* ImageView is also commonly used to
|
||
|
* <a href="#setImageTintMode(android.graphics.PorterDuff.Mode)">apply tints to an image</a> and
|
||
|
* handle <a href="#setScaleType(android.widget.ImageView.ScaleType)">image scaling</a>.
|
||
|
*
|
||
|
* <p>
|
||
|
* The following XML snippet is a common example of using an ImageView to display an image resource:
|
||
|
* </p>
|
||
|
* <pre>
|
||
|
* <LinearLayout
|
||
|
* xmlns:android="http://schemas.android.com/apk/res/android"
|
||
|
* android:layout_width="match_parent"
|
||
|
* android:layout_height="match_parent">
|
||
|
* <ImageView
|
||
|
* android:layout_width="wrap_content"
|
||
|
* android:layout_height="wrap_content"
|
||
|
* android:src="@drawable/my_image"
|
||
|
* android:contentDescription="@string/my_image_description"
|
||
|
* />
|
||
|
* </LinearLayout>
|
||
|
* </pre>
|
||
|
*
|
||
|
* <p>
|
||
|
* To learn more about Drawables, see: <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.
|
||
|
* To learn more about working with Bitmaps, see: <a href="{@docRoot}topic/performance/graphics/index.html">Handling Bitmaps</a>.
|
||
|
* </p>
|
||
|
*
|
||
|
* @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 {
|
||
|
private static final String LOG_TAG = "ImageView";
|
||
|
|
||
|
// settable by the client
|
||
|
@UnsupportedAppUsage
|
||
|
private Uri mUri;
|
||
|
@UnsupportedAppUsage
|
||
|
private int mResource = 0;
|
||
|
private Matrix mMatrix;
|
||
|
private ScaleType mScaleType;
|
||
|
private boolean mHaveFrame = false;
|
||
|
@UnsupportedAppUsage
|
||
|
private boolean mAdjustViewBounds = false;
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
|
||
|
private int mMaxWidth = Integer.MAX_VALUE;
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
|
||
|
private int mMaxHeight = Integer.MAX_VALUE;
|
||
|
|
||
|
// these are applied to the drawable
|
||
|
private ColorFilter mColorFilter = null;
|
||
|
private boolean mHasColorFilter = false;
|
||
|
private Xfermode mXfermode;
|
||
|
private boolean mHasXfermode = false;
|
||
|
@UnsupportedAppUsage
|
||
|
private int mAlpha = 255;
|
||
|
private boolean mHasAlpha = false;
|
||
|
private final int mViewAlphaScale = 256;
|
||
|
|
||
|
@UnsupportedAppUsage
|
||
|
private Drawable mDrawable = null;
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
private BitmapDrawable mRecycleableBitmapDrawable = null;
|
||
|
private ColorStateList mDrawableTintList = null;
|
||
|
private BlendMode mDrawableBlendMode = null;
|
||
|
private boolean mHasDrawableTint = false;
|
||
|
private boolean mHasDrawableBlendMode = false;
|
||
|
|
||
|
private int[] mState = null;
|
||
|
private boolean mMergeState = false;
|
||
|
private boolean mHasLevelSet = false;
|
||
|
private int mLevel = 0;
|
||
|
@UnsupportedAppUsage
|
||
|
private int mDrawableWidth;
|
||
|
@UnsupportedAppUsage
|
||
|
private int mDrawableHeight;
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124051687)
|
||
|
private Matrix mDrawMatrix = null;
|
||
|
|
||
|
// Avoid allocations...
|
||
|
private final RectF mTempSrc = new RectF();
|
||
|
private final RectF mTempDst = new RectF();
|
||
|
|
||
|
@UnsupportedAppUsage
|
||
|
private boolean mCropToPadding;
|
||
|
|
||
|
private int mBaseline = -1;
|
||
|
private boolean mBaselineAlignBottom = false;
|
||
|
|
||
|
/** Compatibility modes dependent on targetSdkVersion of the app. */
|
||
|
private static boolean sCompatDone;
|
||
|
|
||
|
/** AdjustViewBounds behavior will be in compatibility mode for older apps. */
|
||
|
private static boolean sCompatAdjustViewBounds;
|
||
|
|
||
|
/** Whether to pass Resources when creating the source from a stream. */
|
||
|
private static boolean sCompatUseCorrectStreamDensity;
|
||
|
|
||
|
/** Whether to use pre-Nougat drawable visibility dispatching conditions. */
|
||
|
private static boolean sCompatDrawableVisibilityDispatch;
|
||
|
|
||
|
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, @Nullable AttributeSet attrs) {
|
||
|
this(context, attrs, 0);
|
||
|
}
|
||
|
|
||
|
public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||
|
this(context, attrs, defStyleAttr, 0);
|
||
|
}
|
||
|
|
||
|
public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
|
||
|
int defStyleRes) {
|
||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||
|
|
||
|
initImageView();
|
||
|
|
||
|
final TypedArray a = context.obtainStyledAttributes(
|
||
|
attrs, R.styleable.ImageView, defStyleAttr, defStyleRes);
|
||
|
saveAttributeDataForStyleable(context, R.styleable.ImageView,
|
||
|
attrs, a, defStyleAttr, defStyleRes);
|
||
|
|
||
|
final Drawable d = a.getDrawable(R.styleable.ImageView_src);
|
||
|
if (d != null) {
|
||
|
setImageDrawable(d);
|
||
|
}
|
||
|
|
||
|
mBaselineAlignBottom = a.getBoolean(R.styleable.ImageView_baselineAlignBottom, false);
|
||
|
mBaseline = a.getDimensionPixelSize(R.styleable.ImageView_baseline, -1);
|
||
|
|
||
|
setAdjustViewBounds(a.getBoolean(R.styleable.ImageView_adjustViewBounds, false));
|
||
|
setMaxWidth(a.getDimensionPixelSize(R.styleable.ImageView_maxWidth, Integer.MAX_VALUE));
|
||
|
setMaxHeight(a.getDimensionPixelSize(R.styleable.ImageView_maxHeight, Integer.MAX_VALUE));
|
||
|
|
||
|
final int index = a.getInt(R.styleable.ImageView_scaleType, -1);
|
||
|
if (index >= 0) {
|
||
|
setScaleType(sScaleTypeArray[index]);
|
||
|
}
|
||
|
|
||
|
if (a.hasValue(R.styleable.ImageView_tint)) {
|
||
|
mDrawableTintList = a.getColorStateList(R.styleable.ImageView_tint);
|
||
|
mHasDrawableTint = true;
|
||
|
|
||
|
// Prior to L, this attribute would always set a color filter with
|
||
|
// blending mode SRC_ATOP. Preserve that default behavior.
|
||
|
mDrawableBlendMode = BlendMode.SRC_ATOP;
|
||
|
mHasDrawableBlendMode = true;
|
||
|
}
|
||
|
|
||
|
if (a.hasValue(R.styleable.ImageView_tintMode)) {
|
||
|
mDrawableBlendMode = Drawable.parseBlendMode(a.getInt(
|
||
|
R.styleable.ImageView_tintMode, -1), mDrawableBlendMode);
|
||
|
mHasDrawableBlendMode = true;
|
||
|
}
|
||
|
|
||
|
applyImageTint();
|
||
|
|
||
|
final int alpha = a.getInt(R.styleable.ImageView_drawableAlpha, 255);
|
||
|
if (alpha != 255) {
|
||
|
setImageAlpha(alpha);
|
||
|
}
|
||
|
|
||
|
mCropToPadding = a.getBoolean(
|
||
|
R.styleable.ImageView_cropToPadding, false);
|
||
|
|
||
|
a.recycle();
|
||
|
|
||
|
//need inflate syntax/reader for matrix
|
||
|
}
|
||
|
|
||
|
private void initImageView() {
|
||
|
mMatrix = new Matrix();
|
||
|
mScaleType = ScaleType.FIT_CENTER;
|
||
|
|
||
|
if (!sCompatDone) {
|
||
|
final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
|
||
|
sCompatAdjustViewBounds = targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1;
|
||
|
sCompatUseCorrectStreamDensity = targetSdkVersion > Build.VERSION_CODES.M;
|
||
|
sCompatDrawableVisibilityDispatch = targetSdkVersion < Build.VERSION_CODES.N;
|
||
|
sCompatDone = true;
|
||
|
}
|
||
|
|
||
|
// By default, ImageView is not important for autofill but important for content capture.
|
||
|
// Developers can override these defaults via the corresponding attributes.
|
||
|
if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
|
||
|
setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO);
|
||
|
}
|
||
|
if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
|
||
|
setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected boolean verifyDrawable(@NonNull Drawable dr) {
|
||
|
return mDrawable == dr || super.verifyDrawable(dr);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void jumpDrawablesToCurrentState() {
|
||
|
super.jumpDrawablesToCurrentState();
|
||
|
if (mDrawable != null) mDrawable.jumpToCurrentState();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void invalidateDrawable(@NonNull Drawable dr) {
|
||
|
if (dr == mDrawable) {
|
||
|
if (dr != null) {
|
||
|
// update cached drawable dimensions if they've changed
|
||
|
final int w = dr.getIntrinsicWidth();
|
||
|
final int h = dr.getIntrinsicHeight();
|
||
|
if (w != mDrawableWidth || h != mDrawableHeight) {
|
||
|
mDrawableWidth = w;
|
||
|
mDrawableHeight = h;
|
||
|
// updates the matrix, which is dependent on the bounds
|
||
|
configureBounds();
|
||
|
}
|
||
|
}
|
||
|
/* 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 && getBackground().getCurrent() != null);
|
||
|
}
|
||
|
|
||
|
/** @hide */
|
||
|
@Override
|
||
|
public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
|
||
|
super.onPopulateAccessibilityEventInternal(event);
|
||
|
|
||
|
final 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 preserve the original aspect ratio of the drawable
|
||
|
*
|
||
|
* @see #setAdjustViewBounds(boolean)
|
||
|
*
|
||
|
* @attr ref android.R.styleable#ImageView_adjustViewBounds
|
||
|
*/
|
||
|
@InspectableProperty
|
||
|
public boolean getAdjustViewBounds() {
|
||
|
return mAdjustViewBounds;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set this to true if you want the ImageView to adjust its bounds
|
||
|
* to preserve the aspect ratio of its drawable.
|
||
|
*
|
||
|
* <p><strong>Note:</strong> If the application targets API level 17 or lower,
|
||
|
* adjustViewBounds will allow the drawable to shrink the view bounds, but not grow
|
||
|
* to fill available measured space in all cases. This is for compatibility with
|
||
|
* legacy {@link android.view.View.MeasureSpec MeasureSpec} and
|
||
|
* {@link android.widget.RelativeLayout RelativeLayout} behavior.</p>
|
||
|
*
|
||
|
* @param adjustViewBounds Whether to adjust the bounds of this view
|
||
|
* to preserve the original aspect ratio of the drawable.
|
||
|
*
|
||
|
* @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
|
||
|
*/
|
||
|
@InspectableProperty
|
||
|
public int getMaxWidth() {
|
||
|
return mMaxWidth;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* An optional argument to supply a maximum width for this view. Only valid if
|
||
|
* {@link #setAdjustViewBounds(boolean)} 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(android.widget.ImageView.ScaleType)} to determine how to fit
|
||
|
* the image within the bounds.
|
||
|
* </p>
|
||
|
*
|
||
|
* @param maxWidth maximum width for this view
|
||
|
*
|
||
|
* @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
|
||
|
*/
|
||
|
@InspectableProperty
|
||
|
public int getMaxHeight() {
|
||
|
return mMaxHeight;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* An optional argument to supply a maximum height for this view. Only valid if
|
||
|
* {@link #setAdjustViewBounds(boolean)} 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(android.widget.ImageView.ScaleType)} to determine how to fit
|
||
|
* the image within the bounds.
|
||
|
* </p>
|
||
|
*
|
||
|
* @param maxHeight maximum height for this view
|
||
|
*
|
||
|
* @see #getMaxHeight()
|
||
|
*
|
||
|
* @attr ref android.R.styleable#ImageView_maxHeight
|
||
|
*/
|
||
|
@android.view.RemotableViewMethod
|
||
|
public void setMaxHeight(int maxHeight) {
|
||
|
mMaxHeight = maxHeight;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the current Drawable, or null if no Drawable has been
|
||
|
* assigned.
|
||
|
*
|
||
|
* @return the view's drawable, or null if no drawable has been
|
||
|
* assigned.
|
||
|
*/
|
||
|
@InspectableProperty(name = "src")
|
||
|
public Drawable getDrawable() {
|
||
|
if (mDrawable == mRecycleableBitmapDrawable) {
|
||
|
// Consider our cached version dirty since app code now has a reference to it
|
||
|
mRecycleableBitmapDrawable = null;
|
||
|
}
|
||
|
return mDrawable;
|
||
|
}
|
||
|
|
||
|
private class ImageDrawableCallback implements Runnable {
|
||
|
|
||
|
private final Drawable drawable;
|
||
|
private final Uri uri;
|
||
|
private final int resource;
|
||
|
|
||
|
ImageDrawableCallback(Drawable drawable, Uri uri, int resource) {
|
||
|
this.drawable = drawable;
|
||
|
this.uri = uri;
|
||
|
this.resource = resource;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void run() {
|
||
|
setImageDrawable(drawable);
|
||
|
mUri = uri;
|
||
|
mResource = resource;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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(android.graphics.drawable.Drawable)} or
|
||
|
* {@link #setImageBitmap(android.graphics.Bitmap)} and
|
||
|
* {@link android.graphics.BitmapFactory} instead.</p>
|
||
|
*
|
||
|
* @param resId the resource identifier of the drawable
|
||
|
*
|
||
|
* @attr ref android.R.styleable#ImageView_src
|
||
|
*/
|
||
|
@android.view.RemotableViewMethod(asyncImpl="setImageResourceAsync")
|
||
|
public void setImageResource(@DrawableRes int resId) {
|
||
|
// The resource configuration may have changed, so we should always
|
||
|
// try to load the resource even if the resId hasn't changed.
|
||
|
final int oldWidth = mDrawableWidth;
|
||
|
final int oldHeight = mDrawableHeight;
|
||
|
|
||
|
updateDrawable(null);
|
||
|
mResource = resId;
|
||
|
mUri = null;
|
||
|
|
||
|
resolveUri();
|
||
|
|
||
|
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
|
||
|
requestLayout();
|
||
|
}
|
||
|
invalidate();
|
||
|
}
|
||
|
|
||
|
/** @hide **/
|
||
|
@UnsupportedAppUsage
|
||
|
public Runnable setImageResourceAsync(@DrawableRes int resId) {
|
||
|
Drawable d = null;
|
||
|
if (resId != 0) {
|
||
|
try {
|
||
|
d = getContext().getDrawable(resId);
|
||
|
} catch (Exception e) {
|
||
|
Log.w(LOG_TAG, "Unable to find resource: " + resId, e);
|
||
|
resId = 0;
|
||
|
}
|
||
|
}
|
||
|
return new ImageDrawableCallback(d, null, resId);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the content of this ImageView to the specified Uri.
|
||
|
* Note that you use this method to load images from a local Uri only.
|
||
|
* <p/>
|
||
|
* To learn how to display images from a remote Uri see: <a href="https://developer.android.com/topic/performance/graphics/index.html">Handling Bitmaps</a>
|
||
|
* <p/>
|
||
|
* <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(Drawable)} or
|
||
|
* {@link #setImageBitmap(android.graphics.Bitmap)} and
|
||
|
* {@link android.graphics.BitmapFactory} instead.</p>
|
||
|
*
|
||
|
* <p class="note">On devices running SDK < 24, this method will fail to
|
||
|
* apply correct density scaling to images loaded from
|
||
|
* {@link ContentResolver#SCHEME_CONTENT content} and
|
||
|
* {@link ContentResolver#SCHEME_FILE file} schemes. Applications running
|
||
|
* on devices with SDK >= 24 <strong>MUST</strong> specify the
|
||
|
* {@code targetSdkVersion} in their manifest as 24 or above for density
|
||
|
* scaling to be applied to images loaded from these schemes.</p>
|
||
|
*
|
||
|
* @param uri the Uri of an image, or {@code null} to clear the content
|
||
|
*/
|
||
|
@android.view.RemotableViewMethod(asyncImpl="setImageURIAsync")
|
||
|
public void setImageURI(@Nullable 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();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @hide **/
|
||
|
@UnsupportedAppUsage
|
||
|
public Runnable setImageURIAsync(@Nullable Uri uri) {
|
||
|
if (mResource != 0 || (mUri != uri && (uri == null || mUri == null || !uri.equals(mUri)))) {
|
||
|
Drawable d = uri == null ? null : getDrawableFromUri(uri);
|
||
|
if (d == null) {
|
||
|
// Do not set the URI if the drawable couldn't be loaded.
|
||
|
uri = null;
|
||
|
}
|
||
|
return new ImageDrawableCallback(d, uri, 0);
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets a drawable as the content of this ImageView.
|
||
|
*
|
||
|
* @param drawable the Drawable to set, or {@code null} to clear the
|
||
|
* content
|
||
|
*/
|
||
|
public void setImageDrawable(@Nullable 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();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the content of this ImageView to the specified Icon.
|
||
|
*
|
||
|
* <p class="note">Depending on the Icon type, this may do Bitmap reading
|
||
|
* and decoding on the UI thread, which can cause UI jank. If that's a
|
||
|
* concern, consider using
|
||
|
* {@link Icon#loadDrawableAsync(Context, Icon.OnDrawableLoadedListener, Handler)}
|
||
|
* and then {@link #setImageDrawable(android.graphics.drawable.Drawable)}
|
||
|
* instead.</p>
|
||
|
*
|
||
|
* @param icon an Icon holding the desired image, or {@code null} to clear
|
||
|
* the content
|
||
|
*/
|
||
|
@android.view.RemotableViewMethod(asyncImpl="setImageIconAsync")
|
||
|
public void setImageIcon(@Nullable Icon icon) {
|
||
|
setImageDrawable(icon == null ? null : icon.loadDrawable(mContext));
|
||
|
}
|
||
|
|
||
|
/** @hide **/
|
||
|
public Runnable setImageIconAsync(@Nullable Icon icon) {
|
||
|
return new ImageDrawableCallback(icon == null ? null : icon.loadDrawable(mContext), null, 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Applies a tint to the image drawable. Does not modify the current tint
|
||
|
* mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
|
||
|
* <p>
|
||
|
* Subsequent calls to {@link #setImageDrawable(Drawable)} will automatically
|
||
|
* mutate the drawable and apply the specified tint and tint mode using
|
||
|
* {@link Drawable#setTintList(ColorStateList)}.
|
||
|
* <p>
|
||
|
* <em>Note:</em> The default tint mode used by this setter is NOT
|
||
|
* consistent with the default tint mode used by the
|
||
|
* {@link android.R.styleable#ImageView_tint android:tint}
|
||
|
* attribute. If the {@code android:tint} attribute is specified, the
|
||
|
* default tint mode will be set to {@link PorterDuff.Mode#SRC_ATOP} to
|
||
|
* ensure consistency with earlier versions of the platform.
|
||
|
*
|
||
|
* @param tint the tint to apply, may be {@code null} to clear tint
|
||
|
*
|
||
|
* @attr ref android.R.styleable#ImageView_tint
|
||
|
* @see #getImageTintList()
|
||
|
* @see Drawable#setTintList(ColorStateList)
|
||
|
*/
|
||
|
@android.view.RemotableViewMethod
|
||
|
public void setImageTintList(@Nullable ColorStateList tint) {
|
||
|
mDrawableTintList = tint;
|
||
|
mHasDrawableTint = true;
|
||
|
|
||
|
applyImageTint();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the current {@link android.content.res.ColorStateList} used to tint the image Drawable,
|
||
|
* or null if no tint is applied.
|
||
|
*
|
||
|
* @return the tint applied to the image drawable
|
||
|
* @attr ref android.R.styleable#ImageView_tint
|
||
|
* @see #setImageTintList(ColorStateList)
|
||
|
*/
|
||
|
@Nullable
|
||
|
@InspectableProperty(name = "tint")
|
||
|
public ColorStateList getImageTintList() {
|
||
|
return mDrawableTintList;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Specifies the blending mode used to apply the tint specified by
|
||
|
* {@link #setImageTintList(ColorStateList)}} to the image drawable. The default
|
||
|
* mode is {@link PorterDuff.Mode#SRC_IN}.
|
||
|
*
|
||
|
* @param tintMode the blending mode used to apply the tint, may be
|
||
|
* {@code null} to clear tint
|
||
|
* @attr ref android.R.styleable#ImageView_tintMode
|
||
|
* @see #getImageTintMode()
|
||
|
* @see Drawable#setTintMode(PorterDuff.Mode)
|
||
|
*/
|
||
|
public void setImageTintMode(@Nullable PorterDuff.Mode tintMode) {
|
||
|
setImageTintBlendMode(tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Specifies the blending mode used to apply the tint specified by
|
||
|
* {@link #setImageTintList(ColorStateList)}} to the image drawable. The default
|
||
|
* mode is {@link BlendMode#SRC_IN}.
|
||
|
*
|
||
|
* @param blendMode the blending mode used to apply the tint, may be
|
||
|
* {@code null} to clear tint
|
||
|
* @attr ref android.R.styleable#ImageView_tintMode
|
||
|
* @see #getImageTintMode()
|
||
|
* @see Drawable#setTintBlendMode(BlendMode)
|
||
|
*/
|
||
|
@RemotableViewMethod
|
||
|
public void setImageTintBlendMode(@Nullable BlendMode blendMode) {
|
||
|
mDrawableBlendMode = blendMode;
|
||
|
mHasDrawableBlendMode = true;
|
||
|
|
||
|
applyImageTint();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the blending mode used to apply the tint to the image Drawable
|
||
|
* @return the blending mode used to apply the tint to the image Drawable
|
||
|
* @attr ref android.R.styleable#ImageView_tintMode
|
||
|
* @see #setImageTintMode(PorterDuff.Mode)
|
||
|
*/
|
||
|
@Nullable
|
||
|
@InspectableProperty(name = "tintMode")
|
||
|
public PorterDuff.Mode getImageTintMode() {
|
||
|
return mDrawableBlendMode != null
|
||
|
? BlendMode.blendModeToPorterDuffMode(mDrawableBlendMode) : null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the blending mode used to apply the tint to the image Drawable
|
||
|
* @return the blending mode used to apply the tint to the image Drawable
|
||
|
* @attr ref android.R.styleable#ImageView_tintMode
|
||
|
* @see #setImageTintBlendMode(BlendMode)
|
||
|
*/
|
||
|
@Nullable
|
||
|
@InspectableProperty(name = "blendMode", attributeId = android.R.styleable.ImageView_tintMode)
|
||
|
public BlendMode getImageTintBlendMode() {
|
||
|
return mDrawableBlendMode;
|
||
|
}
|
||
|
|
||
|
private void applyImageTint() {
|
||
|
if (mDrawable != null && (mHasDrawableTint || mHasDrawableBlendMode)) {
|
||
|
mDrawable = mDrawable.mutate();
|
||
|
|
||
|
if (mHasDrawableTint) {
|
||
|
mDrawable.setTintList(mDrawableTintList);
|
||
|
}
|
||
|
|
||
|
if (mHasDrawableBlendMode) {
|
||
|
mDrawable.setTintBlendMode(mDrawableBlendMode);
|
||
|
}
|
||
|
|
||
|
// The drawable (or one of its children) may not have been
|
||
|
// stateful before applying the tint, so let's try again.
|
||
|
if (mDrawable.isStateful()) {
|
||
|
mDrawable.setState(getDrawableState());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets a Bitmap as the content of this ImageView.
|
||
|
*
|
||
|
* @param bm The bitmap to set
|
||
|
*/
|
||
|
@android.view.RemotableViewMethod
|
||
|
public void setImageBitmap(Bitmap bm) {
|
||
|
// Hacky fix to force setImageDrawable to do a full setImageDrawable
|
||
|
// instead of doing an object reference comparison
|
||
|
mDrawable = null;
|
||
|
if (mRecycleableBitmapDrawable == null) {
|
||
|
mRecycleableBitmapDrawable = new BitmapDrawable(mContext.getResources(), bm);
|
||
|
} else {
|
||
|
mRecycleableBitmapDrawable.setBitmap(bm);
|
||
|
}
|
||
|
setImageDrawable(mRecycleableBitmapDrawable);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the state of the current {@link android.graphics.drawable.StateListDrawable}.
|
||
|
* For more information about State List Drawables, see: <a href="https://developer.android.com/guide/topics/resources/drawable-resource.html#StateList">the Drawable Resource Guide</a>.
|
||
|
*
|
||
|
* @param state the state to set for the StateListDrawable
|
||
|
* @param merge if true, merges the state values for the state you specify into the current state
|
||
|
*/
|
||
|
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;
|
||
|
mHasLevelSet = true;
|
||
|
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;
|
||
|
|
||
|
requestLayout();
|
||
|
invalidate();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the current ScaleType that is used to scale the bounds of an image to the bounds of the ImageView.
|
||
|
* @return The ScaleType used to scale the image.
|
||
|
* @see ImageView.ScaleType
|
||
|
* @attr ref android.R.styleable#ImageView_scaleType
|
||
|
*/
|
||
|
@InspectableProperty
|
||
|
public ScaleType getScaleType() {
|
||
|
return mScaleType;
|
||
|
}
|
||
|
|
||
|
/** Returns the view's optional matrix. This is applied to the
|
||
|
view's drawable when it is drawn. If there is no matrix,
|
||
|
this method will return an identity matrix.
|
||
|
Do not change this matrix in place but make a copy.
|
||
|
If you want a different matrix applied to the drawable,
|
||
|
be sure to call setImageMatrix().
|
||
|
*/
|
||
|
public Matrix getImageMatrix() {
|
||
|
if (mDrawMatrix == null) {
|
||
|
return new Matrix(Matrix.IDENTITY_MATRIX);
|
||
|
}
|
||
|
return mDrawMatrix;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Adds a transformation {@link Matrix} that is applied
|
||
|
* to the view's drawable when it is drawn. Allows custom scaling,
|
||
|
* translation, and perspective distortion.
|
||
|
*
|
||
|
* @param matrix The transformation parameters in matrix form.
|
||
|
*/
|
||
|
public void setImageMatrix(Matrix matrix) {
|
||
|
// collapse 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
|
||
|
*/
|
||
|
@InspectableProperty
|
||
|
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();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage
|
||
|
private void resolveUri() {
|
||
|
if (mDrawable != null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (getResources() == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Drawable d = null;
|
||
|
|
||
|
if (mResource != 0) {
|
||
|
try {
|
||
|
d = mContext.getDrawable(mResource);
|
||
|
} catch (Exception e) {
|
||
|
Log.w(LOG_TAG, "Unable to find resource: " + mResource, e);
|
||
|
// Don't try again.
|
||
|
mResource = 0;
|
||
|
}
|
||
|
} else if (mUri != null) {
|
||
|
d = getDrawableFromUri(mUri);
|
||
|
|
||
|
if (d == null) {
|
||
|
Log.w(LOG_TAG, "resolveUri failed on bad bitmap uri: " + mUri);
|
||
|
// Don't try again.
|
||
|
mUri = null;
|
||
|
}
|
||
|
} else {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
updateDrawable(d);
|
||
|
}
|
||
|
|
||
|
private Drawable getDrawableFromUri(Uri uri) {
|
||
|
final String scheme = uri.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(uri);
|
||
|
return r.r.getDrawable(r.id, mContext.getTheme());
|
||
|
} catch (Exception e) {
|
||
|
Log.w(LOG_TAG, "Unable to open content: " + uri, e);
|
||
|
}
|
||
|
} else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
|
||
|
|| ContentResolver.SCHEME_FILE.equals(scheme)) {
|
||
|
try {
|
||
|
Resources res = sCompatUseCorrectStreamDensity ? getResources() : null;
|
||
|
ImageDecoder.Source src = ImageDecoder.createSource(mContext.getContentResolver(),
|
||
|
uri, res);
|
||
|
return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
|
||
|
decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
|
||
|
});
|
||
|
} catch (IOException e) {
|
||
|
Log.w(LOG_TAG, "Unable to open content: " + uri, e);
|
||
|
}
|
||
|
} else {
|
||
|
return Drawable.createFromPath(uri.toString());
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
@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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage
|
||
|
private void updateDrawable(Drawable d) {
|
||
|
if (d != mRecycleableBitmapDrawable && mRecycleableBitmapDrawable != null) {
|
||
|
mRecycleableBitmapDrawable.setBitmap(null);
|
||
|
}
|
||
|
|
||
|
boolean sameDrawable = false;
|
||
|
|
||
|
if (mDrawable != null) {
|
||
|
sameDrawable = mDrawable == d;
|
||
|
mDrawable.setCallback(null);
|
||
|
unscheduleDrawable(mDrawable);
|
||
|
if (!sCompatDrawableVisibilityDispatch && !sameDrawable && isAttachedToWindow()) {
|
||
|
mDrawable.setVisible(false, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mDrawable = d;
|
||
|
|
||
|
if (d != null) {
|
||
|
d.setCallback(this);
|
||
|
d.setLayoutDirection(getLayoutDirection());
|
||
|
if (d.isStateful()) {
|
||
|
d.setState(getDrawableState());
|
||
|
}
|
||
|
if (!sameDrawable || sCompatDrawableVisibilityDispatch) {
|
||
|
final boolean visible = sCompatDrawableVisibilityDispatch
|
||
|
? getVisibility() == VISIBLE
|
||
|
: isAttachedToWindow() && getWindowVisibility() == VISIBLE && isShown();
|
||
|
d.setVisible(visible, true);
|
||
|
}
|
||
|
if (mHasLevelSet) {
|
||
|
d.setLevel(mLevel);
|
||
|
}
|
||
|
mDrawableWidth = d.getIntrinsicWidth();
|
||
|
mDrawableHeight = d.getIntrinsicHeight();
|
||
|
applyImageTint();
|
||
|
applyColorFilter();
|
||
|
applyAlpha();
|
||
|
applyXfermode();
|
||
|
|
||
|
configureBounds();
|
||
|
} else {
|
||
|
mDrawableWidth = mDrawableHeight = -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage
|
||
|
private void resizeFromDrawable() {
|
||
|
final 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();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onRtlPropertiesChanged(int layoutDirection) {
|
||
|
super.onRtlPropertiesChanged(layoutDirection);
|
||
|
|
||
|
if (mDrawable != null) {
|
||
|
mDrawable.setLayoutDirection(layoutDirection);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static final Matrix.ScaleToFit[] sS2FArray = {
|
||
|
Matrix.ScaleToFit.FILL,
|
||
|
Matrix.ScaleToFit.START,
|
||
|
Matrix.ScaleToFit.CENTER,
|
||
|
Matrix.ScaleToFit.END
|
||
|
};
|
||
|
|
||
|
@UnsupportedAppUsage
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
final int pleft = mPaddingLeft;
|
||
|
final int pright = mPaddingRight;
|
||
|
final int ptop = mPaddingTop;
|
||
|
final 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
|
||
|
final 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;
|
||
|
|
||
|
// Allow the width to outgrow its original estimate if height is fixed.
|
||
|
if (!resizeHeight && !sCompatAdjustViewBounds) {
|
||
|
widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
|
||
|
// Allow the height to outgrow its original estimate if width is fixed.
|
||
|
if (!resizeWidth && !sCompatAdjustViewBounds) {
|
||
|
heightSize = resolveAdjustedSize(newHeight, mMaxHeight,
|
||
|
heightMeasureSpec);
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
final int specMode = MeasureSpec.getMode(measureSpec);
|
||
|
final 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) {
|
||
|
final boolean changed = super.setFrame(l, t, r, b);
|
||
|
mHaveFrame = true;
|
||
|
configureBounds();
|
||
|
return changed;
|
||
|
}
|
||
|
|
||
|
private void configureBounds() {
|
||
|
if (mDrawable == null || !mHaveFrame) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
final int dwidth = mDrawableWidth;
|
||
|
final int dheight = mDrawableHeight;
|
||
|
|
||
|
final int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
|
||
|
final int vheight = getHeight() - mPaddingTop - mPaddingBottom;
|
||
|
|
||
|
final 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(Math.round((vwidth - dwidth) * 0.5f),
|
||
|
Math.round((vheight - dheight) * 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(Math.round(dx), Math.round(dy));
|
||
|
} 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 = Math.round((vwidth - dwidth * scale) * 0.5f);
|
||
|
dy = Math.round((vheight - dheight * scale) * 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();
|
||
|
|
||
|
final Drawable drawable = mDrawable;
|
||
|
if (drawable != null && drawable.isStateful()
|
||
|
&& drawable.setState(getDrawableState())) {
|
||
|
invalidateDrawable(drawable);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void drawableHotspotChanged(float x, float y) {
|
||
|
super.drawableHotspotChanged(x, y);
|
||
|
|
||
|
if (mDrawable != null) {
|
||
|
mDrawable.setHotspot(x, y);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Applies a temporary transformation {@link Matrix} to the view's drawable when it is drawn.
|
||
|
* Allows custom scaling, translation, and perspective distortion during an animation.
|
||
|
*
|
||
|
* This method is a lightweight analogue of {@link ImageView#setImageMatrix(Matrix)} to use
|
||
|
* only during animations as this matrix will be cleared after the next drawable
|
||
|
* update or view's bounds change.
|
||
|
*
|
||
|
* @param matrix The transformation parameters in matrix form.
|
||
|
*/
|
||
|
public void animateTransform(@Nullable Matrix matrix) {
|
||
|
if (mDrawable == null) {
|
||
|
return;
|
||
|
}
|
||
|
if (matrix == null) {
|
||
|
final int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
|
||
|
final int vheight = getHeight() - mPaddingTop - mPaddingBottom;
|
||
|
mDrawable.setBounds(0, 0, vwidth, vheight);
|
||
|
mDrawMatrix = null;
|
||
|
} else {
|
||
|
mDrawable.setBounds(0, 0, mDrawableWidth, mDrawableHeight);
|
||
|
if (mDrawMatrix == null) {
|
||
|
mDrawMatrix = new Matrix();
|
||
|
}
|
||
|
mDrawMatrix.set(matrix);
|
||
|
}
|
||
|
invalidate();
|
||
|
}
|
||
|
|
||
|
@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 {
|
||
|
final 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>Return the offset of the widget's text baseline from the widget's top
|
||
|
* boundary. </p>
|
||
|
*
|
||
|
* @return the offset of the baseline within the widget's bounds or -1
|
||
|
* if baseline alignment is not supported.
|
||
|
*/
|
||
|
@Override
|
||
|
@InspectableProperty
|
||
|
@ViewDebug.ExportedProperty(category = "layout")
|
||
|
public int getBaseline() {
|
||
|
if (mBaselineAlignBottom) {
|
||
|
return getMeasuredHeight();
|
||
|
} else {
|
||
|
return mBaseline;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <p>Set the offset of the widget's text baseline from the widget's top
|
||
|
* boundary. This value is overridden by the {@link #setBaselineAlignBottom(boolean)}
|
||
|
* property.</p>
|
||
|
*
|
||
|
* @param baseline The baseline to use, or -1 if none is to be provided.
|
||
|
*
|
||
|
* @see #setBaseline(int)
|
||
|
* @attr ref android.R.styleable#ImageView_baseline
|
||
|
*/
|
||
|
public void setBaseline(int baseline) {
|
||
|
if (mBaseline != baseline) {
|
||
|
mBaseline = baseline;
|
||
|
requestLayout();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets whether the baseline of this view to the bottom of the view.
|
||
|
* Setting this value overrides any calls to setBaseline.
|
||
|
*
|
||
|
* @param aligned If true, the image view will be baseline aligned by its bottom edge.
|
||
|
*
|
||
|
* @attr ref android.R.styleable#ImageView_baselineAlignBottom
|
||
|
*/
|
||
|
public void setBaselineAlignBottom(boolean aligned) {
|
||
|
if (mBaselineAlignBottom != aligned) {
|
||
|
mBaselineAlignBottom = aligned;
|
||
|
requestLayout();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks whether this view's baseline is considered the bottom of the view.
|
||
|
*
|
||
|
* @return True if the ImageView's baseline is considered the bottom of the view, false if otherwise.
|
||
|
* @see #setBaselineAlignBottom(boolean)
|
||
|
*/
|
||
|
@InspectableProperty
|
||
|
public boolean getBaselineAlignBottom() {
|
||
|
return mBaselineAlignBottom;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets 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);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Removes the image's {@link android.graphics.ColorFilter}.
|
||
|
*
|
||
|
* @see #setColorFilter(int)
|
||
|
* @see #getColorFilter()
|
||
|
*/
|
||
|
public final void clearColorFilter() {
|
||
|
setColorFilter(null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide Candidate for future API inclusion
|
||
|
*/
|
||
|
public final void setXfermode(Xfermode mode) {
|
||
|
if (mXfermode != mode) {
|
||
|
mXfermode = mode;
|
||
|
mHasXfermode = true;
|
||
|
applyXfermode();
|
||
|
invalidate();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Apply an arbitrary colorfilter to the image.
|
||
|
*
|
||
|
* @param cf the colorfilter to apply (may be null)
|
||
|
*
|
||
|
* @see #getColorFilter()
|
||
|
*/
|
||
|
public void setColorFilter(ColorFilter cf) {
|
||
|
if (mColorFilter != cf) {
|
||
|
mColorFilter = cf;
|
||
|
mHasColorFilter = true;
|
||
|
applyColorFilter();
|
||
|
invalidate();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the alpha that will be applied to the drawable of this ImageView.
|
||
|
*
|
||
|
* @return the alpha value that will be applied to the drawable of this
|
||
|
* ImageView (between 0 and 255 inclusive, with 0 being transparent and
|
||
|
* 255 being opaque)
|
||
|
*
|
||
|
* @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 (between
|
||
|
* 0 and 255 inclusive, with 0 being transparent and 255 being opaque)
|
||
|
*
|
||
|
* @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;
|
||
|
mHasAlpha = true;
|
||
|
applyAlpha();
|
||
|
invalidate();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void applyXfermode() {
|
||
|
if (mDrawable != null && mHasXfermode) {
|
||
|
mDrawable = mDrawable.mutate();
|
||
|
mDrawable.setXfermode(mXfermode);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void applyColorFilter() {
|
||
|
if (mDrawable != null && mHasColorFilter) {
|
||
|
mDrawable = mDrawable.mutate();
|
||
|
mDrawable.setColorFilter(mColorFilter);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void applyAlpha() {
|
||
|
if (mDrawable != null && mHasAlpha) {
|
||
|
mDrawable = mDrawable.mutate();
|
||
|
mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean isOpaque() {
|
||
|
return super.isOpaque() || mDrawable != null && mXfermode == null
|
||
|
&& mDrawable.getOpacity() == PixelFormat.OPAQUE
|
||
|
&& mAlpha * mViewAlphaScale >> 8 == 255
|
||
|
&& isFilledByImage();
|
||
|
}
|
||
|
|
||
|
private boolean isFilledByImage() {
|
||
|
if (mDrawable == null) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
final Rect bounds = mDrawable.getBounds();
|
||
|
final Matrix matrix = mDrawMatrix;
|
||
|
if (matrix == null) {
|
||
|
return bounds.left <= 0 && bounds.top <= 0 && bounds.right >= getWidth()
|
||
|
&& bounds.bottom >= getHeight();
|
||
|
} else if (matrix.rectStaysRect()) {
|
||
|
final RectF boundsSrc = mTempSrc;
|
||
|
final RectF boundsDst = mTempDst;
|
||
|
boundsSrc.set(bounds);
|
||
|
matrix.mapRect(boundsDst, boundsSrc);
|
||
|
return boundsDst.left <= 0 && boundsDst.top <= 0 && boundsDst.right >= getWidth()
|
||
|
&& boundsDst.bottom >= getHeight();
|
||
|
} else {
|
||
|
// If the matrix doesn't map to a rectangle, assume the worst.
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onVisibilityAggregated(boolean isVisible) {
|
||
|
super.onVisibilityAggregated(isVisible);
|
||
|
// Only do this for new apps post-Nougat
|
||
|
if (mDrawable != null && !sCompatDrawableVisibilityDispatch) {
|
||
|
mDrawable.setVisible(isVisible, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@RemotableViewMethod
|
||
|
@Override
|
||
|
public void setVisibility(int visibility) {
|
||
|
super.setVisibility(visibility);
|
||
|
// Only do this for old apps pre-Nougat; new apps use onVisibilityAggregated
|
||
|
if (mDrawable != null && sCompatDrawableVisibilityDispatch) {
|
||
|
mDrawable.setVisible(visibility == VISIBLE, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void onAttachedToWindow() {
|
||
|
super.onAttachedToWindow();
|
||
|
// Only do this for old apps pre-Nougat; new apps use onVisibilityAggregated
|
||
|
if (mDrawable != null && sCompatDrawableVisibilityDispatch) {
|
||
|
mDrawable.setVisible(getVisibility() == VISIBLE, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void onDetachedFromWindow() {
|
||
|
super.onDetachedFromWindow();
|
||
|
// Only do this for old apps pre-Nougat; new apps use onVisibilityAggregated
|
||
|
if (mDrawable != null && sCompatDrawableVisibilityDispatch) {
|
||
|
mDrawable.setVisible(false, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public CharSequence getAccessibilityClassName() {
|
||
|
return ImageView.class.getName();
|
||
|
}
|
||
|
|
||
|
/** @hide */
|
||
|
@Override
|
||
|
protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
|
||
|
super.encodeProperties(stream);
|
||
|
stream.addProperty("layout:baseline", getBaseline());
|
||
|
}
|
||
|
|
||
|
/** @hide */
|
||
|
@Override
|
||
|
@TestApi
|
||
|
public boolean isDefaultFocusHighlightNeeded(Drawable background, Drawable foreground) {
|
||
|
final boolean lackFocusState = mDrawable == null || !mDrawable.isStateful()
|
||
|
|| !mDrawable.hasFocusStateSpecified();
|
||
|
return super.isDefaultFocusHighlightNeeded(background, foreground) && lackFocusState;
|
||
|
}
|
||
|
}
|