• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.graphics.drawable;
18 
19 import android.content.res.Resources;
20 import android.graphics.Canvas;
21 import android.graphics.ColorFilter;
22 import android.graphics.Insets;
23 import android.graphics.PixelFormat;
24 import android.graphics.Rect;
25 import android.os.SystemClock;
26 
27 /**
28  * A helper class that contains several {@link Drawable}s and selects which one to use.
29  *
30  * You can subclass it to create your own DrawableContainers or directly use one its child classes.
31  */
32 public class DrawableContainer extends Drawable implements Drawable.Callback {
33     private static final boolean DEBUG = false;
34     private static final String TAG = "DrawableContainer";
35 
36     /**
37      * To be proper, we should have a getter for dither (and alpha, etc.)
38      * so that proxy classes like this can save/restore their delegates'
39      * values, but we don't have getters. Since we do have setters
40      * (e.g. setDither), which this proxy forwards on, we have to have some
41      * default/initial setting.
42      *
43      * The initial setting for dither is now true, since it almost always seems
44      * to improve the quality at negligible cost.
45      */
46     private static final boolean DEFAULT_DITHER = true;
47     private DrawableContainerState mDrawableContainerState;
48     private Drawable mCurrDrawable;
49     private int mAlpha = 0xFF;
50     private ColorFilter mColorFilter;
51 
52     private int mCurIndex = -1;
53     private boolean mMutated;
54 
55     // Animations.
56     private Runnable mAnimationRunnable;
57     private long mEnterAnimationEnd;
58     private long mExitAnimationEnd;
59     private Drawable mLastDrawable;
60 
61     // overrides from Drawable
62 
63     @Override
draw(Canvas canvas)64     public void draw(Canvas canvas) {
65         if (mCurrDrawable != null) {
66             mCurrDrawable.draw(canvas);
67         }
68         if (mLastDrawable != null) {
69             mLastDrawable.draw(canvas);
70         }
71     }
72 
73     @Override
getChangingConfigurations()74     public int getChangingConfigurations() {
75         return super.getChangingConfigurations()
76                 | mDrawableContainerState.mChangingConfigurations
77                 | mDrawableContainerState.mChildrenChangingConfigurations;
78     }
79 
80     @Override
getPadding(Rect padding)81     public boolean getPadding(Rect padding) {
82         final Rect r = mDrawableContainerState.getConstantPadding();
83         if (r != null) {
84             padding.set(r);
85             return true;
86         }
87         if (mCurrDrawable != null) {
88             return mCurrDrawable.getPadding(padding);
89         } else {
90             return super.getPadding(padding);
91         }
92     }
93 
94     /**
95      * @hide
96      */
97     @Override
getLayoutInsets()98     public Insets getLayoutInsets() {
99         return (mCurrDrawable == null) ? Insets.NONE : mCurrDrawable.getLayoutInsets();
100     }
101 
102     @Override
setAlpha(int alpha)103     public void setAlpha(int alpha) {
104         if (mAlpha != alpha) {
105             mAlpha = alpha;
106             if (mCurrDrawable != null) {
107                 if (mEnterAnimationEnd == 0) {
108                     mCurrDrawable.setAlpha(alpha);
109                 } else {
110                     animate(false);
111                 }
112             }
113         }
114     }
115 
116     @Override
setDither(boolean dither)117     public void setDither(boolean dither) {
118         if (mDrawableContainerState.mDither != dither) {
119             mDrawableContainerState.mDither = dither;
120             if (mCurrDrawable != null) {
121                 mCurrDrawable.setDither(mDrawableContainerState.mDither);
122             }
123         }
124     }
125 
126     @Override
setColorFilter(ColorFilter cf)127     public void setColorFilter(ColorFilter cf) {
128         if (mColorFilter != cf) {
129             mColorFilter = cf;
130             if (mCurrDrawable != null) {
131                 mCurrDrawable.setColorFilter(cf);
132             }
133         }
134     }
135 
136     /**
137      * Change the global fade duration when a new drawable is entering
138      * the scene.
139      * @param ms The amount of time to fade in milliseconds.
140      */
setEnterFadeDuration(int ms)141     public void setEnterFadeDuration(int ms) {
142         mDrawableContainerState.mEnterFadeDuration = ms;
143     }
144 
145     /**
146      * Change the global fade duration when a new drawable is leaving
147      * the scene.
148      * @param ms The amount of time to fade in milliseconds.
149      */
setExitFadeDuration(int ms)150     public void setExitFadeDuration(int ms) {
151         mDrawableContainerState.mExitFadeDuration = ms;
152     }
153 
154     @Override
onBoundsChange(Rect bounds)155     protected void onBoundsChange(Rect bounds) {
156         if (mLastDrawable != null) {
157             mLastDrawable.setBounds(bounds);
158         }
159         if (mCurrDrawable != null) {
160             mCurrDrawable.setBounds(bounds);
161         }
162     }
163 
164     @Override
isStateful()165     public boolean isStateful() {
166         return mDrawableContainerState.isStateful();
167     }
168 
169     @Override
jumpToCurrentState()170     public void jumpToCurrentState() {
171         boolean changed = false;
172         if (mLastDrawable != null) {
173             mLastDrawable.jumpToCurrentState();
174             mLastDrawable = null;
175             changed = true;
176         }
177         if (mCurrDrawable != null) {
178             mCurrDrawable.jumpToCurrentState();
179             mCurrDrawable.setAlpha(mAlpha);
180         }
181         if (mExitAnimationEnd != 0) {
182             mExitAnimationEnd = 0;
183             changed = true;
184         }
185         if (mEnterAnimationEnd != 0) {
186             mEnterAnimationEnd = 0;
187             changed = true;
188         }
189         if (changed) {
190             invalidateSelf();
191         }
192     }
193 
194     @Override
onStateChange(int[] state)195     protected boolean onStateChange(int[] state) {
196         if (mLastDrawable != null) {
197             return mLastDrawable.setState(state);
198         }
199         if (mCurrDrawable != null) {
200             return mCurrDrawable.setState(state);
201         }
202         return false;
203     }
204 
205     @Override
onLevelChange(int level)206     protected boolean onLevelChange(int level) {
207         if (mLastDrawable != null) {
208             return mLastDrawable.setLevel(level);
209         }
210         if (mCurrDrawable != null) {
211             return mCurrDrawable.setLevel(level);
212         }
213         return false;
214     }
215 
216     @Override
getIntrinsicWidth()217     public int getIntrinsicWidth() {
218         if (mDrawableContainerState.isConstantSize()) {
219             return mDrawableContainerState.getConstantWidth();
220         }
221         return mCurrDrawable != null ? mCurrDrawable.getIntrinsicWidth() : -1;
222     }
223 
224     @Override
getIntrinsicHeight()225     public int getIntrinsicHeight() {
226         if (mDrawableContainerState.isConstantSize()) {
227             return mDrawableContainerState.getConstantHeight();
228         }
229         return mCurrDrawable != null ? mCurrDrawable.getIntrinsicHeight() : -1;
230     }
231 
232     @Override
getMinimumWidth()233     public int getMinimumWidth() {
234         if (mDrawableContainerState.isConstantSize()) {
235             return mDrawableContainerState.getConstantMinimumWidth();
236         }
237         return mCurrDrawable != null ? mCurrDrawable.getMinimumWidth() : 0;
238     }
239 
240     @Override
getMinimumHeight()241     public int getMinimumHeight() {
242         if (mDrawableContainerState.isConstantSize()) {
243             return mDrawableContainerState.getConstantMinimumHeight();
244         }
245         return mCurrDrawable != null ? mCurrDrawable.getMinimumHeight() : 0;
246     }
247 
invalidateDrawable(Drawable who)248     public void invalidateDrawable(Drawable who) {
249         if (who == mCurrDrawable && getCallback() != null) {
250             getCallback().invalidateDrawable(this);
251         }
252     }
253 
scheduleDrawable(Drawable who, Runnable what, long when)254     public void scheduleDrawable(Drawable who, Runnable what, long when) {
255         if (who == mCurrDrawable && getCallback() != null) {
256             getCallback().scheduleDrawable(this, what, when);
257         }
258     }
259 
unscheduleDrawable(Drawable who, Runnable what)260     public void unscheduleDrawable(Drawable who, Runnable what) {
261         if (who == mCurrDrawable && getCallback() != null) {
262             getCallback().unscheduleDrawable(this, what);
263         }
264     }
265 
266     @Override
setVisible(boolean visible, boolean restart)267     public boolean setVisible(boolean visible, boolean restart) {
268         boolean changed = super.setVisible(visible, restart);
269         if (mLastDrawable != null) {
270             mLastDrawable.setVisible(visible, restart);
271         }
272         if (mCurrDrawable != null) {
273             mCurrDrawable.setVisible(visible, restart);
274         }
275         return changed;
276     }
277 
278     @Override
getOpacity()279     public int getOpacity() {
280         return mCurrDrawable == null || !mCurrDrawable.isVisible() ? PixelFormat.TRANSPARENT :
281                 mDrawableContainerState.getOpacity();
282     }
283 
selectDrawable(int idx)284     public boolean selectDrawable(int idx) {
285         if (idx == mCurIndex) {
286             return false;
287         }
288 
289         final long now = SystemClock.uptimeMillis();
290 
291         if (DEBUG) android.util.Log.i(TAG, toString() + " from " + mCurIndex + " to " + idx
292                 + ": exit=" + mDrawableContainerState.mExitFadeDuration
293                 + " enter=" + mDrawableContainerState.mEnterFadeDuration);
294 
295         if (mDrawableContainerState.mExitFadeDuration > 0) {
296             if (mLastDrawable != null) {
297                 mLastDrawable.setVisible(false, false);
298             }
299             if (mCurrDrawable != null) {
300                 mLastDrawable = mCurrDrawable;
301                 mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
302             } else {
303                 mLastDrawable = null;
304                 mExitAnimationEnd = 0;
305             }
306         } else if (mCurrDrawable != null) {
307             mCurrDrawable.setVisible(false, false);
308         }
309 
310         if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
311             Drawable d = mDrawableContainerState.mDrawables[idx];
312             mCurrDrawable = d;
313             mCurIndex = idx;
314             if (d != null) {
315                 if (mDrawableContainerState.mEnterFadeDuration > 0) {
316                     mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
317                 } else {
318                     d.setAlpha(mAlpha);
319                 }
320                 d.setVisible(isVisible(), true);
321                 d.setDither(mDrawableContainerState.mDither);
322                 d.setColorFilter(mColorFilter);
323                 d.setState(getState());
324                 d.setLevel(getLevel());
325                 d.setBounds(getBounds());
326             }
327         } else {
328             mCurrDrawable = null;
329             mCurIndex = -1;
330         }
331 
332         if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
333             if (mAnimationRunnable == null) {
334                 mAnimationRunnable = new Runnable() {
335                     @Override public void run() {
336                         animate(true);
337                         invalidateSelf();
338                     }
339                 };
340             } else {
341                 unscheduleSelf(mAnimationRunnable);
342             }
343             // Compute first frame and schedule next animation.
344             animate(true);
345         }
346 
347         invalidateSelf();
348 
349         return true;
350     }
351 
animate(boolean schedule)352     void animate(boolean schedule) {
353         final long now = SystemClock.uptimeMillis();
354         boolean animating = false;
355         if (mCurrDrawable != null) {
356             if (mEnterAnimationEnd != 0) {
357                 if (mEnterAnimationEnd <= now) {
358                     mCurrDrawable.setAlpha(mAlpha);
359                     mEnterAnimationEnd = 0;
360                 } else {
361                     int animAlpha = (int)((mEnterAnimationEnd-now)*255)
362                             / mDrawableContainerState.mEnterFadeDuration;
363                     if (DEBUG) android.util.Log.i(TAG, toString() + " cur alpha " + animAlpha);
364                     mCurrDrawable.setAlpha(((255-animAlpha)*mAlpha)/255);
365                     animating = true;
366                 }
367             }
368         } else {
369             mEnterAnimationEnd = 0;
370         }
371         if (mLastDrawable != null) {
372             if (mExitAnimationEnd != 0) {
373                 if (mExitAnimationEnd <= now) {
374                     mLastDrawable.setVisible(false, false);
375                     mLastDrawable = null;
376                     mExitAnimationEnd = 0;
377                 } else {
378                     int animAlpha = (int)((mExitAnimationEnd-now)*255)
379                             / mDrawableContainerState.mExitFadeDuration;
380                     if (DEBUG) android.util.Log.i(TAG, toString() + " last alpha " + animAlpha);
381                     mLastDrawable.setAlpha((animAlpha*mAlpha)/255);
382                     animating = true;
383                 }
384             }
385         } else {
386             mExitAnimationEnd = 0;
387         }
388 
389         if (schedule && animating) {
390             scheduleSelf(mAnimationRunnable, now + 1000/60);
391         }
392     }
393 
394     @Override
getCurrent()395     public Drawable getCurrent() {
396         return mCurrDrawable;
397     }
398 
399     @Override
getConstantState()400     public ConstantState getConstantState() {
401         if (mDrawableContainerState.canConstantState()) {
402             mDrawableContainerState.mChangingConfigurations = getChangingConfigurations();
403             return mDrawableContainerState;
404         }
405         return null;
406     }
407 
408     @Override
mutate()409     public Drawable mutate() {
410         if (!mMutated && super.mutate() == this) {
411             final int N = mDrawableContainerState.getChildCount();
412             final Drawable[] drawables = mDrawableContainerState.getChildren();
413             for (int i = 0; i < N; i++) {
414                 if (drawables[i] != null) drawables[i].mutate();
415             }
416             mMutated = true;
417         }
418         return this;
419     }
420 
421     /**
422      * A ConstantState that can contain several {@link Drawable}s.
423      *
424      * This class was made public to enable testing, and its visibility may change in a future
425      * release.
426      */
427     public abstract static class DrawableContainerState extends ConstantState {
428         final DrawableContainer mOwner;
429 
430         int         mChangingConfigurations;
431         int         mChildrenChangingConfigurations;
432 
433         Drawable[]  mDrawables;
434         int         mNumChildren;
435 
436         boolean     mVariablePadding = false;
437         Rect        mConstantPadding = null;
438 
439         boolean     mConstantSize = false;
440         boolean     mComputedConstantSize = false;
441         int         mConstantWidth;
442         int         mConstantHeight;
443         int         mConstantMinimumWidth;
444         int         mConstantMinimumHeight;
445 
446         boolean     mHaveOpacity = false;
447         int         mOpacity;
448 
449         boolean     mHaveStateful = false;
450         boolean     mStateful;
451 
452         boolean     mCheckedConstantState;
453         boolean     mCanConstantState;
454 
455         boolean     mPaddingChecked = false;
456 
457         boolean     mDither = DEFAULT_DITHER;
458 
459         int         mEnterFadeDuration;
460         int         mExitFadeDuration;
461 
DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, Resources res)462         DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
463                 Resources res) {
464             mOwner = owner;
465 
466             if (orig != null) {
467                 mChangingConfigurations = orig.mChangingConfigurations;
468                 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
469 
470                 final Drawable[] origDr = orig.mDrawables;
471 
472                 mDrawables = new Drawable[origDr.length];
473                 mNumChildren = orig.mNumChildren;
474 
475                 final int N = mNumChildren;
476                 for (int i=0; i<N; i++) {
477                     if (res != null) {
478                         mDrawables[i] = origDr[i].getConstantState().newDrawable(res);
479                     } else {
480                         mDrawables[i] = origDr[i].getConstantState().newDrawable();
481                     }
482                     mDrawables[i].setCallback(owner);
483                 }
484 
485                 mCheckedConstantState = mCanConstantState = true;
486                 mVariablePadding = orig.mVariablePadding;
487                 if (orig.mConstantPadding != null) {
488                     mConstantPadding = new Rect(orig.mConstantPadding);
489                 }
490                 mConstantSize = orig.mConstantSize;
491                 mComputedConstantSize = orig.mComputedConstantSize;
492                 mConstantWidth = orig.mConstantWidth;
493                 mConstantHeight = orig.mConstantHeight;
494 
495                 mHaveOpacity = orig.mHaveOpacity;
496                 mOpacity = orig.mOpacity;
497                 mHaveStateful = orig.mHaveStateful;
498                 mStateful = orig.mStateful;
499 
500                 mDither = orig.mDither;
501 
502                 mEnterFadeDuration = orig.mEnterFadeDuration;
503                 mExitFadeDuration = orig.mExitFadeDuration;
504 
505             } else {
506                 mDrawables = new Drawable[10];
507                 mNumChildren = 0;
508                 mCheckedConstantState = mCanConstantState = false;
509             }
510         }
511 
512         @Override
getChangingConfigurations()513         public int getChangingConfigurations() {
514             return mChangingConfigurations;
515         }
516 
addChild(Drawable dr)517         public final int addChild(Drawable dr) {
518             final int pos = mNumChildren;
519 
520             if (pos >= mDrawables.length) {
521                 growArray(pos, pos+10);
522             }
523 
524             dr.setVisible(false, true);
525             dr.setCallback(mOwner);
526 
527             mDrawables[pos] = dr;
528             mNumChildren++;
529             mChildrenChangingConfigurations |= dr.getChangingConfigurations();
530             mHaveOpacity = false;
531             mHaveStateful = false;
532 
533             mConstantPadding = null;
534             mPaddingChecked = false;
535             mComputedConstantSize = false;
536 
537             return pos;
538         }
539 
getChildCount()540         public final int getChildCount() {
541             return mNumChildren;
542         }
543 
getChildren()544         public final Drawable[] getChildren() {
545             return mDrawables;
546         }
547 
548         /** A boolean value indicating whether to use the maximum padding value of
549           * all frames in the set (false), or to use the padding value of the frame
550           * being shown (true). Default value is false.
551           */
setVariablePadding(boolean variable)552         public final void setVariablePadding(boolean variable) {
553             mVariablePadding = variable;
554         }
555 
getConstantPadding()556         public final Rect getConstantPadding() {
557             if (mVariablePadding) {
558                 return null;
559             }
560             if (mConstantPadding != null || mPaddingChecked) {
561                 return mConstantPadding;
562             }
563 
564             Rect r = null;
565             final Rect t = new Rect();
566             final int N = getChildCount();
567             final Drawable[] drawables = mDrawables;
568             for (int i = 0; i < N; i++) {
569                 if (drawables[i].getPadding(t)) {
570                     if (r == null) r = new Rect(0, 0, 0, 0);
571                     if (t.left > r.left) r.left = t.left;
572                     if (t.top > r.top) r.top = t.top;
573                     if (t.right > r.right) r.right = t.right;
574                     if (t.bottom > r.bottom) r.bottom = t.bottom;
575                 }
576             }
577             mPaddingChecked = true;
578             return (mConstantPadding = r);
579         }
580 
setConstantSize(boolean constant)581         public final void setConstantSize(boolean constant) {
582             mConstantSize = constant;
583         }
584 
isConstantSize()585         public final boolean isConstantSize() {
586             return mConstantSize;
587         }
588 
getConstantWidth()589         public final int getConstantWidth() {
590             if (!mComputedConstantSize) {
591                 computeConstantSize();
592             }
593 
594             return mConstantWidth;
595         }
596 
getConstantHeight()597         public final int getConstantHeight() {
598             if (!mComputedConstantSize) {
599                 computeConstantSize();
600             }
601 
602             return mConstantHeight;
603         }
604 
getConstantMinimumWidth()605         public final int getConstantMinimumWidth() {
606             if (!mComputedConstantSize) {
607                 computeConstantSize();
608             }
609 
610             return mConstantMinimumWidth;
611         }
612 
getConstantMinimumHeight()613         public final int getConstantMinimumHeight() {
614             if (!mComputedConstantSize) {
615                 computeConstantSize();
616             }
617 
618             return mConstantMinimumHeight;
619         }
620 
computeConstantSize()621         protected void computeConstantSize() {
622             mComputedConstantSize = true;
623 
624             final int N = getChildCount();
625             final Drawable[] drawables = mDrawables;
626             mConstantWidth = mConstantHeight = -1;
627             mConstantMinimumWidth = mConstantMinimumHeight = 0;
628             for (int i = 0; i < N; i++) {
629                 Drawable dr = drawables[i];
630                 int s = dr.getIntrinsicWidth();
631                 if (s > mConstantWidth) mConstantWidth = s;
632                 s = dr.getIntrinsicHeight();
633                 if (s > mConstantHeight) mConstantHeight = s;
634                 s = dr.getMinimumWidth();
635                 if (s > mConstantMinimumWidth) mConstantMinimumWidth = s;
636                 s = dr.getMinimumHeight();
637                 if (s > mConstantMinimumHeight) mConstantMinimumHeight = s;
638             }
639         }
640 
setEnterFadeDuration(int duration)641         public final void setEnterFadeDuration(int duration) {
642             mEnterFadeDuration = duration;
643         }
644 
getEnterFadeDuration()645         public final int getEnterFadeDuration() {
646             return mEnterFadeDuration;
647         }
648 
setExitFadeDuration(int duration)649         public final void setExitFadeDuration(int duration) {
650             mExitFadeDuration = duration;
651         }
652 
getExitFadeDuration()653         public final int getExitFadeDuration() {
654             return mExitFadeDuration;
655         }
656 
getOpacity()657         public final int getOpacity() {
658             if (mHaveOpacity) {
659                 return mOpacity;
660             }
661 
662             final int N = getChildCount();
663             final Drawable[] drawables = mDrawables;
664             int op = N > 0 ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT;
665             for (int i = 1; i < N; i++) {
666                 op = Drawable.resolveOpacity(op, drawables[i].getOpacity());
667             }
668             mOpacity = op;
669             mHaveOpacity = true;
670             return op;
671         }
672 
isStateful()673         public final boolean isStateful() {
674             if (mHaveStateful) {
675                 return mStateful;
676             }
677 
678             boolean stateful = false;
679             final int N = getChildCount();
680             for (int i = 0; i < N; i++) {
681                 if (mDrawables[i].isStateful()) {
682                     stateful = true;
683                     break;
684                 }
685             }
686 
687             mStateful = stateful;
688             mHaveStateful = true;
689             return stateful;
690         }
691 
growArray(int oldSize, int newSize)692         public void growArray(int oldSize, int newSize) {
693             Drawable[] newDrawables = new Drawable[newSize];
694             System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize);
695             mDrawables = newDrawables;
696         }
697 
canConstantState()698         public synchronized boolean canConstantState() {
699             if (!mCheckedConstantState) {
700                 mCanConstantState = true;
701                 final int N = mNumChildren;
702                 for (int i=0; i<N; i++) {
703                     if (mDrawables[i].getConstantState() == null) {
704                         mCanConstantState = false;
705                         break;
706                     }
707                 }
708                 mCheckedConstantState = true;
709             }
710 
711             return mCanConstantState;
712         }
713     }
714 
setConstantState(DrawableContainerState state)715     protected void setConstantState(DrawableContainerState state)
716     {
717         mDrawableContainerState = state;
718     }
719 }
720