/* * 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.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Insets; import android.graphics.Outline; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.PorterDuff.Mode; import android.os.SystemClock; import android.util.LayoutDirection; import android.util.SparseArray; import android.view.View; import java.util.Collection; /** * A helper class that contains several {@link Drawable}s and selects which one to use. * * You can subclass it to create your own DrawableContainers or directly use one its child classes. */ public class DrawableContainer extends Drawable implements Drawable.Callback { private static final boolean DEBUG = false; private static final String TAG = "DrawableContainer"; /** * To be proper, we should have a getter for dither (and alpha, etc.) * so that proxy classes like this can save/restore their delegates' * values, but we don't have getters. Since we do have setters * (e.g. setDither), which this proxy forwards on, we have to have some * default/initial setting. * * The initial setting for dither is now true, since it almost always seems * to improve the quality at negligible cost. */ private static final boolean DEFAULT_DITHER = true; private DrawableContainerState mDrawableContainerState; private Rect mHotspotBounds; private Drawable mCurrDrawable; private Drawable mLastDrawable; private int mAlpha = 0xFF; /** Whether setAlpha() has been called at least once. */ private boolean mHasAlpha; private int mCurIndex = -1; private int mLastIndex = -1; private boolean mMutated; // Animations. private Runnable mAnimationRunnable; private long mEnterAnimationEnd; private long mExitAnimationEnd; // overrides from Drawable @Override public void draw(Canvas canvas) { if (mCurrDrawable != null) { mCurrDrawable.draw(canvas); } if (mLastDrawable != null) { mLastDrawable.draw(canvas); } } @Override public int getChangingConfigurations() { return super.getChangingConfigurations() | mDrawableContainerState.getChangingConfigurations(); } private boolean needsMirroring() { return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; } @Override public boolean getPadding(Rect padding) { final Rect r = mDrawableContainerState.getConstantPadding(); boolean result; if (r != null) { padding.set(r); result = (r.left | r.top | r.bottom | r.right) != 0; } else { if (mCurrDrawable != null) { result = mCurrDrawable.getPadding(padding); } else { result = super.getPadding(padding); } } if (needsMirroring()) { final int left = padding.left; final int right = padding.right; padding.left = right; padding.right = left; } return result; } /** * @hide */ @Override public Insets getOpticalInsets() { if (mCurrDrawable != null) { return mCurrDrawable.getOpticalInsets(); } return Insets.NONE; } @Override public void getOutline(@NonNull Outline outline) { if (mCurrDrawable != null) { mCurrDrawable.getOutline(outline); } } @Override public void setAlpha(int alpha) { if (!mHasAlpha || mAlpha != alpha) { mHasAlpha = true; mAlpha = alpha; if (mCurrDrawable != null) { if (mEnterAnimationEnd == 0) { mCurrDrawable.mutate().setAlpha(alpha); } else { animate(false); } } } } @Override public int getAlpha() { return mAlpha; } @Override public void setDither(boolean dither) { if (mDrawableContainerState.mDither != dither) { mDrawableContainerState.mDither = dither; if (mCurrDrawable != null) { mCurrDrawable.mutate().setDither(mDrawableContainerState.mDither); } } } @Override public void setColorFilter(ColorFilter colorFilter) { mDrawableContainerState.mHasColorFilter = true; if (mDrawableContainerState.mColorFilter != colorFilter) { mDrawableContainerState.mColorFilter = colorFilter; if (mCurrDrawable != null) { mCurrDrawable.mutate().setColorFilter(colorFilter); } } } @Override public void setTintList(ColorStateList tint) { mDrawableContainerState.mHasTintList = true; if (mDrawableContainerState.mTintList != tint) { mDrawableContainerState.mTintList = tint; if (mCurrDrawable != null) { mCurrDrawable.mutate().setTintList(tint); } } } @Override public void setTintMode(Mode tintMode) { mDrawableContainerState.mHasTintMode = true; if (mDrawableContainerState.mTintMode != tintMode) { mDrawableContainerState.mTintMode = tintMode; if (mCurrDrawable != null) { mCurrDrawable.mutate().setTintMode(tintMode); } } } /** * Change the global fade duration when a new drawable is entering * the scene. * @param ms The amount of time to fade in milliseconds. */ public void setEnterFadeDuration(int ms) { mDrawableContainerState.mEnterFadeDuration = ms; } /** * Change the global fade duration when a new drawable is leaving * the scene. * @param ms The amount of time to fade in milliseconds. */ public void setExitFadeDuration(int ms) { mDrawableContainerState.mExitFadeDuration = ms; } @Override protected void onBoundsChange(Rect bounds) { if (mLastDrawable != null) { mLastDrawable.setBounds(bounds); } if (mCurrDrawable != null) { mCurrDrawable.setBounds(bounds); } } @Override public boolean isStateful() { return mDrawableContainerState.isStateful(); } @Override public void setAutoMirrored(boolean mirrored) { if (mDrawableContainerState.mAutoMirrored != mirrored) { mDrawableContainerState.mAutoMirrored = mirrored; if (mCurrDrawable != null) { mCurrDrawable.mutate().setAutoMirrored(mDrawableContainerState.mAutoMirrored); } } } @Override public boolean isAutoMirrored() { return mDrawableContainerState.mAutoMirrored; } @Override public void jumpToCurrentState() { boolean changed = false; if (mLastDrawable != null) { mLastDrawable.jumpToCurrentState(); mLastDrawable = null; mLastIndex = -1; changed = true; } if (mCurrDrawable != null) { mCurrDrawable.jumpToCurrentState(); if (mHasAlpha) { mCurrDrawable.mutate().setAlpha(mAlpha); } } if (mExitAnimationEnd != 0) { mExitAnimationEnd = 0; changed = true; } if (mEnterAnimationEnd != 0) { mEnterAnimationEnd = 0; changed = true; } if (changed) { invalidateSelf(); } } @Override public void setHotspot(float x, float y) { if (mCurrDrawable != null) { mCurrDrawable.setHotspot(x, y); } } @Override public void setHotspotBounds(int left, int top, int right, int bottom) { if (mHotspotBounds == null) { mHotspotBounds = new Rect(left, top, right, bottom); } else { mHotspotBounds.set(left, top, right, bottom); } if (mCurrDrawable != null) { mCurrDrawable.setHotspotBounds(left, top, right, bottom); } } @Override public void getHotspotBounds(Rect outRect) { if (mHotspotBounds != null) { outRect.set(mHotspotBounds); } else { super.getHotspotBounds(outRect); } } @Override protected boolean onStateChange(int[] state) { if (mLastDrawable != null) { return mLastDrawable.setState(state); } if (mCurrDrawable != null) { return mCurrDrawable.setState(state); } return false; } @Override protected boolean onLevelChange(int level) { if (mLastDrawable != null) { return mLastDrawable.setLevel(level); } if (mCurrDrawable != null) { return mCurrDrawable.setLevel(level); } return false; } @Override public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) { // Let the container handle setting its own layout direction. Otherwise, // we're accessing potentially unused states. return mDrawableContainerState.setLayoutDirection(layoutDirection, getCurrentIndex()); } @Override public int getIntrinsicWidth() { if (mDrawableContainerState.isConstantSize()) { return mDrawableContainerState.getConstantWidth(); } return mCurrDrawable != null ? mCurrDrawable.getIntrinsicWidth() : -1; } @Override public int getIntrinsicHeight() { if (mDrawableContainerState.isConstantSize()) { return mDrawableContainerState.getConstantHeight(); } return mCurrDrawable != null ? mCurrDrawable.getIntrinsicHeight() : -1; } @Override public int getMinimumWidth() { if (mDrawableContainerState.isConstantSize()) { return mDrawableContainerState.getConstantMinimumWidth(); } return mCurrDrawable != null ? mCurrDrawable.getMinimumWidth() : 0; } @Override public int getMinimumHeight() { if (mDrawableContainerState.isConstantSize()) { return mDrawableContainerState.getConstantMinimumHeight(); } return mCurrDrawable != null ? mCurrDrawable.getMinimumHeight() : 0; } @Override public void invalidateDrawable(Drawable who) { if (who == mCurrDrawable && getCallback() != null) { getCallback().invalidateDrawable(this); } } @Override public void scheduleDrawable(Drawable who, Runnable what, long when) { if (who == mCurrDrawable && getCallback() != null) { getCallback().scheduleDrawable(this, what, when); } } @Override public void unscheduleDrawable(Drawable who, Runnable what) { if (who == mCurrDrawable && getCallback() != null) { getCallback().unscheduleDrawable(this, what); } } @Override public boolean setVisible(boolean visible, boolean restart) { boolean changed = super.setVisible(visible, restart); if (mLastDrawable != null) { mLastDrawable.setVisible(visible, restart); } if (mCurrDrawable != null) { mCurrDrawable.setVisible(visible, restart); } return changed; } @Override public int getOpacity() { return mCurrDrawable == null || !mCurrDrawable.isVisible() ? PixelFormat.TRANSPARENT : mDrawableContainerState.getOpacity(); } /** @hide */ public void setCurrentIndex(int index) { selectDrawable(index); } /** @hide */ public int getCurrentIndex() { return mCurIndex; } public boolean selectDrawable(int idx) { if (idx == mCurIndex) { return false; } final long now = SystemClock.uptimeMillis(); if (DEBUG) android.util.Log.i(TAG, toString() + " from " + mCurIndex + " to " + idx + ": exit=" + mDrawableContainerState.mExitFadeDuration + " enter=" + mDrawableContainerState.mEnterFadeDuration); if (mDrawableContainerState.mExitFadeDuration > 0) { if (mLastDrawable != null) { mLastDrawable.setVisible(false, false); } if (mCurrDrawable != null) { mLastDrawable = mCurrDrawable; mLastIndex = mCurIndex; mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration; } else { mLastDrawable = null; mLastIndex = -1; mExitAnimationEnd = 0; } } else if (mCurrDrawable != null) { mCurrDrawable.setVisible(false, false); } if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) { final Drawable d = mDrawableContainerState.getChild(idx); mCurrDrawable = d; mCurIndex = idx; if (d != null) { if (mDrawableContainerState.mEnterFadeDuration > 0) { mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration; } initializeDrawableForDisplay(d); } } else { mCurrDrawable = null; mCurIndex = -1; } if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) { if (mAnimationRunnable == null) { mAnimationRunnable = new Runnable() { @Override public void run() { animate(true); invalidateSelf(); } }; } else { unscheduleSelf(mAnimationRunnable); } // Compute first frame and schedule next animation. animate(true); } invalidateSelf(); return true; } /** * Initializes a drawable for display in this container. * * @param d The drawable to initialize. */ private void initializeDrawableForDisplay(Drawable d) { d.mutate(); if (mDrawableContainerState.mEnterFadeDuration <= 0 && mHasAlpha) { d.setAlpha(mAlpha); } if (mDrawableContainerState.mHasColorFilter) { // Color filter always overrides tint. d.setColorFilter(mDrawableContainerState.mColorFilter); } else { if (mDrawableContainerState.mHasTintList) { d.setTintList(mDrawableContainerState.mTintList); } if (mDrawableContainerState.mHasTintMode) { d.setTintMode(mDrawableContainerState.mTintMode); } } d.setVisible(isVisible(), true); d.setDither(mDrawableContainerState.mDither); d.setState(getState()); d.setLevel(getLevel()); d.setBounds(getBounds()); d.setLayoutDirection(getLayoutDirection()); d.setAutoMirrored(mDrawableContainerState.mAutoMirrored); final Rect hotspotBounds = mHotspotBounds; if (hotspotBounds != null) { d.setHotspotBounds(hotspotBounds.left, hotspotBounds.top, hotspotBounds.right, hotspotBounds.bottom); } } void animate(boolean schedule) { mHasAlpha = true; final long now = SystemClock.uptimeMillis(); boolean animating = false; if (mCurrDrawable != null) { if (mEnterAnimationEnd != 0) { if (mEnterAnimationEnd <= now) { mCurrDrawable.mutate().setAlpha(mAlpha); mEnterAnimationEnd = 0; } else { int animAlpha = (int)((mEnterAnimationEnd-now)*255) / mDrawableContainerState.mEnterFadeDuration; if (DEBUG) android.util.Log.i(TAG, toString() + " cur alpha " + animAlpha); mCurrDrawable.mutate().setAlpha(((255-animAlpha)*mAlpha)/255); animating = true; } } } else { mEnterAnimationEnd = 0; } if (mLastDrawable != null) { if (mExitAnimationEnd != 0) { if (mExitAnimationEnd <= now) { mLastDrawable.setVisible(false, false); mLastDrawable = null; mLastIndex = -1; mExitAnimationEnd = 0; } else { int animAlpha = (int)((mExitAnimationEnd-now)*255) / mDrawableContainerState.mExitFadeDuration; if (DEBUG) android.util.Log.i(TAG, toString() + " last alpha " + animAlpha); mLastDrawable.mutate().setAlpha((animAlpha*mAlpha)/255); animating = true; } } } else { mExitAnimationEnd = 0; } if (schedule && animating) { scheduleSelf(mAnimationRunnable, now + 1000 / 60); } } @Override public Drawable getCurrent() { return mCurrDrawable; } @Override public void applyTheme(Theme theme) { mDrawableContainerState.applyTheme(theme); } @Override public boolean canApplyTheme() { return mDrawableContainerState.canApplyTheme(); } @Override public ConstantState getConstantState() { if (mDrawableContainerState.canConstantState()) { mDrawableContainerState.mChangingConfigurations = getChangingConfigurations(); return mDrawableContainerState; } return null; } @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { final DrawableContainerState clone = cloneConstantState(); clone.mutate(); setConstantState(clone); mMutated = true; } return this; } /** * Returns a shallow copy of the container's constant state to be used as * the base state for {@link #mutate()}. * * @return a shallow copy of the constant state */ DrawableContainerState cloneConstantState() { return mDrawableContainerState; } /** * @hide */ public void clearMutated() { super.clearMutated(); mDrawableContainerState.clearMutated(); mMutated = false; } /** * A ConstantState that can contain several {@link Drawable}s. * * This class was made public to enable testing, and its visibility may change in a future * release. */ public abstract static class DrawableContainerState extends ConstantState { final DrawableContainer mOwner; final Resources mRes; SparseArray mDrawableFutures; int mChangingConfigurations; int mChildrenChangingConfigurations; Drawable[] mDrawables; int mNumChildren; boolean mVariablePadding = false; boolean mPaddingChecked; Rect mConstantPadding; boolean mConstantSize = false; boolean mComputedConstantSize; int mConstantWidth; int mConstantHeight; int mConstantMinimumWidth; int mConstantMinimumHeight; boolean mCheckedOpacity; int mOpacity; boolean mCheckedStateful; boolean mStateful; boolean mCheckedConstantState; boolean mCanConstantState; boolean mDither = DEFAULT_DITHER; boolean mMutated; int mLayoutDirection; int mEnterFadeDuration = 0; int mExitFadeDuration = 0; boolean mAutoMirrored; ColorFilter mColorFilter; boolean mHasColorFilter; ColorStateList mTintList; Mode mTintMode; boolean mHasTintList; boolean mHasTintMode; DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, Resources res) { mOwner = owner; mRes = res != null ? res : orig != null ? orig.mRes : null; if (orig != null) { mChangingConfigurations = orig.mChangingConfigurations; mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; mCheckedConstantState = true; mCanConstantState = true; mVariablePadding = orig.mVariablePadding; mConstantSize = orig.mConstantSize; mDither = orig.mDither; mMutated = orig.mMutated; mLayoutDirection = orig.mLayoutDirection; mEnterFadeDuration = orig.mEnterFadeDuration; mExitFadeDuration = orig.mExitFadeDuration; mAutoMirrored = orig.mAutoMirrored; mColorFilter = orig.mColorFilter; mHasColorFilter = orig.mHasColorFilter; mTintList = orig.mTintList; mTintMode = orig.mTintMode; mHasTintList = orig.mHasTintList; mHasTintMode = orig.mHasTintMode; // Cloning the following values may require creating futures. mConstantPadding = orig.getConstantPadding(); mPaddingChecked = true; mConstantWidth = orig.getConstantWidth(); mConstantHeight = orig.getConstantHeight(); mConstantMinimumWidth = orig.getConstantMinimumWidth(); mConstantMinimumHeight = orig.getConstantMinimumHeight(); mComputedConstantSize = true; mOpacity = orig.getOpacity(); mCheckedOpacity = true; mStateful = orig.isStateful(); mCheckedStateful = true; // Postpone cloning children and futures until we're absolutely // sure that we're done computing values for the original state. final Drawable[] origDr = orig.mDrawables; mDrawables = new Drawable[origDr.length]; mNumChildren = orig.mNumChildren; final SparseArray origDf = orig.mDrawableFutures; if (origDf != null) { mDrawableFutures = origDf.clone(); } else { mDrawableFutures = new SparseArray<>(mNumChildren); } // Create futures for drawables with constant states. If a // drawable doesn't have a constant state, then we can't clone // it and we'll have to reference the original. final int N = mNumChildren; for (int i = 0; i < N; i++) { if (origDr[i] != null) { if (origDr[i].getConstantState() != null) { mDrawableFutures.put(i, new ConstantStateFuture(origDr[i])); } else { mDrawables[i] = origDr[i]; } } } } else { mDrawables = new Drawable[10]; mNumChildren = 0; } } @Override public int getChangingConfigurations() { return mChangingConfigurations | mChildrenChangingConfigurations; } public final int addChild(Drawable dr) { final int pos = mNumChildren; if (pos >= mDrawables.length) { growArray(pos, pos+10); } dr.setVisible(false, true); dr.setCallback(mOwner); mDrawables[pos] = dr; mNumChildren++; mChildrenChangingConfigurations |= dr.getChangingConfigurations(); mCheckedStateful = false; mCheckedOpacity = false; mConstantPadding = null; mPaddingChecked = false; mComputedConstantSize = false; return pos; } final int getCapacity() { return mDrawables.length; } private final void createAllFutures() { if (mDrawableFutures != null) { final int futureCount = mDrawableFutures.size(); for (int keyIndex = 0; keyIndex < futureCount; keyIndex++) { final int index = mDrawableFutures.keyAt(keyIndex); mDrawables[index] = mDrawableFutures.valueAt(keyIndex).get(this); } mDrawableFutures = null; } } public final int getChildCount() { return mNumChildren; } /* * @deprecated Use {@link #getChild} instead. */ public final Drawable[] getChildren() { // Create all futures for backwards compatibility. createAllFutures(); return mDrawables; } public final Drawable getChild(int index) { final Drawable result = mDrawables[index]; if (result != null) { return result; } // Prepare future drawable if necessary. if (mDrawableFutures != null) { final int keyIndex = mDrawableFutures.indexOfKey(index); if (keyIndex >= 0) { final Drawable prepared = mDrawableFutures.valueAt(keyIndex).get(this); mDrawables[index] = prepared; mDrawableFutures.removeAt(keyIndex); if (mDrawableFutures.size() == 0) { mDrawableFutures = null; } return prepared; } } return null; } final boolean setLayoutDirection(int layoutDirection, int currentIndex) { boolean changed = false; // No need to call createAllFutures, since future drawables will // change layout direction when they are prepared. final int N = mNumChildren; final Drawable[] drawables = mDrawables; for (int i = 0; i < N; i++) { if (drawables[i] != null) { final boolean childChanged = drawables[i].setLayoutDirection(layoutDirection); if (i == currentIndex) { changed = childChanged; } } } mLayoutDirection = layoutDirection; return changed; } final void applyTheme(Theme theme) { if (theme != null) { createAllFutures(); final int N = mNumChildren; final Drawable[] drawables = mDrawables; for (int i = 0; i < N; i++) { if (drawables[i] != null && drawables[i].canApplyTheme()) { drawables[i].applyTheme(theme); // Update cached mask of child changing configurations. mChildrenChangingConfigurations |= drawables[i].getChangingConfigurations(); } } } } @Override public boolean canApplyTheme() { final int N = mNumChildren; final Drawable[] drawables = mDrawables; for (int i = 0; i < N; i++) { final Drawable d = drawables[i]; if (d != null) { if (d.canApplyTheme()) { return true; } } else { final ConstantStateFuture future = mDrawableFutures.get(i); if (future != null && future.canApplyTheme()) { return true; } } } return false; } private void mutate() { // No need to call createAllFutures, since future drawables will // mutate when they are prepared. final int N = mNumChildren; final Drawable[] drawables = mDrawables; for (int i = 0; i < N; i++) { if (drawables[i] != null) { drawables[i].mutate(); } } mMutated = true; } final void clearMutated() { final int N = mNumChildren; final Drawable[] drawables = mDrawables; for (int i = 0; i < N; i++) { if (drawables[i] != null) { drawables[i].clearMutated(); } } mMutated = false; } /** * A boolean value indicating whether to use the maximum padding value * of all frames in the set (false), or to use the padding value of the * frame being shown (true). Default value is false. */ public final void setVariablePadding(boolean variable) { mVariablePadding = variable; } public final Rect getConstantPadding() { if (mVariablePadding) { return null; } if ((mConstantPadding != null) || mPaddingChecked) { return mConstantPadding; } createAllFutures(); Rect r = null; final Rect t = new Rect(); final int N = mNumChildren; final Drawable[] drawables = mDrawables; for (int i = 0; i < N; i++) { if (drawables[i].getPadding(t)) { if (r == null) r = new Rect(0, 0, 0, 0); if (t.left > r.left) r.left = t.left; if (t.top > r.top) r.top = t.top; if (t.right > r.right) r.right = t.right; if (t.bottom > r.bottom) r.bottom = t.bottom; } } mPaddingChecked = true; return (mConstantPadding = r); } public final void setConstantSize(boolean constant) { mConstantSize = constant; } public final boolean isConstantSize() { return mConstantSize; } public final int getConstantWidth() { if (!mComputedConstantSize) { computeConstantSize(); } return mConstantWidth; } public final int getConstantHeight() { if (!mComputedConstantSize) { computeConstantSize(); } return mConstantHeight; } public final int getConstantMinimumWidth() { if (!mComputedConstantSize) { computeConstantSize(); } return mConstantMinimumWidth; } public final int getConstantMinimumHeight() { if (!mComputedConstantSize) { computeConstantSize(); } return mConstantMinimumHeight; } protected void computeConstantSize() { mComputedConstantSize = true; createAllFutures(); final int N = mNumChildren; final Drawable[] drawables = mDrawables; mConstantWidth = mConstantHeight = -1; mConstantMinimumWidth = mConstantMinimumHeight = 0; for (int i = 0; i < N; i++) { final Drawable dr = drawables[i]; int s = dr.getIntrinsicWidth(); if (s > mConstantWidth) mConstantWidth = s; s = dr.getIntrinsicHeight(); if (s > mConstantHeight) mConstantHeight = s; s = dr.getMinimumWidth(); if (s > mConstantMinimumWidth) mConstantMinimumWidth = s; s = dr.getMinimumHeight(); if (s > mConstantMinimumHeight) mConstantMinimumHeight = s; } } public final void setEnterFadeDuration(int duration) { mEnterFadeDuration = duration; } public final int getEnterFadeDuration() { return mEnterFadeDuration; } public final void setExitFadeDuration(int duration) { mExitFadeDuration = duration; } public final int getExitFadeDuration() { return mExitFadeDuration; } public final int getOpacity() { if (mCheckedOpacity) { return mOpacity; } createAllFutures(); mCheckedOpacity = true; final int N = mNumChildren; final Drawable[] drawables = mDrawables; int op = (N > 0) ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT; for (int i = 1; i < N; i++) { op = Drawable.resolveOpacity(op, drawables[i].getOpacity()); } mOpacity = op; return op; } public final boolean isStateful() { if (mCheckedStateful) { return mStateful; } createAllFutures(); mCheckedStateful = true; final int N = mNumChildren; final Drawable[] drawables = mDrawables; for (int i = 0; i < N; i++) { if (drawables[i].isStateful()) { mStateful = true; return true; } } mStateful = false; return false; } public void growArray(int oldSize, int newSize) { Drawable[] newDrawables = new Drawable[newSize]; System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize); mDrawables = newDrawables; } public synchronized boolean canConstantState() { if (mCheckedConstantState) { return mCanConstantState; } createAllFutures(); mCheckedConstantState = true; final int N = mNumChildren; final Drawable[] drawables = mDrawables; for (int i = 0; i < N; i++) { if (drawables[i].getConstantState() == null) { mCanConstantState = false; return false; } } mCanConstantState = true; return true; } /** @hide */ @Override public int addAtlasableBitmaps(Collection atlasList) { final int N = mNumChildren; int pixelCount = 0; for (int i = 0; i < N; i++) { final ConstantState state = getChild(i).getConstantState(); if (state != null) { pixelCount += state.addAtlasableBitmaps(atlasList); } } return pixelCount; } /** * Class capable of cloning a Drawable from another Drawable's * ConstantState. */ private static class ConstantStateFuture { private final ConstantState mConstantState; private ConstantStateFuture(Drawable source) { mConstantState = source.getConstantState(); } /** * Obtains and prepares the Drawable represented by this future. * * @param state the container into which this future will be placed * @return a prepared Drawable */ public Drawable get(DrawableContainerState state) { final Drawable result; if (state.mRes == null) { result = mConstantState.newDrawable(); } else { result = mConstantState.newDrawable(state.mRes); } result.setLayoutDirection(state.mLayoutDirection); result.setCallback(state.mOwner); if (state.mMutated) { result.mutate(); } return result; } /** * Whether the constant state wrapped by this future can apply a * theme. */ public boolean canApplyTheme() { return mConstantState.canApplyTheme(); } } } protected void setConstantState(DrawableContainerState state) { mDrawableContainerState = state; // The locally cached drawables may have changed. if (mCurIndex >= 0) { mCurrDrawable = state.getChild(mCurIndex); if (mCurrDrawable != null) { initializeDrawableForDisplay(mCurrDrawable); } } // Clear out the last drawable. We don't have enough information to // propagate local state from the past. mLastIndex = -1; mLastDrawable = null; } }