OSDN Git Service

Postpone creating new drawables for DrawableContainerState.
authorAlan Viverette <alanv@google.com>
Thu, 13 Jun 2013 22:11:16 +0000 (15:11 -0700)
committerAlan Viverette <alanv@google.com>
Thu, 13 Jun 2013 22:11:16 +0000 (15:11 -0700)
When creating a DrawableContainerState from a constant state, calls to
ConstantState.newDrawable() are postponed and made as they are needed.

Bug: 9280861
Change-Id: I03c93a43ee00aca3ff618d66d7f507f1722538d1

graphics/java/android/graphics/drawable/DrawableContainer.java
graphics/java/android/graphics/drawable/StateListDrawable.java

index e75ec75..f9cd4e2 100644 (file)
@@ -23,6 +23,7 @@ import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.os.SystemClock;
+import android.util.SparseArray;
 
 /**
  * A helper class that contains several {@link Drawable}s and selects which one to use.
@@ -76,7 +77,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
                 | mDrawableContainerState.mChangingConfigurations
                 | mDrawableContainerState.mChildrenChangingConfigurations;
     }
-    
+
     @Override
     public boolean getPadding(Rect padding) {
         final Rect r = mDrawableContainerState.getConstantPadding();
@@ -137,7 +138,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
             }
         }
     }
-    
+
     /**
      * Change the global fade duration when a new drawable is entering
      * the scene.
@@ -146,7 +147,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
     public void setEnterFadeDuration(int ms) {
         mDrawableContainerState.mEnterFadeDuration = ms;
     }
-    
+
     /**
      * Change the global fade duration when a new drawable is leaving
      * the scene.
@@ -155,7 +156,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
     public void setExitFadeDuration(int ms) {
         mDrawableContainerState.mExitFadeDuration = ms;
     }
-    
+
     @Override
     protected void onBoundsChange(Rect bounds) {
         if (mLastDrawable != null) {
@@ -165,12 +166,12 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
             mCurrDrawable.setBounds(bounds);
         }
     }
-    
+
     @Override
     public boolean isStateful() {
         return mDrawableContainerState.isStateful();
     }
-    
+
     @Override
     public void jumpToCurrentState() {
         boolean changed = false;
@@ -233,7 +234,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
         }
         return mCurrDrawable != null ? mCurrDrawable.getIntrinsicHeight() : -1;
     }
-    
+
     @Override
     public int getMinimumWidth() {
         if (mDrawableContainerState.isConstantSize()) {
@@ -316,7 +317,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
         }
 
         if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
-            Drawable d = mDrawableContainerState.mDrawables[idx];
+            final Drawable d = mDrawableContainerState.getChild(idx);
             mCurrDrawable = d;
             mCurIndex = idx;
             if (d != null) {
@@ -358,7 +359,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
 
         return true;
     }
-    
+
     void animate(boolean schedule) {
         final long now = SystemClock.uptimeMillis();
         boolean animating = false;
@@ -432,90 +433,105 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
      */
     public abstract static class DrawableContainerState extends ConstantState {
         final DrawableContainer mOwner;
+        final Resources mRes;
+
+        SparseArray<ConstantStateFuture> mDrawableFutures;
 
-        int         mChangingConfigurations;
-        int         mChildrenChangingConfigurations;
-        
-        Drawable[]  mDrawables;
-        int         mNumChildren;
+        int mChangingConfigurations;
+        int mChildrenChangingConfigurations;
 
-        boolean     mVariablePadding = false;
-        Rect        mConstantPadding = null;
+        Drawable[] mDrawables;
+        int mNumChildren;
 
-        boolean     mConstantSize = false;
-        boolean     mComputedConstantSize = false;
-        int         mConstantWidth;
-        int         mConstantHeight;
-        int         mConstantMinimumWidth;
-        int         mConstantMinimumHeight;
+        boolean mVariablePadding;
+        boolean mPaddingChecked;
+        Rect mConstantPadding;
 
-        int         mOpacity;
+        boolean mConstantSize;
+        boolean mComputedConstantSize;
+        int mConstantWidth;
+        int mConstantHeight;
+        int mConstantMinimumWidth;
+        int mConstantMinimumHeight;
 
-        boolean     mHaveStateful = false;
-        boolean     mStateful;
+        boolean mCheckedOpacity;
+        int mOpacity;
 
-        boolean     mCheckedConstantState;
-        boolean     mCanConstantState;
+        boolean mCheckedStateful;
+        boolean mStateful;
 
-        boolean     mPaddingChecked = false;
-        
-        boolean     mDither = DEFAULT_DITHER;        
+        boolean mCheckedConstantState;
+        boolean mCanConstantState;
 
-        int         mEnterFadeDuration;
-        int         mExitFadeDuration;
+        boolean mDither = DEFAULT_DITHER;
+
+        boolean mMutated;
+        int mLayoutDirection;
+
+        int mEnterFadeDuration;
+        int mExitFadeDuration;
 
         DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
                 Resources res) {
             mOwner = owner;
+            mRes = res;
 
             if (orig != null) {
                 mChangingConfigurations = orig.mChangingConfigurations;
                 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
-                
-                final Drawable[] origDr = orig.mDrawables;
 
-                mDrawables = new Drawable[origDr.length];
-                mNumChildren = orig.mNumChildren;
-
-                final int N = mNumChildren;
-                for (int i=0; i<N; i++) {
-                    if (res != null) {
-                        mDrawables[i] = origDr[i].getConstantState().newDrawable(res);
-                    } else {
-                        mDrawables[i] = origDr[i].getConstantState().newDrawable();
-                    }
-                    mDrawables[i].setCallback(owner);
-                    mDrawables[i].setLayoutDirection(origDr[i].getLayoutDirection());
-                }
+                mCheckedConstantState = true;
+                mCanConstantState = true;
 
-                mCheckedConstantState = mCanConstantState = true;
                 mVariablePadding = orig.mVariablePadding;
-                if (orig.mConstantPadding != null) {
-                    mConstantPadding = new Rect(orig.mConstantPadding);
-                }
                 mConstantSize = orig.mConstantSize;
-                mComputedConstantSize = orig.mComputedConstantSize;
-                mConstantWidth = orig.mConstantWidth;
-                mConstantHeight = orig.mConstantHeight;
-                mConstantMinimumWidth = orig.mConstantMinimumWidth;
-                mConstantMinimumHeight = orig.mConstantMinimumHeight;
-                
-                mOpacity = orig.mOpacity;
-                mHaveStateful = orig.mHaveStateful;
-                mStateful = orig.mStateful;
-                
                 mDither = orig.mDither;
-
+                mMutated = orig.mMutated;
+                mLayoutDirection = orig.mLayoutDirection;
                 mEnterFadeDuration = orig.mEnterFadeDuration;
                 mExitFadeDuration = orig.mExitFadeDuration;
 
+                // 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<ConstantStateFuture> origDf = orig.mDrawableFutures;
+                if (origDf != null) {
+                    mDrawableFutures = origDf.clone();
+                } else {
+                    mDrawableFutures = new SparseArray<ConstantStateFuture>(mNumChildren);
+                }
+
+                final int N = mNumChildren;
+                for (int i = 0; i < N; i++) {
+                    if (origDr[i] != null) {
+                        mDrawableFutures.put(i, new ConstantStateFuture(origDr[i]));
+                    }
+                }
             } else {
                 mDrawables = new Drawable[10];
                 mNumChildren = 0;
-                mCheckedConstantState = mCanConstantState = false;
             }
         }
