2173 lines
75 KiB
Java
2173 lines
75 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.graphics.drawable;
|
||
|
|
||
|
import android.annotation.NonNull;
|
||
|
import android.annotation.Nullable;
|
||
|
import android.compat.annotation.UnsupportedAppUsage;
|
||
|
import android.content.pm.ActivityInfo.Config;
|
||
|
import android.content.res.ColorStateList;
|
||
|
import android.content.res.Resources;
|
||
|
import android.content.res.Resources.Theme;
|
||
|
import android.content.res.TypedArray;
|
||
|
import android.graphics.BlendMode;
|
||
|
import android.graphics.Canvas;
|
||
|
import android.graphics.ColorFilter;
|
||
|
import android.graphics.Outline;
|
||
|
import android.graphics.PixelFormat;
|
||
|
import android.graphics.Rect;
|
||
|
import android.os.Build;
|
||
|
import android.util.AttributeSet;
|
||
|
import android.util.DisplayMetrics;
|
||
|
import android.util.LayoutDirection;
|
||
|
import android.util.Log;
|
||
|
import android.view.Gravity;
|
||
|
import android.view.View;
|
||
|
|
||
|
import com.android.internal.R;
|
||
|
|
||
|
import org.xmlpull.v1.XmlPullParser;
|
||
|
import org.xmlpull.v1.XmlPullParserException;
|
||
|
|
||
|
import java.io.IOException;
|
||
|
|
||
|
/**
|
||
|
* A Drawable that manages an array of other Drawables. These are drawn in array
|
||
|
* order, so the element with the largest index will be drawn on top.
|
||
|
* <p>
|
||
|
* It can be defined in an XML file with the <code><layer-list></code> element.
|
||
|
* Each Drawable in the layer is defined in a nested <code><item></code>.
|
||
|
* <p>
|
||
|
* For more information, see the guide to
|
||
|
* <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.
|
||
|
*
|
||
|
* @attr ref android.R.styleable#LayerDrawable_paddingMode
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_left
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_top
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_right
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_bottom
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_start
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_end
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_width
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_height
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_gravity
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_drawable
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_id
|
||
|
*/
|
||
|
public class LayerDrawable extends Drawable implements Drawable.Callback {
|
||
|
private static final String LOG_TAG = "LayerDrawable";
|
||
|
|
||
|
/**
|
||
|
* Padding mode used to nest each layer inside the padding of the previous
|
||
|
* layer.
|
||
|
*
|
||
|
* @see #setPaddingMode(int)
|
||
|
*/
|
||
|
public static final int PADDING_MODE_NEST = 0;
|
||
|
|
||
|
/**
|
||
|
* Padding mode used to stack each layer directly atop the previous layer.
|
||
|
*
|
||
|
* @see #setPaddingMode(int)
|
||
|
*/
|
||
|
public static final int PADDING_MODE_STACK = 1;
|
||
|
|
||
|
/**
|
||
|
* Value used for undefined start and end insets.
|
||
|
*
|
||
|
* @see #getLayerInsetStart(int)
|
||
|
* @see #getLayerInsetEnd(int)
|
||
|
*/
|
||
|
public static final int INSET_UNDEFINED = Integer.MIN_VALUE;
|
||
|
|
||
|
@NonNull
|
||
|
@UnsupportedAppUsage
|
||
|
LayerState mLayerState;
|
||
|
|
||
|
private int[] mPaddingL;
|
||
|
private int[] mPaddingT;
|
||
|
private int[] mPaddingR;
|
||
|
private int[] mPaddingB;
|
||
|
|
||
|
private final Rect mTmpRect = new Rect();
|
||
|
private final Rect mTmpOutRect = new Rect();
|
||
|
private final Rect mTmpContainer = new Rect();
|
||
|
private Rect mHotspotBounds;
|
||
|
private boolean mMutated;
|
||
|
|
||
|
private boolean mSuspendChildInvalidation;
|
||
|
private boolean mChildRequestedInvalidation;
|
||
|
|
||
|
/**
|
||
|
* Creates a new layer drawable with the list of specified layers.
|
||
|
*
|
||
|
* @param layers a list of drawables to use as layers in this new drawable,
|
||
|
* must be non-null
|
||
|
*/
|
||
|
public LayerDrawable(@NonNull Drawable[] layers) {
|
||
|
this(layers, null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new layer drawable with the specified list of layers and the
|
||
|
* specified constant state.
|
||
|
*
|
||
|
* @param layers The list of layers to add to this drawable.
|
||
|
* @param state The constant drawable state.
|
||
|
*/
|
||
|
LayerDrawable(@NonNull Drawable[] layers, @Nullable LayerState state) {
|
||
|
this(state, null);
|
||
|
|
||
|
if (layers == null) {
|
||
|
throw new IllegalArgumentException("layers must be non-null");
|
||
|
}
|
||
|
|
||
|
final int length = layers.length;
|
||
|
final ChildDrawable[] r = new ChildDrawable[length];
|
||
|
for (int i = 0; i < length; i++) {
|
||
|
r[i] = new ChildDrawable(mLayerState.mDensity);
|
||
|
Drawable child = layers[i];
|
||
|
r[i].mDrawable = child;
|
||
|
if (child != null) {
|
||
|
child.setCallback(this);
|
||
|
mLayerState.mChildrenChangingConfigurations |= child.getChangingConfigurations();
|
||
|
}
|
||
|
}
|
||
|
mLayerState.mNumChildren = length;
|
||
|
mLayerState.mChildren = r;
|
||
|
|
||
|
ensurePadding();
|
||
|
refreshPadding();
|
||
|
}
|
||
|
|
||
|
LayerDrawable() {
|
||
|
this((LayerState) null, null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The one constructor to rule them all. This is called by all public
|
||
|
* constructors to set the state and initialize local properties.
|
||
|
*/
|
||
|
LayerDrawable(@Nullable LayerState state, @Nullable Resources res) {
|
||
|
mLayerState = createConstantState(state, res);
|
||
|
if (mLayerState.mNumChildren > 0) {
|
||
|
ensurePadding();
|
||
|
refreshPadding();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
LayerState createConstantState(@Nullable LayerState state, @Nullable Resources res) {
|
||
|
return new LayerState(state, this, res);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
|
||
|
@NonNull AttributeSet attrs, @Nullable Theme theme)
|
||
|
throws XmlPullParserException, IOException {
|
||
|
super.inflate(r, parser, attrs, theme);
|
||
|
|
||
|
// The density may have changed since the last update. This will
|
||
|
// apply scaling to any existing constant state properties.
|
||
|
final LayerState state = mLayerState;
|
||
|
final int density = Drawable.resolveDensity(r, 0);
|
||
|
state.setDensity(density);
|
||
|
|
||
|
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawable);
|
||
|
updateStateFromTypedArray(a);
|
||
|
a.recycle();
|
||
|
|
||
|
final ChildDrawable[] array = state.mChildren;
|
||
|
final int N = state.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final ChildDrawable layer = array[i];
|
||
|
layer.setDensity(density);
|
||
|
}
|
||
|
|
||
|
inflateLayers(r, parser, attrs, theme);
|
||
|
|
||
|
ensurePadding();
|
||
|
refreshPadding();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void applyTheme(@NonNull Theme t) {
|
||
|
super.applyTheme(t);
|
||
|
|
||
|
final LayerState state = mLayerState;
|
||
|
final int density = Drawable.resolveDensity(t.getResources(), 0);
|
||
|
state.setDensity(density);
|
||
|
|
||
|
if (state.mThemeAttrs != null) {
|
||
|
final TypedArray a = t.resolveAttributes(
|
||
|
state.mThemeAttrs, R.styleable.LayerDrawable);
|
||
|
updateStateFromTypedArray(a);
|
||
|
a.recycle();
|
||
|
}
|
||
|
|
||
|
final ChildDrawable[] array = state.mChildren;
|
||
|
final int N = state.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final ChildDrawable layer = array[i];
|
||
|
layer.setDensity(density);
|
||
|
|
||
|
if (layer.mThemeAttrs != null) {
|
||
|
final TypedArray a = t.resolveAttributes(
|
||
|
layer.mThemeAttrs, R.styleable.LayerDrawableItem);
|
||
|
updateLayerFromTypedArray(layer, a);
|
||
|
a.recycle();
|
||
|
}
|
||
|
|
||
|
final Drawable d = layer.mDrawable;
|
||
|
if (d != null && d.canApplyTheme()) {
|
||
|
d.applyTheme(t);
|
||
|
|
||
|
// Update cached mask of child changing configurations.
|
||
|
state.mChildrenChangingConfigurations |= d.getChangingConfigurations();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Inflates child layers using the specified parser.
|
||
|
*/
|
||
|
private void inflateLayers(@NonNull Resources r, @NonNull XmlPullParser parser,
|
||
|
@NonNull AttributeSet attrs, @Nullable Theme theme)
|
||
|
throws XmlPullParserException, IOException {
|
||
|
final LayerState state = mLayerState;
|
||
|
|
||
|
final int innerDepth = parser.getDepth() + 1;
|
||
|
int type;
|
||
|
int depth;
|
||
|
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
|
||
|
&& ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
|
||
|
if (type != XmlPullParser.START_TAG) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (depth > innerDepth || !parser.getName().equals("item")) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
final ChildDrawable layer = new ChildDrawable(state.mDensity);
|
||
|
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawableItem);
|
||
|
updateLayerFromTypedArray(layer, a);
|
||
|
a.recycle();
|
||
|
|
||
|
// If the layer doesn't have a drawable or unresolved theme
|
||
|
// attribute for a drawable, attempt to parse one from the child
|
||
|
// element. If multiple child elements exist, we'll only use the
|
||
|
// first one.
|
||
|
if (layer.mDrawable == null && (layer.mThemeAttrs == null ||
|
||
|
layer.mThemeAttrs[R.styleable.LayerDrawableItem_drawable] == 0)) {
|
||
|
while ((type = parser.next()) == XmlPullParser.TEXT) {
|
||
|
}
|
||
|
if (type != XmlPullParser.START_TAG) {
|
||
|
throw new XmlPullParserException(parser.getPositionDescription()
|
||
|
+ ": <item> tag requires a 'drawable' attribute or "
|
||
|
+ "child tag defining a drawable");
|
||
|
}
|
||
|
|
||
|
// We found a child drawable. Take ownership.
|
||
|
layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme);
|
||
|
layer.mDrawable.setCallback(this);
|
||
|
state.mChildrenChangingConfigurations |=
|
||
|
layer.mDrawable.getChangingConfigurations();
|
||
|
}
|
||
|
|
||
|
addLayer(layer);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initializes the constant state from the values in the typed array.
|
||
|
*/
|
||
|
private void updateStateFromTypedArray(@NonNull TypedArray a) {
|
||
|
final LayerState state = mLayerState;
|
||
|
|
||
|
// Account for any configuration changes.
|
||
|
state.mChangingConfigurations |= a.getChangingConfigurations();
|
||
|
|
||
|
// Extract the theme attributes, if any.
|
||
|
state.mThemeAttrs = a.extractThemeAttrs();
|
||
|
|
||
|
final int N = a.getIndexCount();
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final int attr = a.getIndex(i);
|
||
|
switch (attr) {
|
||
|
case R.styleable.LayerDrawable_opacity:
|
||
|
state.mOpacityOverride = a.getInt(attr, state.mOpacityOverride);
|
||
|
break;
|
||
|
case R.styleable.LayerDrawable_paddingTop:
|
||
|
state.mPaddingTop = a.getDimensionPixelOffset(attr, state.mPaddingTop);
|
||
|
break;
|
||
|
case R.styleable.LayerDrawable_paddingBottom:
|
||
|
state.mPaddingBottom = a.getDimensionPixelOffset(attr, state.mPaddingBottom);
|
||
|
break;
|
||
|
case R.styleable.LayerDrawable_paddingLeft:
|
||
|
state.mPaddingLeft = a.getDimensionPixelOffset(attr, state.mPaddingLeft);
|
||
|
break;
|
||
|
case R.styleable.LayerDrawable_paddingRight:
|
||
|
state.mPaddingRight = a.getDimensionPixelOffset(attr, state.mPaddingRight);
|
||
|
break;
|
||
|
case R.styleable.LayerDrawable_paddingStart:
|
||
|
state.mPaddingStart = a.getDimensionPixelOffset(attr, state.mPaddingStart);
|
||
|
break;
|
||
|
case R.styleable.LayerDrawable_paddingEnd:
|
||
|
state.mPaddingEnd = a.getDimensionPixelOffset(attr, state.mPaddingEnd);
|
||
|
break;
|
||
|
case R.styleable.LayerDrawable_autoMirrored:
|
||
|
state.mAutoMirrored = a.getBoolean(attr, state.mAutoMirrored);
|
||
|
break;
|
||
|
case R.styleable.LayerDrawable_paddingMode:
|
||
|
state.mPaddingMode = a.getInteger(attr, state.mPaddingMode);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void updateLayerFromTypedArray(@NonNull ChildDrawable layer, @NonNull TypedArray a) {
|
||
|
final LayerState state = mLayerState;
|
||
|
|
||
|
// Account for any configuration changes.
|
||
|
state.mChildrenChangingConfigurations |= a.getChangingConfigurations();
|
||
|
|
||
|
// Extract the theme attributes, if any.
|
||
|
layer.mThemeAttrs = a.extractThemeAttrs();
|
||
|
|
||
|
final int N = a.getIndexCount();
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final int attr = a.getIndex(i);
|
||
|
switch (attr) {
|
||
|
case R.styleable.LayerDrawableItem_left:
|
||
|
layer.mInsetL = a.getDimensionPixelOffset(attr, layer.mInsetL);
|
||
|
break;
|
||
|
case R.styleable.LayerDrawableItem_top:
|
||
|
layer.mInsetT = a.getDimensionPixelOffset(attr, layer.mInsetT);
|
||
|
break;
|
||
|
case R.styleable.LayerDrawableItem_right:
|
||
|
layer.mInsetR = a.getDimensionPixelOffset(attr, layer.mInsetR);
|
||
|
break;
|
||
|
case R.styleable.LayerDrawableItem_bottom:
|
||
|
layer.mInsetB = a.getDimensionPixelOffset(attr, layer.mInsetB);
|
||
|
break;
|
||
|
case R.styleable.LayerDrawableItem_start:
|
||
|
layer.mInsetS = a.getDimensionPixelOffset(attr, layer.mInsetS);
|
||
|
break;
|
||
|
case R.styleable.LayerDrawableItem_end:
|
||
|
layer.mInsetE = a.getDimensionPixelOffset(attr, layer.mInsetE);
|
||
|
break;
|
||
|
case R.styleable.LayerDrawableItem_width:
|
||
|
layer.mWidth = a.getDimensionPixelSize(attr, layer.mWidth);
|
||
|
break;
|
||
|
case R.styleable.LayerDrawableItem_height:
|
||
|
layer.mHeight = a.getDimensionPixelSize(attr, layer.mHeight);
|
||
|
break;
|
||
|
case R.styleable.LayerDrawableItem_gravity:
|
||
|
layer.mGravity = a.getInteger(attr, layer.mGravity);
|
||
|
break;
|
||
|
case R.styleable.LayerDrawableItem_id:
|
||
|
layer.mId = a.getResourceId(attr, layer.mId);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
final Drawable dr = a.getDrawable(R.styleable.LayerDrawableItem_drawable);
|
||
|
if (dr != null) {
|
||
|
if (layer.mDrawable != null) {
|
||
|
// It's possible that a drawable was already set, in which case
|
||
|
// we should clear the callback. We may have also integrated the
|
||
|
// drawable's changing configurations, but we don't have enough
|
||
|
// information to revert that change.
|
||
|
layer.mDrawable.setCallback(null);
|
||
|
}
|
||
|
|
||
|
// Take ownership of the new drawable.
|
||
|
layer.mDrawable = dr;
|
||
|
layer.mDrawable.setCallback(this);
|
||
|
state.mChildrenChangingConfigurations |=
|
||
|
layer.mDrawable.getChangingConfigurations();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean canApplyTheme() {
|
||
|
return mLayerState.canApplyTheme() || super.canApplyTheme();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
@Override
|
||
|
public boolean isProjected() {
|
||
|
if (super.isProjected()) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
final ChildDrawable[] layers = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
Drawable childDrawable = layers[i].mDrawable;
|
||
|
if (childDrawable != null && childDrawable.isProjected()) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Adds a new layer at the end of list of layers and returns its index.
|
||
|
*
|
||
|
* @param layer The layer to add.
|
||
|
* @return The index of the layer.
|
||
|
*/
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
int addLayer(@NonNull ChildDrawable layer) {
|
||
|
final LayerState st = mLayerState;
|
||
|
final int N = st.mChildren != null ? st.mChildren.length : 0;
|
||
|
final int i = st.mNumChildren;
|
||
|
if (i >= N) {
|
||
|
final ChildDrawable[] nu = new ChildDrawable[N + 10];
|
||
|
if (i > 0) {
|
||
|
System.arraycopy(st.mChildren, 0, nu, 0, i);
|
||
|
}
|
||
|
|
||
|
st.mChildren = nu;
|
||
|
}
|
||
|
|
||
|
st.mChildren[i] = layer;
|
||
|
st.mNumChildren++;
|
||
|
st.invalidateCache();
|
||
|
return i;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add a new layer to this drawable. The new layer is identified by an id.
|
||
|
*
|
||
|
* @param dr The drawable to add as a layer.
|
||
|
* @param themeAttrs Theme attributes extracted from the layer.
|
||
|
* @param id The id of the new layer.
|
||
|
* @param left The left padding of the new layer.
|
||
|
* @param top The top padding of the new layer.
|
||
|
* @param right The right padding of the new layer.
|
||
|
* @param bottom The bottom padding of the new layer.
|
||
|
*/
|
||
|
ChildDrawable addLayer(Drawable dr, int[] themeAttrs, int id,
|
||
|
int left, int top, int right, int bottom) {
|
||
|
final ChildDrawable childDrawable = createLayer(dr);
|
||
|
childDrawable.mId = id;
|
||
|
childDrawable.mThemeAttrs = themeAttrs;
|
||
|
childDrawable.mDrawable.setAutoMirrored(isAutoMirrored());
|
||
|
childDrawable.mInsetL = left;
|
||
|
childDrawable.mInsetT = top;
|
||
|
childDrawable.mInsetR = right;
|
||
|
childDrawable.mInsetB = bottom;
|
||
|
|
||
|
addLayer(childDrawable);
|
||
|
|
||
|
mLayerState.mChildrenChangingConfigurations |= dr.getChangingConfigurations();
|
||
|
dr.setCallback(this);
|
||
|
|
||
|
return childDrawable;
|
||
|
}
|
||
|
|
||
|
private ChildDrawable createLayer(Drawable dr) {
|
||
|
final ChildDrawable layer = new ChildDrawable(mLayerState.mDensity);
|
||
|
layer.mDrawable = dr;
|
||
|
return layer;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Adds a new layer containing the specified {@code drawable} to the end of
|
||
|
* the layer list and returns its index.
|
||
|
*
|
||
|
* @param dr The drawable to add as a new layer.
|
||
|
* @return The index of the new layer.
|
||
|
*/
|
||
|
public int addLayer(Drawable dr) {
|
||
|
final ChildDrawable layer = createLayer(dr);
|
||
|
final int index = addLayer(layer);
|
||
|
ensurePadding();
|
||
|
refreshChildPadding(index, layer);
|
||
|
return index;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Looks for a layer with the given ID and returns its {@link Drawable}.
|
||
|
* <p>
|
||
|
* If multiple layers are found for the given ID, returns the
|
||
|
* {@link Drawable} for the matching layer at the highest index.
|
||
|
*
|
||
|
* @param id The layer ID to search for.
|
||
|
* @return The {@link Drawable} for the highest-indexed layer that has the
|
||
|
* given ID, or null if not found.
|
||
|
*/
|
||
|
public Drawable findDrawableByLayerId(int id) {
|
||
|
final ChildDrawable[] layers = mLayerState.mChildren;
|
||
|
for (int i = mLayerState.mNumChildren - 1; i >= 0; i--) {
|
||
|
if (layers[i].mId == id) {
|
||
|
return layers[i].mDrawable;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the ID of a layer.
|
||
|
*
|
||
|
* @param index The index of the layer to modify, must be in the range
|
||
|
* {@code 0...getNumberOfLayers()-1}.
|
||
|
* @param id The id to assign to the layer.
|
||
|
*
|
||
|
* @see #getId(int)
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_id
|
||
|
*/
|
||
|
public void setId(int index, int id) {
|
||
|
mLayerState.mChildren[index].mId = id;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the ID of the specified layer.
|
||
|
*
|
||
|
* @param index The index of the layer, must be in the range
|
||
|
* {@code 0...getNumberOfLayers()-1}.
|
||
|
* @return The id of the layer or {@link android.view.View#NO_ID} if the
|
||
|
* layer has no id.
|
||
|
*
|
||
|
* @see #setId(int, int)
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_id
|
||
|
*/
|
||
|
public int getId(int index) {
|
||
|
if (index >= mLayerState.mNumChildren) {
|
||
|
throw new IndexOutOfBoundsException();
|
||
|
}
|
||
|
return mLayerState.mChildren[index].mId;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the number of layers contained within this layer drawable.
|
||
|
*
|
||
|
* @return The number of layers.
|
||
|
*/
|
||
|
public int getNumberOfLayers() {
|
||
|
return mLayerState.mNumChildren;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Replaces the {@link Drawable} for the layer with the given id.
|
||
|
*
|
||
|
* @param id The layer ID to search for.
|
||
|
* @param drawable The replacement {@link Drawable}.
|
||
|
* @return Whether the {@link Drawable} was replaced (could return false if
|
||
|
* the id was not found).
|
||
|
*/
|
||
|
public boolean setDrawableByLayerId(int id, Drawable drawable) {
|
||
|
final int index = findIndexByLayerId(id);
|
||
|
if (index < 0) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
setDrawable(index, drawable);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the layer with the specified {@code id}.
|
||
|
* <p>
|
||
|
* If multiple layers have the same ID, returns the layer with the lowest
|
||
|
* index.
|
||
|
*
|
||
|
* @param id The ID of the layer to return.
|
||
|
* @return The index of the layer with the specified ID.
|
||
|
*/
|
||
|
public int findIndexByLayerId(int id) {
|
||
|
final ChildDrawable[] layers = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final ChildDrawable childDrawable = layers[i];
|
||
|
if (childDrawable.mId == id) {
|
||
|
return i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the drawable for the layer at the specified index.
|
||
|
*
|
||
|
* @param index The index of the layer to modify, must be in the range
|
||
|
* {@code 0...getNumberOfLayers()-1}.
|
||
|
* @param drawable The drawable to set for the layer.
|
||
|
*
|
||
|
* @see #getDrawable(int)
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_drawable
|
||
|
*/
|
||
|
public void setDrawable(int index, Drawable drawable) {
|
||
|
if (index >= mLayerState.mNumChildren) {
|
||
|
throw new IndexOutOfBoundsException();
|
||
|
}
|
||
|
|
||
|
final ChildDrawable[] layers = mLayerState.mChildren;
|
||
|
final ChildDrawable childDrawable = layers[index];
|
||
|
if (childDrawable.mDrawable != null) {
|
||
|
if (drawable != null) {
|
||
|
final Rect bounds = childDrawable.mDrawable.getBounds();
|
||
|
drawable.setBounds(bounds);
|
||
|
}
|
||
|
|
||
|
childDrawable.mDrawable.setCallback(null);
|
||
|
}
|
||
|
|
||
|
if (drawable != null) {
|
||
|
drawable.setCallback(this);
|
||
|
}
|
||
|
|
||
|
childDrawable.mDrawable = drawable;
|
||
|
mLayerState.invalidateCache();
|
||
|
|
||
|
refreshChildPadding(index, childDrawable);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the drawable for the layer at the specified index.
|
||
|
*
|
||
|
* @param index The index of the layer, must be in the range
|
||
|
* {@code 0...getNumberOfLayers()-1}.
|
||
|
* @return The {@link Drawable} at the specified layer index.
|
||
|
*
|
||
|
* @see #setDrawable(int, Drawable)
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_drawable
|
||
|
*/
|
||
|
public Drawable getDrawable(int index) {
|
||
|
if (index >= mLayerState.mNumChildren) {
|
||
|
throw new IndexOutOfBoundsException();
|
||
|
}
|
||
|
return mLayerState.mChildren[index].mDrawable;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets an explicit size for the specified layer.
|
||
|
* <p>
|
||
|
* <strong>Note:</strong> Setting an explicit layer size changes the
|
||
|
* default layer gravity behavior. See {@link #setLayerGravity(int, int)}
|
||
|
* for more information.
|
||
|
*
|
||
|
* @param index the index of the layer to adjust
|
||
|
* @param w width in pixels, or -1 to use the intrinsic width
|
||
|
* @param h height in pixels, or -1 to use the intrinsic height
|
||
|
* @see #getLayerWidth(int)
|
||
|
* @see #getLayerHeight(int)
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_width
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_height
|
||
|
*/
|
||
|
public void setLayerSize(int index, int w, int h) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
childDrawable.mWidth = w;
|
||
|
childDrawable.mHeight = h;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param index the index of the layer to adjust
|
||
|
* @param w width in pixels, or -1 to use the intrinsic width
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_width
|
||
|
*/
|
||
|
public void setLayerWidth(int index, int w) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
childDrawable.mWidth = w;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param index the index of the drawable to adjust
|
||
|
* @return the explicit width of the layer, or -1 if not specified
|
||
|
* @see #setLayerSize(int, int, int)
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_width
|
||
|
*/
|
||
|
public int getLayerWidth(int index) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
return childDrawable.mWidth;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param index the index of the layer to adjust
|
||
|
* @param h height in pixels, or -1 to use the intrinsic height
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_height
|
||
|
*/
|
||
|
public void setLayerHeight(int index, int h) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
childDrawable.mHeight = h;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param index the index of the drawable to adjust
|
||
|
* @return the explicit height of the layer, or -1 if not specified
|
||
|
* @see #setLayerSize(int, int, int)
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_height
|
||
|
*/
|
||
|
public int getLayerHeight(int index) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
return childDrawable.mHeight;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the gravity used to position or stretch the specified layer within
|
||
|
* its container. Gravity is applied after any layer insets (see
|
||
|
* {@link #setLayerInset(int, int, int, int, int)}) or padding (see
|
||
|
* {@link #setPaddingMode(int)}).
|
||
|
* <p>
|
||
|
* If gravity is specified as {@link Gravity#NO_GRAVITY}, the default
|
||
|
* behavior depends on whether an explicit width or height has been set
|
||
|
* (see {@link #setLayerSize(int, int, int)}), If a dimension is not set,
|
||
|
* gravity in that direction defaults to {@link Gravity#FILL_HORIZONTAL} or
|
||
|
* {@link Gravity#FILL_VERTICAL}; otherwise, gravity in that direction
|
||
|
* defaults to {@link Gravity#LEFT} or {@link Gravity#TOP}.
|
||
|
*
|
||
|
* @param index the index of the drawable to adjust
|
||
|
* @param gravity the gravity to set for the layer
|
||
|
*
|
||
|
* @see #getLayerGravity(int)
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_gravity
|
||
|
*/
|
||
|
public void setLayerGravity(int index, int gravity) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
childDrawable.mGravity = gravity;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param index the index of the layer
|
||
|
* @return the gravity used to position or stretch the specified layer
|
||
|
* within its container
|
||
|
*
|
||
|
* @see #setLayerGravity(int, int)
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_gravity
|
||
|
*/
|
||
|
public int getLayerGravity(int index) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
return childDrawable.mGravity;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Specifies the insets in pixels for the drawable at the specified index.
|
||
|
*
|
||
|
* @param index the index of the drawable to adjust
|
||
|
* @param l number of pixels to add to the left bound
|
||
|
* @param t number of pixels to add to the top bound
|
||
|
* @param r number of pixels to subtract from the right bound
|
||
|
* @param b number of pixels to subtract from the bottom bound
|
||
|
*
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_left
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_top
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_right
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_bottom
|
||
|
*/
|
||
|
public void setLayerInset(int index, int l, int t, int r, int b) {
|
||
|
setLayerInsetInternal(index, l, t, r, b, INSET_UNDEFINED, INSET_UNDEFINED);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Specifies the relative insets in pixels for the drawable at the
|
||
|
* specified index.
|
||
|
*
|
||
|
* @param index the index of the layer to adjust
|
||
|
* @param s number of pixels to inset from the start bound
|
||
|
* @param t number of pixels to inset from the top bound
|
||
|
* @param e number of pixels to inset from the end bound
|
||
|
* @param b number of pixels to inset from the bottom bound
|
||
|
*
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_start
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_top
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_end
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_bottom
|
||
|
*/
|
||
|
public void setLayerInsetRelative(int index, int s, int t, int e, int b) {
|
||
|
setLayerInsetInternal(index, 0, t, 0, b, s, e);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param index the index of the layer to adjust
|
||
|
* @param l number of pixels to inset from the left bound
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_left
|
||
|
*/
|
||
|
public void setLayerInsetLeft(int index, int l) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
childDrawable.mInsetL = l;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param index the index of the layer
|
||
|
* @return number of pixels to inset from the left bound
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_left
|
||
|
*/
|
||
|
public int getLayerInsetLeft(int index) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
return childDrawable.mInsetL;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param index the index of the layer to adjust
|
||
|
* @param r number of pixels to inset from the right bound
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_right
|
||
|
*/
|
||
|
public void setLayerInsetRight(int index, int r) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
childDrawable.mInsetR = r;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param index the index of the layer
|
||
|
* @return number of pixels to inset from the right bound
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_right
|
||
|
*/
|
||
|
public int getLayerInsetRight(int index) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
return childDrawable.mInsetR;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param index the index of the layer to adjust
|
||
|
* @param t number of pixels to inset from the top bound
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_top
|
||
|
*/
|
||
|
public void setLayerInsetTop(int index, int t) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
childDrawable.mInsetT = t;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param index the index of the layer
|
||
|
* @return number of pixels to inset from the top bound
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_top
|
||
|
*/
|
||
|
public int getLayerInsetTop(int index) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
return childDrawable.mInsetT;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param index the index of the layer to adjust
|
||
|
* @param b number of pixels to inset from the bottom bound
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_bottom
|
||
|
*/
|
||
|
public void setLayerInsetBottom(int index, int b) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
childDrawable.mInsetB = b;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param index the index of the layer
|
||
|
* @return number of pixels to inset from the bottom bound
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_bottom
|
||
|
*/
|
||
|
public int getLayerInsetBottom(int index) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
return childDrawable.mInsetB;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param index the index of the layer to adjust
|
||
|
* @param s number of pixels to inset from the start bound
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_start
|
||
|
*/
|
||
|
public void setLayerInsetStart(int index, int s) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
childDrawable.mInsetS = s;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param index the index of the layer
|
||
|
* @return the number of pixels to inset from the start bound, or
|
||
|
* {@link #INSET_UNDEFINED} if not specified
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_start
|
||
|
*/
|
||
|
public int getLayerInsetStart(int index) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
return childDrawable.mInsetS;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param index the index of the layer to adjust
|
||
|
* @param e number of pixels to inset from the end bound, or
|
||
|
* {@link #INSET_UNDEFINED} if not specified
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_end
|
||
|
*/
|
||
|
public void setLayerInsetEnd(int index, int e) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
childDrawable.mInsetE = e;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param index the index of the layer
|
||
|
* @return number of pixels to inset from the end bound
|
||
|
* @attr ref android.R.styleable#LayerDrawableItem_end
|
||
|
*/
|
||
|
public int getLayerInsetEnd(int index) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
return childDrawable.mInsetE;
|
||
|
}
|
||
|
|
||
|
private void setLayerInsetInternal(int index, int l, int t, int r, int b, int s, int e) {
|
||
|
final ChildDrawable childDrawable = mLayerState.mChildren[index];
|
||
|
childDrawable.mInsetL = l;
|
||
|
childDrawable.mInsetT = t;
|
||
|
childDrawable.mInsetR = r;
|
||
|
childDrawable.mInsetB = b;
|
||
|
childDrawable.mInsetS = s;
|
||
|
childDrawable.mInsetE = e;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Specifies how layer padding should affect the bounds of subsequent
|
||
|
* layers. The default value is {@link #PADDING_MODE_NEST}.
|
||
|
*
|
||
|
* @param mode padding mode, one of:
|
||
|
* <ul>
|
||
|
* <li>{@link #PADDING_MODE_NEST} to nest each layer inside the
|
||
|
* padding of the previous layer
|
||
|
* <li>{@link #PADDING_MODE_STACK} to stack each layer directly
|
||
|
* atop the previous layer
|
||
|
* </ul>
|
||
|
*
|
||
|
* @see #getPaddingMode()
|
||
|
* @attr ref android.R.styleable#LayerDrawable_paddingMode
|
||
|
*/
|
||
|
public void setPaddingMode(int mode) {
|
||
|
if (mLayerState.mPaddingMode != mode) {
|
||
|
mLayerState.mPaddingMode = mode;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return the current padding mode
|
||
|
*
|
||
|
* @see #setPaddingMode(int)
|
||
|
* @attr ref android.R.styleable#LayerDrawable_paddingMode
|
||
|
*/
|
||
|
public int getPaddingMode() {
|
||
|
return mLayerState.mPaddingMode;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Temporarily suspends child invalidation.
|
||
|
*
|
||
|
* @see #resumeChildInvalidation()
|
||
|
*/
|
||
|
private void suspendChildInvalidation() {
|
||
|
mSuspendChildInvalidation = true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Resumes child invalidation after suspension, immediately performing an
|
||
|
* invalidation if one was requested by a child during suspension.
|
||
|
*
|
||
|
* @see #suspendChildInvalidation()
|
||
|
*/
|
||
|
private void resumeChildInvalidation() {
|
||
|
mSuspendChildInvalidation = false;
|
||
|
|
||
|
if (mChildRequestedInvalidation) {
|
||
|
mChildRequestedInvalidation = false;
|
||
|
invalidateSelf();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void invalidateDrawable(@NonNull Drawable who) {
|
||
|
if (mSuspendChildInvalidation) {
|
||
|
mChildRequestedInvalidation = true;
|
||
|
} else {
|
||
|
// This may have been called as the result of a tint changing, in
|
||
|
// which case we may need to refresh the cached statefulness or
|
||
|
// opacity.
|
||
|
mLayerState.invalidateCache();
|
||
|
|
||
|
invalidateSelf();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
|
||
|
scheduleSelf(what, when);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
|
||
|
unscheduleSelf(what);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void draw(Canvas canvas) {
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null) {
|
||
|
dr.draw(canvas);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public @Config int getChangingConfigurations() {
|
||
|
return super.getChangingConfigurations() | mLayerState.getChangingConfigurations();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean getPadding(Rect padding) {
|
||
|
final LayerState layerState = mLayerState;
|
||
|
if (layerState.mPaddingMode == PADDING_MODE_NEST) {
|
||
|
computeNestedPadding(padding);
|
||
|
} else {
|
||
|
computeStackedPadding(padding);
|
||
|
}
|
||
|
|
||
|
final int paddingT = layerState.mPaddingTop;
|
||
|
final int paddingB = layerState.mPaddingBottom;
|
||
|
|
||
|
// Resolve padding for RTL. Relative padding overrides absolute
|
||
|
// padding.
|
||
|
final boolean isLayoutRtl = getLayoutDirection() == LayoutDirection.RTL;
|
||
|
final int paddingRtlL = isLayoutRtl ? layerState.mPaddingEnd : layerState.mPaddingStart;
|
||
|
final int paddingRtlR = isLayoutRtl ? layerState.mPaddingStart : layerState.mPaddingEnd;
|
||
|
final int paddingL = paddingRtlL >= 0 ? paddingRtlL : layerState.mPaddingLeft;
|
||
|
final int paddingR = paddingRtlR >= 0 ? paddingRtlR : layerState.mPaddingRight;
|
||
|
|
||
|
// If padding was explicitly specified (e.g. not -1) then override the
|
||
|
// computed padding in that dimension.
|
||
|
if (paddingL >= 0) {
|
||
|
padding.left = paddingL;
|
||
|
}
|
||
|
|
||
|
if (paddingT >= 0) {
|
||
|
padding.top = paddingT;
|
||
|
}
|
||
|
|
||
|
if (paddingR >= 0) {
|
||
|
padding.right = paddingR;
|
||
|
}
|
||
|
|
||
|
if (paddingB >= 0) {
|
||
|
padding.bottom = paddingB;
|
||
|
}
|
||
|
|
||
|
return padding.left != 0 || padding.top != 0 || padding.right != 0 || padding.bottom != 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the absolute padding.
|
||
|
* <p>
|
||
|
* If padding in a dimension is specified as {@code -1}, the resolved
|
||
|
* padding will use the value computed according to the padding mode (see
|
||
|
* {@link #setPaddingMode(int)}).
|
||
|
* <p>
|
||
|
* Calling this method clears any relative padding values previously set
|
||
|
* using {@link #setPaddingRelative(int, int, int, int)}.
|
||
|
*
|
||
|
* @param left the left padding in pixels, or -1 to use computed padding
|
||
|
* @param top the top padding in pixels, or -1 to use computed padding
|
||
|
* @param right the right padding in pixels, or -1 to use computed padding
|
||
|
* @param bottom the bottom padding in pixels, or -1 to use computed
|
||
|
* padding
|
||
|
* @attr ref android.R.styleable#LayerDrawable_paddingLeft
|
||
|
* @attr ref android.R.styleable#LayerDrawable_paddingTop
|
||
|
* @attr ref android.R.styleable#LayerDrawable_paddingRight
|
||
|
* @attr ref android.R.styleable#LayerDrawable_paddingBottom
|
||
|
* @see #setPaddingRelative(int, int, int, int)
|
||
|
*/
|
||
|
public void setPadding(int left, int top, int right, int bottom) {
|
||
|
final LayerState layerState = mLayerState;
|
||
|
layerState.mPaddingLeft = left;
|
||
|
layerState.mPaddingTop = top;
|
||
|
layerState.mPaddingRight = right;
|
||
|
layerState.mPaddingBottom = bottom;
|
||
|
|
||
|
// Clear relative padding values.
|
||
|
layerState.mPaddingStart = -1;
|
||
|
layerState.mPaddingEnd = -1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the relative padding.
|
||
|
* <p>
|
||
|
* If padding in a dimension is specified as {@code -1}, the resolved
|
||
|
* padding will use the value computed according to the padding mode (see
|
||
|
* {@link #setPaddingMode(int)}).
|
||
|
* <p>
|
||
|
* Calling this method clears any absolute padding values previously set
|
||
|
* using {@link #setPadding(int, int, int, int)}.
|
||
|
*
|
||
|
* @param start the start padding in pixels, or -1 to use computed padding
|
||
|
* @param top the top padding in pixels, or -1 to use computed padding
|
||
|
* @param end the end padding in pixels, or -1 to use computed padding
|
||
|
* @param bottom the bottom padding in pixels, or -1 to use computed
|
||
|
* padding
|
||
|
* @attr ref android.R.styleable#LayerDrawable_paddingStart
|
||
|
* @attr ref android.R.styleable#LayerDrawable_paddingTop
|
||
|
* @attr ref android.R.styleable#LayerDrawable_paddingEnd
|
||
|
* @attr ref android.R.styleable#LayerDrawable_paddingBottom
|
||
|
* @see #setPadding(int, int, int, int)
|
||
|
*/
|
||
|
public void setPaddingRelative(int start, int top, int end, int bottom) {
|
||
|
final LayerState layerState = mLayerState;
|
||
|
layerState.mPaddingStart = start;
|
||
|
layerState.mPaddingTop = top;
|
||
|
layerState.mPaddingEnd = end;
|
||
|
layerState.mPaddingBottom = bottom;
|
||
|
|
||
|
// Clear absolute padding values.
|
||
|
layerState.mPaddingLeft = -1;
|
||
|
layerState.mPaddingRight = -1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the left padding in pixels.
|
||
|
* <p>
|
||
|
* A return value of {@code -1} means there is no explicit padding set for
|
||
|
* this dimension. As a result, the value for this dimension returned by
|
||
|
* {@link #getPadding(Rect)} will be computed from the child layers
|
||
|
* according to the padding mode (see {@link #getPaddingMode()}.
|
||
|
*
|
||
|
* @return the left padding in pixels, or -1 if not explicitly specified
|
||
|
* @see #setPadding(int, int, int, int)
|
||
|
* @see #getPadding(Rect)
|
||
|
*/
|
||
|
public int getLeftPadding() {
|
||
|
return mLayerState.mPaddingLeft;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the right padding in pixels.
|
||
|
* <p>
|
||
|
* A return value of {@code -1} means there is no explicit padding set for
|
||
|
* this dimension. As a result, the value for this dimension returned by
|
||
|
* {@link #getPadding(Rect)} will be computed from the child layers
|
||
|
* according to the padding mode (see {@link #getPaddingMode()}.
|
||
|
*
|
||
|
* @return the right padding in pixels, or -1 if not explicitly specified
|
||
|
* @see #setPadding(int, int, int, int)
|
||
|
* @see #getPadding(Rect)
|
||
|
*/
|
||
|
public int getRightPadding() {
|
||
|
return mLayerState.mPaddingRight;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the start padding in pixels.
|
||
|
* <p>
|
||
|
* A return value of {@code -1} means there is no explicit padding set for
|
||
|
* this dimension. As a result, the value for this dimension returned by
|
||
|
* {@link #getPadding(Rect)} will be computed from the child layers
|
||
|
* according to the padding mode (see {@link #getPaddingMode()}.
|
||
|
*
|
||
|
* @return the start padding in pixels, or -1 if not explicitly specified
|
||
|
* @see #setPaddingRelative(int, int, int, int)
|
||
|
* @see #getPadding(Rect)
|
||
|
*/
|
||
|
public int getStartPadding() {
|
||
|
return mLayerState.mPaddingStart;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the end padding in pixels.
|
||
|
* <p>
|
||
|
* A return value of {@code -1} means there is no explicit padding set for
|
||
|
* this dimension. As a result, the value for this dimension returned by
|
||
|
* {@link #getPadding(Rect)} will be computed from the child layers
|
||
|
* according to the padding mode (see {@link #getPaddingMode()}.
|
||
|
*
|
||
|
* @return the end padding in pixels, or -1 if not explicitly specified
|
||
|
* @see #setPaddingRelative(int, int, int, int)
|
||
|
* @see #getPadding(Rect)
|
||
|
*/
|
||
|
public int getEndPadding() {
|
||
|
return mLayerState.mPaddingEnd;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the top padding in pixels.
|
||
|
* <p>
|
||
|
* A return value of {@code -1} means there is no explicit padding set for
|
||
|
* this dimension. As a result, the value for this dimension returned by
|
||
|
* {@link #getPadding(Rect)} will be computed from the child layers
|
||
|
* according to the padding mode (see {@link #getPaddingMode()}.
|
||
|
*
|
||
|
* @return the top padding in pixels, or -1 if not explicitly specified
|
||
|
* @see #setPadding(int, int, int, int)
|
||
|
* @see #setPaddingRelative(int, int, int, int)
|
||
|
* @see #getPadding(Rect)
|
||
|
*/
|
||
|
public int getTopPadding() {
|
||
|
return mLayerState.mPaddingTop;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the bottom padding in pixels.
|
||
|
* <p>
|
||
|
* A return value of {@code -1} means there is no explicit padding set for
|
||
|
* this dimension. As a result, the value for this dimension returned by
|
||
|
* {@link #getPadding(Rect)} will be computed from the child layers
|
||
|
* according to the padding mode (see {@link #getPaddingMode()}.
|
||
|
*
|
||
|
* @return the bottom padding in pixels, or -1 if not explicitly specified
|
||
|
* @see #setPadding(int, int, int, int)
|
||
|
* @see #setPaddingRelative(int, int, int, int)
|
||
|
* @see #getPadding(Rect)
|
||
|
*/
|
||
|
public int getBottomPadding() {
|
||
|
return mLayerState.mPaddingBottom;
|
||
|
}
|
||
|
|
||
|
private void computeNestedPadding(Rect padding) {
|
||
|
padding.left = 0;
|
||
|
padding.top = 0;
|
||
|
padding.right = 0;
|
||
|
padding.bottom = 0;
|
||
|
|
||
|
// Add all the padding.
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
refreshChildPadding(i, array[i]);
|
||
|
|
||
|
padding.left += mPaddingL[i];
|
||
|
padding.top += mPaddingT[i];
|
||
|
padding.right += mPaddingR[i];
|
||
|
padding.bottom += mPaddingB[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void computeStackedPadding(Rect padding) {
|
||
|
padding.left = 0;
|
||
|
padding.top = 0;
|
||
|
padding.right = 0;
|
||
|
padding.bottom = 0;
|
||
|
|
||
|
// Take the max padding.
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
refreshChildPadding(i, array[i]);
|
||
|
|
||
|
padding.left = Math.max(padding.left, mPaddingL[i]);
|
||
|
padding.top = Math.max(padding.top, mPaddingT[i]);
|
||
|
padding.right = Math.max(padding.right, mPaddingR[i]);
|
||
|
padding.bottom = Math.max(padding.bottom, mPaddingB[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Populates <code>outline</code> with the first available (non-empty) layer outline.
|
||
|
*
|
||
|
* @param outline Outline in which to place the first available layer outline
|
||
|
*/
|
||
|
@Override
|
||
|
public void getOutline(@NonNull Outline outline) {
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null) {
|
||
|
dr.getOutline(outline);
|
||
|
if (!outline.isEmpty()) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setHotspot(float x, float y) {
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null) {
|
||
|
dr.setHotspot(x, y);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setHotspotBounds(int left, int top, int right, int bottom) {
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null) {
|
||
|
dr.setHotspotBounds(left, top, right, bottom);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (mHotspotBounds == null) {
|
||
|
mHotspotBounds = new Rect(left, top, right, bottom);
|
||
|
} else {
|
||
|
mHotspotBounds.set(left, top, right, bottom);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void getHotspotBounds(Rect outRect) {
|
||
|
if (mHotspotBounds != null) {
|
||
|
outRect.set(mHotspotBounds);
|
||
|
} else {
|
||
|
super.getHotspotBounds(outRect);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean setVisible(boolean visible, boolean restart) {
|
||
|
final boolean changed = super.setVisible(visible, restart);
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null) {
|
||
|
dr.setVisible(visible, restart);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return changed;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setDither(boolean dither) {
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null) {
|
||
|
dr.setDither(dither);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setAlpha(int alpha) {
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null) {
|
||
|
dr.setAlpha(alpha);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getAlpha() {
|
||
|
final Drawable dr = getFirstNonNullDrawable();
|
||
|
if (dr != null) {
|
||
|
return dr.getAlpha();
|
||
|
} else {
|
||
|
return super.getAlpha();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setColorFilter(ColorFilter colorFilter) {
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null) {
|
||
|
dr.setColorFilter(colorFilter);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setTintList(ColorStateList tint) {
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null) {
|
||
|
dr.setTintList(tint);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setTintBlendMode(@NonNull BlendMode blendMode) {
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null) {
|
||
|
dr.setTintBlendMode(blendMode);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private Drawable getFirstNonNullDrawable() {
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null) {
|
||
|
return dr;
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the opacity of this drawable directly instead of collecting the
|
||
|
* states from the layers.
|
||
|
*
|
||
|
* @param opacity The opacity to use, or {@link PixelFormat#UNKNOWN
|
||
|
* PixelFormat.UNKNOWN} for the default behavior
|
||
|
* @see PixelFormat#UNKNOWN
|
||
|
* @see PixelFormat#TRANSLUCENT
|
||
|
* @see PixelFormat#TRANSPARENT
|
||
|
* @see PixelFormat#OPAQUE
|
||
|
*/
|
||
|
public void setOpacity(int opacity) {
|
||
|
mLayerState.mOpacityOverride = opacity;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getOpacity() {
|
||
|
if (mLayerState.mOpacityOverride != PixelFormat.UNKNOWN) {
|
||
|
return mLayerState.mOpacityOverride;
|
||
|
}
|
||
|
return mLayerState.getOpacity();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setAutoMirrored(boolean mirrored) {
|
||
|
mLayerState.mAutoMirrored = mirrored;
|
||
|
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null) {
|
||
|
dr.setAutoMirrored(mirrored);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean isAutoMirrored() {
|
||
|
return mLayerState.mAutoMirrored;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void jumpToCurrentState() {
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null) {
|
||
|
dr.jumpToCurrentState();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean isStateful() {
|
||
|
return mLayerState.isStateful();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean hasFocusStateSpecified() {
|
||
|
return mLayerState.hasFocusStateSpecified();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected boolean onStateChange(int[] state) {
|
||
|
boolean changed = false;
|
||
|
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null && dr.isStateful() && dr.setState(state)) {
|
||
|
refreshChildPadding(i, array[i]);
|
||
|
changed = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (changed) {
|
||
|
updateLayerBounds(getBounds());
|
||
|
}
|
||
|
|
||
|
return changed;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected boolean onLevelChange(int level) {
|
||
|
boolean changed = false;
|
||
|
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null && dr.setLevel(level)) {
|
||
|
refreshChildPadding(i, array[i]);
|
||
|
changed = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (changed) {
|
||
|
updateLayerBounds(getBounds());
|
||
|
}
|
||
|
|
||
|
return changed;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void onBoundsChange(Rect bounds) {
|
||
|
updateLayerBounds(bounds);
|
||
|
}
|
||
|
|
||
|
private void updateLayerBounds(Rect bounds) {
|
||
|
try {
|
||
|
suspendChildInvalidation();
|
||
|
updateLayerBoundsInternal(bounds);
|
||
|
} finally {
|
||
|
resumeChildInvalidation();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void updateLayerBoundsInternal(Rect bounds) {
|
||
|
int paddingL = 0;
|
||
|
int paddingT = 0;
|
||
|
int paddingR = 0;
|
||
|
int paddingB = 0;
|
||
|
|
||
|
final Rect outRect = mTmpOutRect;
|
||
|
final int layoutDirection = getLayoutDirection();
|
||
|
final boolean isLayoutRtl = layoutDirection == LayoutDirection.RTL;
|
||
|
final boolean isPaddingNested = mLayerState.mPaddingMode == PADDING_MODE_NEST;
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
|
||
|
for (int i = 0, count = mLayerState.mNumChildren; i < count; i++) {
|
||
|
final ChildDrawable r = array[i];
|
||
|
final Drawable d = r.mDrawable;
|
||
|
if (d == null) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
final int insetT = r.mInsetT;
|
||
|
final int insetB = r.mInsetB;
|
||
|
|
||
|
// Resolve insets for RTL. Relative insets override absolute
|
||
|
// insets.
|
||
|
final int insetRtlL = isLayoutRtl ? r.mInsetE : r.mInsetS;
|
||
|
final int insetRtlR = isLayoutRtl ? r.mInsetS : r.mInsetE;
|
||
|
final int insetL = insetRtlL == INSET_UNDEFINED ? r.mInsetL : insetRtlL;
|
||
|
final int insetR = insetRtlR == INSET_UNDEFINED ? r.mInsetR : insetRtlR;
|
||
|
|
||
|
// Establish containing region based on aggregate padding and
|
||
|
// requested insets for the current layer.
|
||
|
final Rect container = mTmpContainer;
|
||
|
container.set(bounds.left + insetL + paddingL, bounds.top + insetT + paddingT,
|
||
|
bounds.right - insetR - paddingR, bounds.bottom - insetB - paddingB);
|
||
|
|
||
|
// Compute a reasonable default gravity based on the intrinsic and
|
||
|
// explicit dimensions, if specified.
|
||
|
final int intrinsicW = d.getIntrinsicWidth();
|
||
|
final int intrinsicH = d.getIntrinsicHeight();
|
||
|
final int layerW = r.mWidth;
|
||
|
final int layerH = r.mHeight;
|
||
|
final int gravity = resolveGravity(r.mGravity, layerW, layerH, intrinsicW, intrinsicH);
|
||
|
|
||
|
// Explicit dimensions override intrinsic dimensions.
|
||
|
final int resolvedW = layerW < 0 ? intrinsicW : layerW;
|
||
|
final int resolvedH = layerH < 0 ? intrinsicH : layerH;
|
||
|
Gravity.apply(gravity, resolvedW, resolvedH, container, outRect, layoutDirection);
|
||
|
d.setBounds(outRect);
|
||
|
|
||
|
if (isPaddingNested) {
|
||
|
paddingL += mPaddingL[i];
|
||
|
paddingR += mPaddingR[i];
|
||
|
paddingT += mPaddingT[i];
|
||
|
paddingB += mPaddingB[i];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Resolves layer gravity given explicit gravity and dimensions.
|
||
|
* <p>
|
||
|
* If the client hasn't specified a gravity but has specified an explicit
|
||
|
* dimension, defaults to START or TOP. Otherwise, defaults to FILL to
|
||
|
* preserve legacy behavior.
|
||
|
*
|
||
|
* @param gravity layer gravity
|
||
|
* @param width width of the layer if set, -1 otherwise
|
||
|
* @param height height of the layer if set, -1 otherwise
|
||
|
* @return the default gravity for the layer
|
||
|
*/
|
||
|
private static int resolveGravity(int gravity, int width, int height,
|
||
|
int intrinsicWidth, int intrinsicHeight) {
|
||
|
if (!Gravity.isHorizontal(gravity)) {
|
||
|
if (width < 0) {
|
||
|
gravity |= Gravity.FILL_HORIZONTAL;
|
||
|
} else {
|
||
|
gravity |= Gravity.START;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!Gravity.isVertical(gravity)) {
|
||
|
if (height < 0) {
|
||
|
gravity |= Gravity.FILL_VERTICAL;
|
||
|
} else {
|
||
|
gravity |= Gravity.TOP;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If a dimension if not specified, either implicitly or explicitly,
|
||
|
// force FILL for that dimension's gravity. This ensures that colors
|
||
|
// are handled correctly and ensures backward compatibility.
|
||
|
if (width < 0 && intrinsicWidth < 0) {
|
||
|
gravity |= Gravity.FILL_HORIZONTAL;
|
||
|
}
|
||
|
|
||
|
if (height < 0 && intrinsicHeight < 0) {
|
||
|
gravity |= Gravity.FILL_VERTICAL;
|
||
|
}
|
||
|
|
||
|
return gravity;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getIntrinsicWidth() {
|
||
|
int width = -1;
|
||
|
int padL = 0;
|
||
|
int padR = 0;
|
||
|
|
||
|
final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
|
||
|
final boolean isLayoutRtl = getLayoutDirection() == LayoutDirection.RTL;
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final ChildDrawable r = array[i];
|
||
|
if (r.mDrawable == null) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Take the resolved layout direction into account. If start / end
|
||
|
// padding are defined, they will be resolved (hence overriding) to
|
||
|
// left / right or right / left depending on the resolved layout
|
||
|
// direction. If start / end padding are not defined, use the
|
||
|
// left / right ones.
|
||
|
final int insetRtlL = isLayoutRtl ? r.mInsetE : r.mInsetS;
|
||
|
final int insetRtlR = isLayoutRtl ? r.mInsetS : r.mInsetE;
|
||
|
final int insetL = insetRtlL == INSET_UNDEFINED ? r.mInsetL : insetRtlL;
|
||
|
final int insetR = insetRtlR == INSET_UNDEFINED ? r.mInsetR : insetRtlR;
|
||
|
|
||
|
// Don't apply padding and insets for children that don't have
|
||
|
// an intrinsic dimension.
|
||
|
final int minWidth = r.mWidth < 0 ? r.mDrawable.getIntrinsicWidth() : r.mWidth;
|
||
|
final int w = minWidth < 0 ? -1 : minWidth + insetL + insetR + padL + padR;
|
||
|
if (w > width) {
|
||
|
width = w;
|
||
|
}
|
||
|
|
||
|
if (nest) {
|
||
|
padL += mPaddingL[i];
|
||
|
padR += mPaddingR[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return width;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getIntrinsicHeight() {
|
||
|
int height = -1;
|
||
|
int padT = 0;
|
||
|
int padB = 0;
|
||
|
|
||
|
final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final ChildDrawable r = array[i];
|
||
|
if (r.mDrawable == null) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Don't apply padding and insets for children that don't have
|
||
|
// an intrinsic dimension.
|
||
|
final int minHeight = r.mHeight < 0 ? r.mDrawable.getIntrinsicHeight() : r.mHeight;
|
||
|
final int h = minHeight < 0 ? -1 : minHeight + r.mInsetT + r.mInsetB + padT + padB;
|
||
|
if (h > height) {
|
||
|
height = h;
|
||
|
}
|
||
|
|
||
|
if (nest) {
|
||
|
padT += mPaddingT[i];
|
||
|
padB += mPaddingB[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return height;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Refreshes the cached padding values for the specified child.
|
||
|
*
|
||
|
* @return true if the child's padding has changed
|
||
|
*/
|
||
|
private boolean refreshChildPadding(int i, ChildDrawable r) {
|
||
|
if (r.mDrawable != null) {
|
||
|
final Rect rect = mTmpRect;
|
||
|
r.mDrawable.getPadding(rect);
|
||
|
if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i]
|
||
|
|| rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) {
|
||
|
mPaddingL[i] = rect.left;
|
||
|
mPaddingT[i] = rect.top;
|
||
|
mPaddingR[i] = rect.right;
|
||
|
mPaddingB[i] = rect.bottom;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Ensures the child padding caches are large enough.
|
||
|
*/
|
||
|
@UnsupportedAppUsage
|
||
|
void ensurePadding() {
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
if (mPaddingL != null && mPaddingL.length >= N) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
mPaddingL = new int[N];
|
||
|
mPaddingT = new int[N];
|
||
|
mPaddingR = new int[N];
|
||
|
mPaddingB = new int[N];
|
||
|
}
|
||
|
|
||
|
void refreshPadding() {
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
refreshChildPadding(i, array[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ConstantState getConstantState() {
|
||
|
if (mLayerState.canConstantState()) {
|
||
|
mLayerState.mChangingConfigurations = getChangingConfigurations();
|
||
|
return mLayerState;
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Drawable mutate() {
|
||
|
if (!mMutated && super.mutate() == this) {
|
||
|
mLayerState = createConstantState(mLayerState, null);
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null) {
|
||
|
dr.mutate();
|
||
|
}
|
||
|
}
|
||
|
mMutated = true;
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public void clearMutated() {
|
||
|
super.clearMutated();
|
||
|
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null) {
|
||
|
dr.clearMutated();
|
||
|
}
|
||
|
}
|
||
|
mMutated = false;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) {
|
||
|
boolean changed = false;
|
||
|
|
||
|
final ChildDrawable[] array = mLayerState.mChildren;
|
||
|
final int N = mLayerState.mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null) {
|
||
|
changed |= dr.setLayoutDirection(layoutDirection);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
updateLayerBounds(getBounds());
|
||
|
return changed;
|
||
|
}
|
||
|
|
||
|
static class ChildDrawable {
|
||
|
@UnsupportedAppUsage
|
||
|
public Drawable mDrawable;
|
||
|
public int[] mThemeAttrs;
|
||
|
public int mDensity = DisplayMetrics.DENSITY_DEFAULT;
|
||
|
public int mInsetL, mInsetT, mInsetR, mInsetB;
|
||
|
public int mInsetS = INSET_UNDEFINED;
|
||
|
public int mInsetE = INSET_UNDEFINED;
|
||
|
public int mWidth = -1;
|
||
|
public int mHeight = -1;
|
||
|
public int mGravity = Gravity.NO_GRAVITY;
|
||
|
public int mId = View.NO_ID;
|
||
|
|
||
|
ChildDrawable(int density) {
|
||
|
mDensity = density;
|
||
|
}
|
||
|
|
||
|
ChildDrawable(@NonNull ChildDrawable orig, @NonNull LayerDrawable owner,
|
||
|
@Nullable Resources res) {
|
||
|
final Drawable dr = orig.mDrawable;
|
||
|
final Drawable clone;
|
||
|
if (dr != null) {
|
||
|
final ConstantState cs = dr.getConstantState();
|
||
|
if (cs == null) {
|
||
|
clone = dr;
|
||
|
if (dr.getCallback() != null) {
|
||
|
// This drawable already has an owner.
|
||
|
Log.w(LOG_TAG, "Invalid drawable added to LayerDrawable! Drawable already "
|
||
|
+ "belongs to another owner but does not expose a constant state.",
|
||
|
new RuntimeException());
|
||
|
}
|
||
|
} else if (res != null) {
|
||
|
clone = cs.newDrawable(res);
|
||
|
} else {
|
||
|
clone = cs.newDrawable();
|
||
|
}
|
||
|
clone.setLayoutDirection(dr.getLayoutDirection());
|
||
|
clone.setBounds(dr.getBounds());
|
||
|
clone.setLevel(dr.getLevel());
|
||
|
|
||
|
// Set the callback last to prevent invalidation from
|
||
|
// propagating before the constant state has been set.
|
||
|
clone.setCallback(owner);
|
||
|
} else {
|
||
|
clone = null;
|
||
|
}
|
||
|
|
||
|
mDrawable = clone;
|
||
|
mThemeAttrs = orig.mThemeAttrs;
|
||
|
mInsetL = orig.mInsetL;
|
||
|
mInsetT = orig.mInsetT;
|
||
|
mInsetR = orig.mInsetR;
|
||
|
mInsetB = orig.mInsetB;
|
||
|
mInsetS = orig.mInsetS;
|
||
|
mInsetE = orig.mInsetE;
|
||
|
mWidth = orig.mWidth;
|
||
|
mHeight = orig.mHeight;
|
||
|
mGravity = orig.mGravity;
|
||
|
mId = orig.mId;
|
||
|
|
||
|
mDensity = Drawable.resolveDensity(res, orig.mDensity);
|
||
|
if (orig.mDensity != mDensity) {
|
||
|
applyDensityScaling(orig.mDensity, mDensity);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public boolean canApplyTheme() {
|
||
|
return mThemeAttrs != null
|
||
|
|| (mDrawable != null && mDrawable.canApplyTheme());
|
||
|
}
|
||
|
|
||
|
public final void setDensity(int targetDensity) {
|
||
|
if (mDensity != targetDensity) {
|
||
|
final int sourceDensity = mDensity;
|
||
|
mDensity = targetDensity;
|
||
|
|
||
|
applyDensityScaling(sourceDensity, targetDensity);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void applyDensityScaling(int sourceDensity, int targetDensity) {
|
||
|
mInsetL = Drawable.scaleFromDensity(mInsetL, sourceDensity, targetDensity, false);
|
||
|
mInsetT = Drawable.scaleFromDensity(mInsetT, sourceDensity, targetDensity, false);
|
||
|
mInsetR = Drawable.scaleFromDensity(mInsetR, sourceDensity, targetDensity, false);
|
||
|
mInsetB = Drawable.scaleFromDensity(mInsetB, sourceDensity, targetDensity, false);
|
||
|
if (mInsetS != INSET_UNDEFINED) {
|
||
|
mInsetS = Drawable.scaleFromDensity(mInsetS, sourceDensity, targetDensity, false);
|
||
|
}
|
||
|
if (mInsetE != INSET_UNDEFINED) {
|
||
|
mInsetE = Drawable.scaleFromDensity(mInsetE, sourceDensity, targetDensity, false);
|
||
|
}
|
||
|
if (mWidth > 0) {
|
||
|
mWidth = Drawable.scaleFromDensity(mWidth, sourceDensity, targetDensity, true);
|
||
|
}
|
||
|
if (mHeight > 0) {
|
||
|
mHeight = Drawable.scaleFromDensity(mHeight, sourceDensity, targetDensity, true);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static class LayerState extends ConstantState {
|
||
|
private int[] mThemeAttrs;
|
||
|
|
||
|
int mNumChildren;
|
||
|
@UnsupportedAppUsage
|
||
|
ChildDrawable[] mChildren;
|
||
|
|
||
|
int mDensity;
|
||
|
|
||
|
// These values all correspond to mDensity.
|
||
|
int mPaddingTop = -1;
|
||
|
int mPaddingBottom = -1;
|
||
|
int mPaddingLeft = -1;
|
||
|
int mPaddingRight = -1;
|
||
|
int mPaddingStart = -1;
|
||
|
int mPaddingEnd = -1;
|
||
|
int mOpacityOverride = PixelFormat.UNKNOWN;
|
||
|
|
||
|
@Config int mChangingConfigurations;
|
||
|
@Config int mChildrenChangingConfigurations;
|
||
|
|
||
|
private boolean mCheckedOpacity;
|
||
|
private int mOpacity;
|
||
|
|
||
|
private boolean mCheckedStateful;
|
||
|
private boolean mIsStateful;
|
||
|
|
||
|
private boolean mAutoMirrored = false;
|
||
|
|
||
|
private int mPaddingMode = PADDING_MODE_NEST;
|
||
|
|
||
|
LayerState(@Nullable LayerState orig, @NonNull LayerDrawable owner,
|
||
|
@Nullable Resources res) {
|
||
|
mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
|
||
|
|
||
|
if (orig != null) {
|
||
|
final ChildDrawable[] origChildDrawable = orig.mChildren;
|
||
|
final int N = orig.mNumChildren;
|
||
|
|
||
|
mNumChildren = N;
|
||
|
mChildren = new ChildDrawable[N];
|
||
|
|
||
|
mChangingConfigurations = orig.mChangingConfigurations;
|
||
|
mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
|
||
|
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final ChildDrawable or = origChildDrawable[i];
|
||
|
mChildren[i] = new ChildDrawable(or, owner, res);
|
||
|
}
|
||
|
|
||
|
mCheckedOpacity = orig.mCheckedOpacity;
|
||
|
mOpacity = orig.mOpacity;
|
||
|
mCheckedStateful = orig.mCheckedStateful;
|
||
|
mIsStateful = orig.mIsStateful;
|
||
|
mAutoMirrored = orig.mAutoMirrored;
|
||
|
mPaddingMode = orig.mPaddingMode;
|
||
|
mThemeAttrs = orig.mThemeAttrs;
|
||
|
mPaddingTop = orig.mPaddingTop;
|
||
|
mPaddingBottom = orig.mPaddingBottom;
|
||
|
mPaddingLeft = orig.mPaddingLeft;
|
||
|
mPaddingRight = orig.mPaddingRight;
|
||
|
mPaddingStart = orig.mPaddingStart;
|
||
|
mPaddingEnd = orig.mPaddingEnd;
|
||
|
mOpacityOverride = orig.mOpacityOverride;
|
||
|
|
||
|
if (orig.mDensity != mDensity) {
|
||
|
applyDensityScaling(orig.mDensity, mDensity);
|
||
|
}
|
||
|
} else {
|
||
|
mNumChildren = 0;
|
||
|
mChildren = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public final void setDensity(int targetDensity) {
|
||
|
if (mDensity != targetDensity) {
|
||
|
final int sourceDensity = mDensity;
|
||
|
mDensity = targetDensity;
|
||
|
|
||
|
onDensityChanged(sourceDensity, targetDensity);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected void onDensityChanged(int sourceDensity, int targetDensity) {
|
||
|
applyDensityScaling(sourceDensity, targetDensity);
|
||
|
}
|
||
|
|
||
|
private void applyDensityScaling(int sourceDensity, int targetDensity) {
|
||
|
if (mPaddingLeft > 0) {
|
||
|
mPaddingLeft = Drawable.scaleFromDensity(
|
||
|
mPaddingLeft, sourceDensity, targetDensity, false);
|
||
|
}
|
||
|
if (mPaddingTop > 0) {
|
||
|
mPaddingTop = Drawable.scaleFromDensity(
|
||
|
mPaddingTop, sourceDensity, targetDensity, false);
|
||
|
}
|
||
|
if (mPaddingRight > 0) {
|
||
|
mPaddingRight = Drawable.scaleFromDensity(
|
||
|
mPaddingRight, sourceDensity, targetDensity, false);
|
||
|
}
|
||
|
if (mPaddingBottom > 0) {
|
||
|
mPaddingBottom = Drawable.scaleFromDensity(
|
||
|
mPaddingBottom, sourceDensity, targetDensity, false);
|
||
|
}
|
||
|
if (mPaddingStart > 0) {
|
||
|
mPaddingStart = Drawable.scaleFromDensity(
|
||
|
mPaddingStart, sourceDensity, targetDensity, false);
|
||
|
}
|
||
|
if (mPaddingEnd > 0) {
|
||
|
mPaddingEnd = Drawable.scaleFromDensity(
|
||
|
mPaddingEnd, sourceDensity, targetDensity, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean canApplyTheme() {
|
||
|
if (mThemeAttrs != null || super.canApplyTheme()) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
final ChildDrawable[] array = mChildren;
|
||
|
final int N = mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final ChildDrawable layer = array[i];
|
||
|
if (layer.canApplyTheme()) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Drawable newDrawable() {
|
||
|
return new LayerDrawable(this, null);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Drawable newDrawable(@Nullable Resources res) {
|
||
|
return new LayerDrawable(this, res);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public @Config int getChangingConfigurations() {
|
||
|
return mChangingConfigurations
|
||
|
| mChildrenChangingConfigurations;
|
||
|
}
|
||
|
|
||
|
public final int getOpacity() {
|
||
|
if (mCheckedOpacity) {
|
||
|
return mOpacity;
|
||
|
}
|
||
|
|
||
|
final int N = mNumChildren;
|
||
|
final ChildDrawable[] array = mChildren;
|
||
|
|
||
|
// Seek to the first non-null drawable.
|
||
|
int firstIndex = -1;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
if (array[i].mDrawable != null) {
|
||
|
firstIndex = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int op;
|
||
|
if (firstIndex >= 0) {
|
||
|
op = array[firstIndex].mDrawable.getOpacity();
|
||
|
} else {
|
||
|
op = PixelFormat.TRANSPARENT;
|
||
|
}
|
||
|
|
||
|
// Merge all remaining non-null drawables.
|
||
|
for (int i = firstIndex + 1; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null) {
|
||
|
op = Drawable.resolveOpacity(op, dr.getOpacity());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mOpacity = op;
|
||
|
mCheckedOpacity = true;
|
||
|
return op;
|
||
|
}
|
||
|
|
||
|
public final boolean isStateful() {
|
||
|
if (mCheckedStateful) {
|
||
|
return mIsStateful;
|
||
|
}
|
||
|
|
||
|
final int N = mNumChildren;
|
||
|
final ChildDrawable[] array = mChildren;
|
||
|
boolean isStateful = false;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null && dr.isStateful()) {
|
||
|
isStateful = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mIsStateful = isStateful;
|
||
|
mCheckedStateful = true;
|
||
|
return isStateful;
|
||
|
}
|
||
|
|
||
|
public final boolean hasFocusStateSpecified() {
|
||
|
final int N = mNumChildren;
|
||
|
final ChildDrawable[] array = mChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null && dr.hasFocusStateSpecified()) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public final boolean canConstantState() {
|
||
|
final ChildDrawable[] array = mChildren;
|
||
|
final int N = mNumChildren;
|
||
|
for (int i = 0; i < N; i++) {
|
||
|
final Drawable dr = array[i].mDrawable;
|
||
|
if (dr != null && dr.getConstantState() == null) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Don't cache the result, this method is not called very often.
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Invalidates the cached opacity and statefulness.
|
||
|
*/
|
||
|
void invalidateCache() {
|
||
|
mCheckedOpacity = false;
|
||
|
mCheckedStateful = false;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|