/* * 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.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.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.DisplayMetrics; 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. *
* It can be defined in an XML file with the <layer-list>
element.
* Each Drawable in the layer is defined in a nested <item>
.
*
* For more information, see the guide to
* Drawable Resources.
*
* @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.
*
* @see #getLayerInsetStart(int)
* @see #getLayerInsetEnd(int)
*/
public static final int INSET_UNDEFINED = 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;
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);
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);
}
/**
* 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.mNum > 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);
final LayerState state = mLayerState;
if (state == null) {
return;
}
// The density may have changed since the last update. This will
// apply scaling to any existing constant state properties.
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.mNum;
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;
if (state == null) {
return;
}
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.mNum;
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()
+ ":
* 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}.
*
* 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.
*
* Note: 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)}).
*
* 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:
*
* 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)}).
*
* 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.
*
* 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)}).
*
* 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.
*
* 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.
*
* 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.
*
* 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.
*
* 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.
*
* 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.
*
* 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
* 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.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 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.mNum;
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.
*/
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 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;
} else 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;
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 mNum;
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 mHaveOpacity;
private int mOpacity;
private boolean mHaveIsStateful;
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.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;
if (orig.mDensity != mDensity) {
applyDensityScaling(orig.mDensity, mDensity);
}
} else {
mNum = 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 = 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(@Nullable Resources res) {
return new LayerDrawable(this, res);
}
@Override
public @Config 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
*
*
* @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 {
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.mNum;
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.
* outline
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 void jumpToCurrentState() {
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.jumpToCurrentState();
}
}
}
@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) {
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.mNum; 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.
*