DrawableContainer.java revision 211db4a2874f1a2d0e7a8cb8d33e81fa08801763
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
17package android.graphics.drawable;
18
19import android.graphics.*;
20
21public class DrawableContainer extends Drawable implements Drawable.Callback {
22
23    /**
24     * To be proper, we should have a getter for dither (and alpha, etc.)
25     * so that proxy classes like this can save/restore their delegates'
26     * values, but we don't have getters. Since we do have setters
27     * (e.g. setDither), which this proxy forwards on, we have to have some
28     * default/initial setting.
29     *
30     * The initial setting for dither is now true, since it almost always seems
31     * to improve the quality at negligible cost.
32     */
33    private static final boolean DEFAULT_DITHER = true;
34    private DrawableContainerState mDrawableContainerState;
35    private Drawable mCurrDrawable;
36    private int mAlpha = 0xFF;
37    private ColorFilter mColorFilter;
38    private boolean mDither = DEFAULT_DITHER;
39
40    private int mCurIndex = -1;
41    private boolean mMutated;
42
43    // overrides from Drawable
44
45    @Override
46    public void draw(Canvas canvas) {
47        if (mCurrDrawable != null) {
48            mCurrDrawable.draw(canvas);
49        }
50    }
51
52    @Override
53    public int getChangingConfigurations() {
54        return super.getChangingConfigurations()
55                | mDrawableContainerState.mChangingConfigurations
56                | mDrawableContainerState.mChildrenChangingConfigurations;
57    }
58
59    @Override
60    public boolean getPadding(Rect padding) {
61        final Rect r = mDrawableContainerState.getConstantPadding();
62        if (r != null) {
63            padding.set(r);
64            return true;
65        }
66        if (mCurrDrawable != null) {
67            return mCurrDrawable.getPadding(padding);
68        } else {
69            return super.getPadding(padding);
70        }
71    }
72
73    @Override
74    public void setAlpha(int alpha) {
75        if (mAlpha != alpha) {
76            mAlpha = alpha;
77            if (mCurrDrawable != null) {
78                mCurrDrawable.setAlpha(alpha);
79            }
80        }
81    }
82
83    @Override
84    public void setDither(boolean dither) {
85        if (mDither != dither) {
86            mDither = dither;
87            if (mCurrDrawable != null) {
88                mCurrDrawable.setDither(mDither);
89            }
90        }
91    }
92
93    @Override
94    public void setColorFilter(ColorFilter cf) {
95        if (mColorFilter != cf) {
96            mColorFilter = cf;
97            if (mCurrDrawable != null) {
98                mCurrDrawable.setColorFilter(cf);
99            }
100        }
101    }
102
103    @Override
104    protected void onBoundsChange(Rect bounds) {
105        if (mCurrDrawable != null) {
106            mCurrDrawable.setBounds(bounds);
107        }
108    }
109
110    @Override
111    public boolean isStateful() {
112        return mDrawableContainerState.isStateful();
113    }
114
115    @Override
116    protected boolean onStateChange(int[] state) {
117        if (mCurrDrawable != null) {
118            return mCurrDrawable.setState(state);
119        }
120        return false;
121    }
122
123    @Override
124    protected boolean onLevelChange(int level) {
125        if (mCurrDrawable != null) {
126            return mCurrDrawable.setLevel(level);
127        }
128        return false;
129    }
130
131    @Override
132    public int getIntrinsicWidth() {
133        if (mDrawableContainerState.isConstantSize()) {
134            return mDrawableContainerState.getConstantWidth();
135        }
136        return mCurrDrawable != null ? mCurrDrawable.getIntrinsicWidth() : -1;
137    }
138
139    @Override
140    public int getIntrinsicHeight() {
141        if (mDrawableContainerState.isConstantSize()) {
142            return mDrawableContainerState.getConstantHeight();
143        }
144        return mCurrDrawable != null ? mCurrDrawable.getIntrinsicHeight() : -1;
145    }
146
147    @Override
148    public int getMinimumWidth() {
149        if (mDrawableContainerState.isConstantSize()) {
150            return mDrawableContainerState.getConstantMinimumWidth();
151        }
152        return mCurrDrawable != null ? mCurrDrawable.getMinimumWidth() : 0;
153    }
154
155    @Override
156    public int getMinimumHeight() {
157        if (mDrawableContainerState.isConstantSize()) {
158            return mDrawableContainerState.getConstantMinimumHeight();
159        }
160        return mCurrDrawable != null ? mCurrDrawable.getMinimumHeight() : 0;
161    }
162
163    public void invalidateDrawable(Drawable who)
164    {
165        if (who == mCurrDrawable && mCallback != null) {
166            mCallback.invalidateDrawable(this);
167        }
168    }
169
170    public void scheduleDrawable(Drawable who, Runnable what, long when)
171    {
172        if (who == mCurrDrawable && mCallback != null) {
173            mCallback.scheduleDrawable(this, what, when);
174        }
175    }
176
177    public void unscheduleDrawable(Drawable who, Runnable what)
178    {
179        if (who == mCurrDrawable && mCallback != null) {
180            mCallback.unscheduleDrawable(this, what);
181        }
182    }
183
184    @Override
185    public boolean setVisible(boolean visible, boolean restart) {
186        boolean changed = super.setVisible(visible, restart);
187        if (mCurrDrawable != null) {
188            mCurrDrawable.setVisible(visible, restart);
189        }
190        return changed;
191    }
192
193    @Override
194    public int getOpacity() {
195        return mCurrDrawable == null || !mCurrDrawable.isVisible() ? PixelFormat.TRANSPARENT :
196                mDrawableContainerState.getOpacity();
197    }
198
199    public boolean selectDrawable(int idx)
200    {
201        if (idx == mCurIndex) {
202            return false;
203        }
204        if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
205            Drawable d = mDrawableContainerState.mDrawables[idx];
206            if (mCurrDrawable != null) {
207                mCurrDrawable.setVisible(false, false);
208            }
209            mCurrDrawable = d;
210            mCurIndex = idx;
211            if (d != null) {
212                d.setVisible(isVisible(), true);
213                d.setAlpha(mAlpha);
214                d.setDither(mDither);
215                d.setColorFilter(mColorFilter);
216                d.setState(getState());
217                d.setLevel(getLevel());
218                d.setBounds(getBounds());
219            }
220        } else {
221            if (mCurrDrawable != null) {
222                mCurrDrawable.setVisible(false, false);
223            }
224            mCurrDrawable = null;
225            mCurIndex = -1;
226        }
227        invalidateSelf();
228        return true;
229    }
230
231    @Override
232    public Drawable getCurrent() {
233        return mCurrDrawable;
234    }
235
236    @Override
237    public ConstantState getConstantState() {
238        if (mDrawableContainerState.canConstantState()) {
239            mDrawableContainerState.mChangingConfigurations = super.getChangingConfigurations();
240            return mDrawableContainerState;
241        }
242        return null;
243    }
244
245    @Override
246    public Drawable mutate() {
247        if (!mMutated && super.mutate() == this) {
248            final int N = mDrawableContainerState.getChildCount();
249            final Drawable[] drawables = mDrawableContainerState.getChildren();
250            for (int i = 0; i < N; i++) {
251                if (drawables[i] != null) drawables[i].mutate();
252            }
253            mMutated = true;
254        }
255        return this;
256    }
257
258    public abstract static class DrawableContainerState extends ConstantState {
259        final DrawableContainer mOwner;
260
261        int         mChangingConfigurations;
262        int         mChildrenChangingConfigurations;
263
264        Drawable[]  mDrawables;
265        int         mNumChildren;
266
267        boolean     mVariablePadding = false;
268        Rect        mConstantPadding = null;
269
270        boolean     mConstantSize = false;
271        boolean     mComputedConstantSize = false;
272        int         mConstantWidth;
273        int         mConstantHeight;
274        int         mConstantMinimumWidth;
275        int         mConstantMinimumHeight;
276
277        boolean     mHaveOpacity = false;
278        int         mOpacity;
279
280        boolean     mHaveStateful = false;
281        boolean     mStateful;
282
283        boolean     mCheckedConstantState;
284        boolean     mCanConstantState;
285
286        boolean     mPaddingChecked = false;
287
288        DrawableContainerState(DrawableContainerState orig, DrawableContainer owner) {
289            mOwner = owner;
290
291            if (orig != null) {
292                mChangingConfigurations = orig.mChangingConfigurations;
293                mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
294
295                final Drawable[] origDr = orig.mDrawables;
296
297                mDrawables = new Drawable[origDr.length];
298                mNumChildren = orig.mNumChildren;
299
300                final int N = mNumChildren;
301                for (int i=0; i<N; i++) {
302                    mDrawables[i] = origDr[i].getConstantState().newDrawable();
303                    mDrawables[i].setCallback(owner);
304                }
305
306                mCheckedConstantState = mCanConstantState = true;
307                mVariablePadding = orig.mVariablePadding;
308                if (orig.mConstantPadding != null) {
309                    mConstantPadding = new Rect(orig.mConstantPadding);
310                }
311                mConstantSize = orig.mConstantSize;
312                mComputedConstantSize = orig.mComputedConstantSize;
313                mConstantWidth = orig.mConstantWidth;
314                mConstantHeight = orig.mConstantHeight;
315
316                mHaveOpacity = orig.mHaveOpacity;
317                mOpacity = orig.mOpacity;
318                mHaveStateful = orig.mHaveStateful;
319                mStateful = orig.mStateful;
320
321            } else {
322                mDrawables = new Drawable[10];
323                mNumChildren = 0;
324                mCheckedConstantState = mCanConstantState = false;
325            }
326        }
327
328        @Override
329        public int getChangingConfigurations() {
330            return mChangingConfigurations;
331        }
332
333        public final int addChild(Drawable dr) {
334            final int pos = mNumChildren;
335
336            if (pos >= mDrawables.length) {
337                growArray(pos, pos+10);
338            }
339
340            dr.setVisible(false, true);
341            dr.setCallback(mOwner);
342
343            mDrawables[pos] = dr;
344            mNumChildren++;
345            mChildrenChangingConfigurations |= dr.getChangingConfigurations();
346            mHaveOpacity = false;
347            mHaveStateful = false;
348
349            mConstantPadding = null;
350            mPaddingChecked = false;
351            mComputedConstantSize = false;
352
353            return pos;
354        }
355
356        public final int getChildCount() {
357            return mNumChildren;
358        }
359
360        public final Drawable[] getChildren() {
361            return mDrawables;
362        }
363
364        /** A boolean value indicating whether to use the maximum padding value of
365          * all frames in the set (false), or to use the padding value of the frame
366          * being shown (true). Default value is false.
367          */
368        public final void setVariablePadding(boolean variable) {
369            mVariablePadding = variable;
370        }
371
372        public final Rect getConstantPadding() {
373            if (mVariablePadding) {
374                return null;
375            }
376            if (mConstantPadding != null || mPaddingChecked) {
377                return mConstantPadding;
378            }
379
380            Rect r = null;
381            final Rect t = new Rect();
382            final int N = getChildCount();
383            final Drawable[] drawables = mDrawables;
384            for (int i = 0; i < N; i++) {
385                if (drawables[i].getPadding(t)) {
386                    if (r == null) r = new Rect(0, 0, 0, 0);
387                    if (t.left > r.left) r.left = t.left;
388                    if (t.top > r.top) r.top = t.top;
389                    if (t.right > r.right) r.right = t.right;
390                    if (t.bottom > r.bottom) r.bottom = t.bottom;
391                }
392            }
393            mPaddingChecked = true;
394            return (mConstantPadding = r);
395        }
396
397        public final void setConstantSize(boolean constant) {
398            mConstantSize = constant;
399        }
400
401        public final boolean isConstantSize() {
402            return mConstantSize;
403        }
404
405        public final int getConstantWidth() {
406            if (!mComputedConstantSize) {
407                computeConstantSize();
408            }
409
410            return mConstantWidth;
411        }
412
413        public final int getConstantHeight() {
414            if (!mComputedConstantSize) {
415                computeConstantSize();
416            }
417
418            return mConstantHeight;
419        }
420
421        public final int getConstantMinimumWidth() {
422            if (!mComputedConstantSize) {
423                computeConstantSize();
424            }
425
426            return mConstantMinimumWidth;
427        }
428
429        public final int getConstantMinimumHeight() {
430            if (!mComputedConstantSize) {
431                computeConstantSize();
432            }
433
434            return mConstantMinimumHeight;
435        }
436
437        private void computeConstantSize() {
438            mComputedConstantSize = true;
439
440            final int N = getChildCount();
441            final Drawable[] drawables = mDrawables;
442            mConstantWidth = mConstantHeight = 0;
443            mConstantMinimumWidth = mConstantMinimumHeight = 0;
444            for (int i = 0; i < N; i++) {
445                Drawable dr = drawables[i];
446                int s = dr.getIntrinsicWidth();
447                if (s > mConstantWidth) mConstantWidth = s;
448                s = dr.getIntrinsicHeight();
449                if (s > mConstantHeight) mConstantHeight = s;
450                s = dr.getMinimumWidth();
451                if (s > mConstantMinimumWidth) mConstantMinimumWidth = s;
452                s = dr.getMinimumHeight();
453                if (s > mConstantMinimumHeight) mConstantMinimumHeight = s;
454            }
455        }
456
457        public final int getOpacity() {
458            if (mHaveOpacity) {
459                return mOpacity;
460            }
461
462            final int N = getChildCount();
463            final Drawable[] drawables = mDrawables;
464            int op = N > 0 ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT;
465            for (int i = 1; i < N; i++) {
466                op = Drawable.resolveOpacity(op, drawables[i].getOpacity());
467            }
468            mOpacity = op;
469            mHaveOpacity = true;
470            return op;
471        }
472
473        public final boolean isStateful() {
474            if (mHaveStateful) {
475                return mStateful;
476            }
477
478            boolean stateful = false;
479            final int N = getChildCount();
480            for (int i = 0; i < N; i++) {
481                if (mDrawables[i].isStateful()) {
482                    stateful = true;
483                    break;
484                }
485            }
486
487            mStateful = stateful;
488            mHaveStateful = true;
489            return stateful;
490        }
491
492        public void growArray(int oldSize, int newSize) {
493            Drawable[] newDrawables = new Drawable[newSize];
494            System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize);
495            mDrawables = newDrawables;
496        }
497
498        public synchronized boolean canConstantState() {
499            if (!mCheckedConstantState) {
500                mCanConstantState = true;
501                final int N = mNumChildren;
502                for (int i=0; i<N; i++) {
503                    if (mDrawables[i].getConstantState() == null) {
504                        mCanConstantState = false;
505                        break;
506                    }
507                }
508                mCheckedConstantState = true;
509            }
510
511            return mCanConstantState;
512        }
513    }
514
515    protected void setConstantState(DrawableContainerState state)
516    {
517        mDrawableContainerState = state;
518    }
519}
520