Files
frameworks_base/graphics/java/android/graphics/drawable/LayerDrawable.java
Alan Viverette 06ff2af68a Update local state when creating LayerDrawable from constant state
Also clears DrawableContainer's futures list when it's no longer needed,
correctly sets deep copy of state set in StateListDrawable, makes some
private methods into package-protected to avoid thunk, and propagates
state to StateListDrawable's super class so that getState() has correct
information.

Bug: 21840003
Change-Id: I0d4232807f280d663c03b4a80e4aab8626806440
2015-06-24 14:42:44 -07:00

1945 lines
65 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.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Outline;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.LayoutDirection;
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;
import java.util.Collection;
/**
* 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>&lt;layer-list></code> element.
* Each Drawable in the layer is defined in a nested <code>&lt;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 {
/**
* 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. */
private static final int UNDEFINED_INSET = Integer.MIN_VALUE;
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;
/**
* 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();
r[i].mDrawable = layers[i];
layers[i].setCallback(this);
mLayerState.mChildrenChangingConfigurations |= layers[i].getChangingConfigurations();
}
mLayerState.mNum = length;
mLayerState.mChildren = r;
ensurePadding();
refreshPadding();
}
LayerDrawable() {
this((LayerState) null, null);
}
LayerDrawable(@Nullable LayerState state, @Nullable Resources res) {
mLayerState = createConstantState(state, res);
if (mLayerState.mNum > 0) {
ensurePadding();
refreshPadding();
}
}
LayerState createConstantState(@Nullable LayerState state, @Nullable Resources res) {
return new LayerState(state, this, res);
}
@Override
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
throws XmlPullParserException, IOException {
super.inflate(r, parser, attrs, theme);
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawable);
updateStateFromTypedArray(a);
a.recycle();
inflateLayers(r, parser, attrs, theme);
ensurePadding();
refreshPadding();
}
/**
* Initializes the constant state from the values in the typed array.
*/
private void updateStateFromTypedArray(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++) {
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;
}
}
}
/**
* Inflates child layers using the specified parser.
*/
private void inflateLayers(Resources r, XmlPullParser parser, AttributeSet attrs, 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();
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 (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");
}
layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme);
}
if (layer.mDrawable != null) {
state.mChildrenChangingConfigurations |=
layer.mDrawable.getChangingConfigurations();
layer.mDrawable.setCallback(this);
}
addLayer(layer);
}
}
private void updateLayerFromTypedArray(ChildDrawable layer, 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();
layer.mInsetL = a.getDimensionPixelOffset(
R.styleable.LayerDrawableItem_left, layer.mInsetL);
layer.mInsetT = a.getDimensionPixelOffset(
R.styleable.LayerDrawableItem_top, layer.mInsetT);
layer.mInsetR = a.getDimensionPixelOffset(
R.styleable.LayerDrawableItem_right, layer.mInsetR);
layer.mInsetB = a.getDimensionPixelOffset(
R.styleable.LayerDrawableItem_bottom, layer.mInsetB);
layer.mInsetS = a.getDimensionPixelOffset(
R.styleable.LayerDrawableItem_start, layer.mInsetS);
layer.mInsetE = a.getDimensionPixelOffset(
R.styleable.LayerDrawableItem_end, layer.mInsetE);
layer.mWidth = a.getDimensionPixelSize(
R.styleable.LayerDrawableItem_width, layer.mWidth);
layer.mHeight = a.getDimensionPixelSize(
R.styleable.LayerDrawableItem_height, layer.mHeight);
layer.mGravity = a.getInteger(
R.styleable.LayerDrawableItem_gravity, layer.mGravity);
layer.mId = a.getResourceId(R.styleable.LayerDrawableItem_id, layer.mId);
final Drawable dr = a.getDrawable(R.styleable.LayerDrawableItem_drawable);
if (dr != null) {
layer.mDrawable = dr;
}
}
@Override
public void applyTheme(Theme t) {
super.applyTheme(t);
final LayerState state = mLayerState;
if (state == null) {
return;
}
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.mNum;
for (int i = 0; i < N; i++) {
final ChildDrawable layer = array[i];
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();
}
}
ensurePadding();
}
@Override
public boolean canApplyTheme() {
return (mLayerState != null && mLayerState.canApplyTheme()) || super.canApplyTheme();
}
/**
* @hide
*/
@Override
public boolean isProjected() {
if (super.isProjected()) {
return true;
}
final ChildDrawable[] layers = mLayerState.mChildren;
final int N = mLayerState.mNum;
for (int i = 0; i < N; i++) {
if (layers[i].mDrawable.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.
*/
int addLayer(ChildDrawable layer) {
final LayerState st = mLayerState;
final int N = st.mChildren != null ? st.mChildren.length : 0;
final int i = st.mNum;
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.mNum++;
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();
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.mNum - 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.mNum) {
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.mNum;
}
/**
* 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.mNum;
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.mNum) {
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.mNum) {
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, UNDEFINED_INSET, UNDEFINED_INSET);
}
/**
* 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 number of pixels to inset from the start bound
* @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
* @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;
}
@Override
public void invalidateDrawable(Drawable who) {
invalidateSelf();
}
@Override
public void scheduleDrawable(Drawable who, Runnable what, long when) {
scheduleSelf(what, when);
}
@Override
public void unscheduleDrawable(Drawable who, Runnable what) {
unscheduleSelf(what);
}
@Override
public void draw(Canvas canvas) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.draw(canvas);
}
}
}
@Override
public 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);
}
// If padding was explicitly specified (e.g. not -1) then override the
// computed padding in that dimension.
if (layerState.mPaddingTop >= 0) {
padding.top = layerState.mPaddingTop;
}
if (layerState.mPaddingBottom >= 0) {
padding.bottom = layerState.mPaddingBottom;
}
final int paddingRtlLeft;
final int paddingRtlRight;
if (getLayoutDirection() == LayoutDirection.RTL) {
paddingRtlLeft = layerState.mPaddingEnd;
paddingRtlRight = layerState.mPaddingStart;
} else {
paddingRtlLeft = layerState.mPaddingStart;
paddingRtlRight = layerState.mPaddingEnd;
}
final int paddingLeft = paddingRtlLeft >= 0 ? paddingRtlLeft : layerState.mPaddingLeft;
if (paddingLeft >= 0) {
padding.left = paddingLeft;
}
final int paddingRight = paddingRtlRight >= 0 ? paddingRtlRight : layerState.mPaddingRight;
if (paddingRight >= 0) {
padding.right = paddingRight;
}
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.mNum;
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.mNum;
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.mNum;
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.mNum;
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.mNum;
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.mNum;
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.mNum;
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.mNum;
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.mNum;
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.mNum;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.setTintList(tint);
}
}
}
@Override
public void setTintMode(Mode tintMode) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.setTintMode(tintMode);
}
}
}
private Drawable getFirstNonNullDrawable() {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
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.mNum;
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 boolean isStateful() {
return mLayerState.isStateful();
}
@Override
protected boolean onStateChange(int[] state) {
boolean changed = false;
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
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.mNum;
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) {
int padL = 0;
int padT = 0;
int padR = 0;
int padB = 0;
final Rect outRect = mTmpOutRect;
final int layoutDirection = getLayoutDirection();
final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
for (int i = 0; i < N; i++) {
final ChildDrawable r = array[i];
final Drawable d = r.mDrawable;
if (d == null) {
continue;
}
final Rect container = mTmpContainer;
container.set(d.getBounds());
// 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 insetL, insetR;
if (layoutDirection == LayoutDirection.RTL) {
insetL = r.mInsetE == UNDEFINED_INSET ? r.mInsetL : r.mInsetE;
insetR = r.mInsetS == UNDEFINED_INSET ? r.mInsetR : r.mInsetS;
} else {
insetL = r.mInsetS == UNDEFINED_INSET ? r.mInsetL : r.mInsetS;
insetR = r.mInsetE == UNDEFINED_INSET ? r.mInsetR : r.mInsetE;
}
// Establish containing region based on aggregate padding and
// requested insets for the current layer.
container.set(bounds.left + insetL + padL, bounds.top + r.mInsetT + padT,
bounds.right - insetR - padR, bounds.bottom - r.mInsetB - padB);
// Apply resolved gravity to drawable based on resolved size.
final int gravity = resolveGravity(r.mGravity, r.mWidth, r.mHeight,
d.getIntrinsicWidth(), d.getIntrinsicHeight());
final int w = r.mWidth < 0 ? d.getIntrinsicWidth() : r.mWidth;
final int h = r.mHeight < 0 ? d.getIntrinsicHeight() : r.mHeight;
Gravity.apply(gravity, w, h, container, outRect, layoutDirection);
d.setBounds(outRect);
if (nest) {
padL += mPaddingL[i];
padR += mPaddingR[i];
padT += mPaddingT[i];
padB += 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 ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
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 insetL, insetR;
final int layoutDirection = getLayoutDirection();
if (layoutDirection == LayoutDirection.RTL) {
insetL = r.mInsetE == UNDEFINED_INSET ? r.mInsetL : r.mInsetE;
insetR = r.mInsetS == UNDEFINED_INSET ? r.mInsetR : r.mInsetS;
} else {
insetL = r.mInsetS == UNDEFINED_INSET ? r.mInsetL : r.mInsetS;
insetR = r.mInsetE == UNDEFINED_INSET ? r.mInsetR : r.mInsetE;
}
final int minWidth = r.mWidth < 0 ? r.mDrawable.getIntrinsicWidth() : r.mWidth;
final int w = 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.mNum;
for (int i = 0; i < N; i++) {
final ChildDrawable r = array[i];
if (r.mDrawable == null) {
continue;
}
final int minHeight = r.mHeight < 0 ? r.mDrawable.getIntrinsicHeight() : r.mHeight;
final int h = 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.
*/
void ensurePadding() {
final int N = mLayerState.mNum;
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.mNum;
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.mNum;
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.mNum;
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.mNum;
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 {
public Drawable mDrawable;
public int[] mThemeAttrs;
public int mInsetL, mInsetT, mInsetR, mInsetB;
public int mInsetS = UNDEFINED_INSET;
public int mInsetE = UNDEFINED_INSET;
public int mWidth = -1;
public int mHeight = -1;
public int mGravity = Gravity.NO_GRAVITY;
public int mId = View.NO_ID;
ChildDrawable() {
// Default empty constructor.
}
ChildDrawable(ChildDrawable orig, LayerDrawable owner, Resources res) {
final Drawable dr = orig.mDrawable;
final Drawable clone;
if (dr != null) {
final ConstantState cs = dr.getConstantState();
if (res != null) {
clone = cs.newDrawable(res);
} else {
clone = cs.newDrawable();
}
clone.setCallback(owner);
clone.setLayoutDirection(dr.getLayoutDirection());
clone.setBounds(dr.getBounds());
clone.setLevel(dr.getLevel());
} 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;
}
public boolean canApplyTheme() {
return mThemeAttrs != null
|| (mDrawable != null && mDrawable.canApplyTheme());
}
}
static class LayerState extends ConstantState {
int mNum;
ChildDrawable[] mChildren;
int[] mThemeAttrs;
int mPaddingTop = -1;
int mPaddingBottom = -1;
int mPaddingLeft = -1;
int mPaddingRight = -1;
int mPaddingStart = -1;
int mPaddingEnd = -1;
int mOpacityOverride = PixelFormat.UNKNOWN;
int mChangingConfigurations;
int mChildrenChangingConfigurations;
private boolean mHaveOpacity;
private int mOpacity;
private boolean mHaveIsStateful;
private boolean mIsStateful;
private boolean mAutoMirrored = false;
private int mPaddingMode = PADDING_MODE_NEST;
LayerState(LayerState orig, LayerDrawable owner, Resources res) {
if (orig != null) {
final ChildDrawable[] origChildDrawable = orig.mChildren;
final int N = orig.mNum;
mNum = 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);
}
mHaveOpacity = orig.mHaveOpacity;
mOpacity = orig.mOpacity;
mHaveIsStateful = orig.mHaveIsStateful;
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;
} else {
mNum = 0;
mChildren = null;
}
}
@Override
public boolean canApplyTheme() {
if (mThemeAttrs != null || super.canApplyTheme()) {
return true;
}
final ChildDrawable[] array = mChildren;
final int N = mNum;
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(Resources res) {
return new LayerDrawable(this, res);
}
@Override
public int getChangingConfigurations() {
return mChangingConfigurations
| mChildrenChangingConfigurations;
}
public final int getOpacity() {
if (mHaveOpacity) {
return mOpacity;
}
final ChildDrawable[] array = mChildren;
final int N = mNum;
// 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;
mHaveOpacity = true;
return op;
}
public final boolean isStateful() {
if (mHaveIsStateful) {
return mIsStateful;
}
final ChildDrawable[] array = mChildren;
final int N = mNum;
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;
mHaveIsStateful = true;
return isStateful;
}
public final boolean canConstantState() {
final ChildDrawable[] array = mChildren;
final int N = mNum;
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;
}
public void invalidateCache() {
mHaveOpacity = false;
mHaveIsStateful = false;
}
@Override
public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
final ChildDrawable[] array = mChildren;
final int N = mNum;
int pixelCount = 0;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
final ConstantState state = dr.getConstantState();
if (state != null) {
pixelCount += state.addAtlasableBitmaps(atlasList);
}
}
}
return pixelCount;
}
}
}