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