-        
+
         @Override
         public int getChangingConfigurations() {
             return mChangingConfigurations | mChildrenChangingConfigurations;
@@ -534,7 +550,8 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
             mDrawables[pos] = dr;
             mNumChildren++;
             mChildrenChangingConfigurations |= dr.getChangingConfigurations();
-            mHaveStateful = false;
+            mCheckedStateful = false;
+            mCheckedOpacity = false;
 
             mConstantPadding = null;
             mPaddingChecked = false;
@@ -547,6 +564,18 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
             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;
         }
@@ -555,25 +584,65 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
          * @deprecated Use {@link #getChild} instead.
          */
         public final Drawable[] getChildren() {
+            // Create all futures for backwards compatibility.
+            createAllFutures();
+
             return mDrawables;
         }
 
         public final Drawable getChild(int index) {
-            return mDrawables[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);
+                    return prepared;
+                }
+            }
+
+            return null;
+        }
+
+        final void setLayoutDirection(int layoutDirection) {
+            // 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) {
+                    drawables[i].setLayoutDirection(layoutDirection);
+                }
+            }
+
+            mLayoutDirection = layoutDirection;
         }
 
         final void mutate() {
-            final int N = getChildCount();
+            // 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();
+                if (drawables[i] != null) {
+                    drawables[i].mutate();
+                }
             }
+
+            mMutated = true;
         }
 
