/* * 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 org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.PixelFormat; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; 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. *

* 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#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_drawable * @attr ref android.R.styleable#LayerDrawableItem_id */ public class LayerDrawable extends Drawable implements Drawable.Callback { LayerState mLayerState; private int mOpacityOverride = PixelFormat.UNKNOWN; private int[] mPaddingL; private int[] mPaddingT; private int[] mPaddingR; private int[] mPaddingB; private final Rect mTmpRect = new Rect(); private boolean mMutated; /** * Create a new layer drawable with the list of specified layers. * * @param layers A list of drawables to use as layers in this new drawable. */ public LayerDrawable(Drawable[] layers) { this(layers, null); } /** * Create 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(Drawable[] layers, LayerState state) { this(state, null); int length = layers.length; ChildDrawable[] r = new ChildDrawable[length]; for (int i = 0; i < length; i++) { r[i] = new ChildDrawable(); r[i].mDrawable = layers[i]; layers[i].setCallback(this); mLayerState.mChildrenChangingConfigurations |= layers[i].getChangingConfigurations(); } mLayerState.mNum = length; mLayerState.mChildren = r; ensurePadding(); } LayerDrawable() { this((LayerState) null, null); } LayerDrawable(LayerState state, Resources res) { LayerState as = createConstantState(state, res); mLayerState = as; if (as.mNum > 0) { ensurePadding(); } } LayerState createConstantState(LayerState state, Resources res) { return new LayerState(state, this, res); } @Override public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException { super.inflate(r, parser, attrs); int type; TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.LayerDrawable); mOpacityOverride = a.getInt(com.android.internal.R.styleable.LayerDrawable_opacity, PixelFormat.UNKNOWN); a.recycle(); final int innerDepth = parser.getDepth() + 1; 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; } a = r.obtainAttributes(attrs, com.android.internal.R.styleable.LayerDrawableItem); int left = a.getDimensionPixelOffset( com.android.internal.R.styleable.LayerDrawableItem_left, 0); int top = a.getDimensionPixelOffset( com.android.internal.R.styleable.LayerDrawableItem_top, 0); int right = a.getDimensionPixelOffset( com.android.internal.R.styleable.LayerDrawableItem_right, 0); int bottom = a.getDimensionPixelOffset( com.android.internal.R.styleable.LayerDrawableItem_bottom, 0); int drawableRes = a.getResourceId( com.android.internal.R.styleable.LayerDrawableItem_drawable, 0); int id = a.getResourceId(com.android.internal.R.styleable.LayerDrawableItem_id, View.NO_ID); a.recycle(); Drawable dr; if (drawableRes != 0) { dr = r.getDrawable(drawableRes); } else { while ((type = parser.next()) == XmlPullParser.TEXT) { } if (type != XmlPullParser.START_TAG) { throw new XmlPullParserException(parser.getPositionDescription() + ": tag requires a 'drawable' attribute or " + "child tag defining a drawable"); } dr = Drawable.createFromXmlInner(r, parser, attrs); } addLayer(dr, id, left, top, right, bottom); } ensurePadding(); onStateChange(getState()); } /** * Add a new layer to this drawable. The new layer is identified by an id. * * @param layer The drawable to add as a 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. */ private void addLayer(Drawable layer, int id, int left, int top, int right, int bottom) { final LayerState st = mLayerState; int N = st.mChildren != null ? st.mChildren.length : 0; int i = st.mNum; if (i >= N) { ChildDrawable[] nu = new ChildDrawable[N + 10]; if (i > 0) { System.arraycopy(st.mChildren, 0, nu, 0, i); } st.mChildren = nu; } mLayerState.mChildrenChangingConfigurations |= layer.getChangingConfigurations(); ChildDrawable childDrawable = new ChildDrawable(); st.mChildren[i] = childDrawable; childDrawable.mId = id; childDrawable.mDrawable = layer; childDrawable.mInsetL = left; childDrawable.mInsetT = top; childDrawable.mInsetR = right; childDrawable.mInsetB = bottom; st.mNum++; layer.setCallback(this); } /** * Look for a layer with the given id, and returns its {@link Drawable}. * * @param id The layer ID to search for. * @return The {@link Drawable} of the layer that has the given id in the hierarchy or null. */ 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 which will received the ID. * @param id The ID to assign to the layer. */ public void setId(int index, int id) { mLayerState.mChildren[index].mId = id; } /** * Returns the number of layers contained within this. * @return The number of layers. */ public int getNumberOfLayers() { return mLayerState.mNum; } /** * Returns the drawable at the specified layer index. * * @param index The layer index of the drawable to retrieve. * * @return The {@link android.graphics.drawable.Drawable} at the specified layer index. */ public Drawable getDrawable(int index) { return mLayerState.mChildren[index].mDrawable; } /** * Returns the id of the specified layer. * * @param index The index of the layer. * * @return The id of the layer or {@link android.view.View#NO_ID} if the layer has no id. */ public int getId(int index) { return mLayerState.mChildren[index].mId; } /** * Sets (or 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 ChildDrawable[] layers = mLayerState.mChildren; for (int i = mLayerState.mNum - 1; i >= 0; i--) { if (layers[i].mId == id) { if (layers[i].mDrawable != null) { if (drawable != null) { Rect bounds = layers[i].mDrawable.getBounds(); drawable.setBounds(bounds); } layers[i].mDrawable.setCallback(null); } if (drawable != null) { drawable.setCallback(this); } layers[i].mDrawable = drawable; return true; } } return false; } /** Specify modifiers to the bounds for the drawable[index]. left += l top += t; right -= r; bottom -= b; */ public void setLayerInset(int index, int l, int t, int r, int b) { ChildDrawable childDrawable = mLayerState.mChildren[index]; childDrawable.mInsetL = l; childDrawable.mInsetT = t; childDrawable.mInsetR = r; childDrawable.mInsetB = b; } // overrides from Drawable.Callback public void invalidateDrawable(Drawable who) { final Callback callback = getCallback(); if (callback != null) { callback.invalidateDrawable(this); } } public void scheduleDrawable(Drawable who, Runnable what, long when) { final Callback callback = getCallback(); if (callback != null) { callback.scheduleDrawable(this, what, when); } } public void unscheduleDrawable(Drawable who, Runnable what) { final Callback callback = getCallback(); if (callback != null) { callback.unscheduleDrawable(this, what); } } // overrides from Drawable @Override public void draw(Canvas canvas) { final ChildDrawable[] array = mLayerState.mChildren; final int N = mLayerState.mNum; for (int i=0; i width) { width = w; } padL += mPaddingL[i]; padR += mPaddingR[i]; } return width; } @Override public int getIntrinsicHeight() { int height = -1; final ChildDrawable[] array = mLayerState.mChildren; final int N = mLayerState.mNum; int padT=0, padB=0; for (int i=0; i height) { height = h; } padT += mPaddingT[i]; padB += mPaddingB[i]; } return height; } private boolean reapplyPadding(int i, ChildDrawable r) { 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; } private 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]; } @Override public ConstantState getConstantState() { if (mLayerState.canConstantState()) { mLayerState.mChangingConfigurations = getChangingConfigurations(); return mLayerState; } return null; } @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { if (!mLayerState.canConstantState()) { throw new IllegalStateException("One or more children of this LayerDrawable does " + "not have constant state; this drawable cannot be mutated."); } mLayerState = new LayerState(mLayerState, this, null); final ChildDrawable[] array = mLayerState.mChildren; final int N = mLayerState.mNum; for (int i = 0; i < N; i++) { array[i].mDrawable.mutate(); } mMutated = true; } return this; } /** @hide */ @Override public void setLayoutDirection(int layoutDirection) { if (getLayoutDirection() != layoutDirection) { final ChildDrawable[] array = mLayerState.mChildren; final int N = mLayerState.mNum; for (int i = 0; i < N; i++) { array[i].mDrawable.setLayoutDirection(layoutDirection); } } super.setLayoutDirection(layoutDirection); } static class ChildDrawable { public Drawable mDrawable; public int mInsetL, mInsetT, mInsetR, mInsetB; public int mId; } static class LayerState extends ConstantState { int mNum; ChildDrawable[] mChildren; int mChangingConfigurations; int mChildrenChangingConfigurations; private boolean mHaveOpacity = false; private int mOpacity; private boolean mHaveStateful = false; private boolean mStateful; private boolean mCheckedConstantState; private boolean mCanConstantState; LayerState(LayerState orig, LayerDrawable owner, Resources res) { if (orig != null) { final ChildDrawable[] origChildDrawable = orig.mChildren; final int N = orig.mNum; mNum = N; mChildren = new ChildDrawable[N]; mChangingConfigurations = orig.mChangingConfigurations; mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; for (int i = 0; i < N; i++) { final ChildDrawable r = mChildren[i] = new ChildDrawable(); final ChildDrawable or = origChildDrawable[i]; if (res != null) { r.mDrawable = or.mDrawable.getConstantState().newDrawable(res); } else { r.mDrawable = or.mDrawable.getConstantState().newDrawable(); } r.mDrawable.setCallback(owner); r.mInsetL = or.mInsetL; r.mInsetT = or.mInsetT; r.mInsetR = or.mInsetR; r.mInsetB = or.mInsetB; r.mId = or.mId; } mHaveOpacity = orig.mHaveOpacity; mOpacity = orig.mOpacity; mHaveStateful = orig.mHaveStateful; mStateful = orig.mStateful; mCheckedConstantState = mCanConstantState = true; } else { mNum = 0; mChildren = null; } } @Override public Drawable newDrawable() { return new LayerDrawable(this, null); } @Override public Drawable newDrawable(Resources res) { return new LayerDrawable(this, res); } @Override public int getChangingConfigurations() { return mChangingConfigurations; } public final int getOpacity() { if (mHaveOpacity) { return mOpacity; } final int N = mNum; int op = N > 0 ? mChildren[0].mDrawable.getOpacity() : PixelFormat.TRANSPARENT; for (int i = 1; i < N; i++) { op = Drawable.resolveOpacity(op, mChildren[i].mDrawable.getOpacity()); } mOpacity = op; mHaveOpacity = true; return op; } public final boolean isStateful() { if (mHaveStateful) { return mStateful; } boolean stateful = false; final int N = mNum; for (int i = 0; i < N; i++) { if (mChildren[i].mDrawable.isStateful()) { stateful = true; break; } } mStateful = stateful; mHaveStateful = true; return stateful; } public boolean canConstantState() { if (!mCheckedConstantState && mChildren != null) { mCanConstantState = true; final int N = mNum; for (int i=0; i