-        /** 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. 
-          */
+        /**
+         * 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;
         }
@@ -582,13 +651,16 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
             if (mVariablePadding) {
                 return null;
             }
-            if (mConstantPadding != null || mPaddingChecked) {
+
+            if ((mConstantPadding != null) || mPaddingChecked) {
                 return mConstantPadding;
             }
 
+            createAllFutures();
+
             Rect r = null;
             final Rect t = new Rect();
-            final int N = getChildCount();
+            final int N = mNumChildren;
             final Drawable[] drawables = mDrawables;
             for (int i = 0; i < N; i++) {
                 if (drawables[i].getPadding(t)) {
@@ -599,6 +671,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
                     if (t.bottom > r.bottom) r.bottom = t.bottom;
                 }
             }
+
             mPaddingChecked = true;
             return (mConstantPadding = r);
         }
@@ -646,12 +719,14 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
         protected void computeConstantSize() {
             mComputedConstantSize = true;
 
-            final int N = getChildCount();
+            createAllFutures();
+
+            final int N = mNumChildren;
             final Drawable[] drawables = mDrawables;
             mConstantWidth = mConstantHeight = -1;
             mConstantMinimumWidth = mConstantMinimumHeight = 0;
             for (int i = 0; i < N; i++) {
-                Drawable dr = drawables[i];
+                final Drawable dr = drawables[i];
                 int s = dr.getIntrinsicWidth();
                 if (s > mConstantWidth) mConstantWidth = s;
                 s = dr.getIntrinsicHeight();
@@ -680,33 +755,45 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
         }
 
         public final int getOpacity() {
-            final int N = getChildCount();
+            if (mCheckedOpacity) {
+                return mOpacity;
+            }
+
+            createAllFutures();
+
+            mCheckedOpacity = true;
+
+            final int N = mNumChildren;
             final Drawable[] drawables = mDrawables;
-            int op = N > 0 ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT;
+            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 (mHaveStateful) {
+            if (mCheckedStateful) {
                 return mStateful;
             }
-            
-            boolean stateful = false;
-            final int N = getChildCount();
+
+            createAllFutures();
+
+            mCheckedStateful = true;
+
+            final int N = mNumChildren;
+            final Drawable[] drawables = mDrawables;
             for (int i = 0; i < N; i++) {
-                if (mDrawables[i].isStateful()) {
-                    stateful = true;
-                    break;
+                if (drawables[i].isStateful()) {
+                    mStateful = true;
+                    return true;
                 }
             }
-            
-            mStateful = stateful;
-            mHaveStateful = true;
-            return stateful;
+
+            mStateful = false;
+            return false;
         }
 
         public void growArray(int oldSize, int newSize) {
@@ -716,24 +803,60 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
         }
 
         public synchronized boolean canConstantState() {
-            if (!mCheckedConstantState) {
-                mCanConstantState = true;
-                final int N = mNumChildren;
-                for (int i=0; i<N; i++) {
-                    if (mDrawables[i].getConstantState() == null) {
-                        mCanConstantState = false;
-                        break;
-                    }
+            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;
                 }
-                mCheckedConstantState = true;
             }
 
-            return mCanConstantState;
+            mCanConstantState = true;
+            return true;
+        }
+
+        /**
+         * 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 = (state.mRes == null) ?
+                        mConstantState.newDrawable() : mConstantState.newDrawable(state.mRes);
+                result.setLayoutDirection(state.mLayoutDirection);
+                result.setCallback(state.mOwner);
+
+                if (state.mMutated) {
+                    result.mutate();
+                }
+
+                return result;
+            }
         }
     }
 
-    protected void setConstantState(DrawableContainerState state)
-    {
+    protected void setConstantState(DrawableContainerState state) {
         mDrawableContainerState = state;
     }
 }
index 5ecd296..e3c7bc5 100644 (file)
@@ -264,11 +264,11 @@ public class StateListDrawable extends DrawableContainer {
     /** @hide */
     @Override
     public void setLayoutDirection(int layoutDirection) {
-        final int numStates = getStateCount();
-        for (int i = 0; i < numStates; i++) {
-            getStateDrawable(i).setLayoutDirection(layoutDirection);
-        }
         super.setLayoutDirection(layoutDirection);
+
+        // Let the container handle setting its own layout direction. Otherwise,
+        // we're accessing potentially unused states.
+        mStateListState.setLayoutDirection(layoutDirection);
     }
 
     static final class StateListState extends DrawableContainerState {