CompositeDrawable.java revision 00f780c9e3cca0b2e364c61e936147e09877c43f
1/*
2 * Copyright (C) 2015 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 */
16package android.support.v17.leanback.graphics;
17
18import android.annotation.TargetApi;
19import android.content.res.Resources;
20import android.graphics.Canvas;
21import android.graphics.ColorFilter;
22import android.graphics.PixelFormat;
23import android.graphics.Rect;
24import android.graphics.drawable.Drawable;
25import android.os.Build;
26import android.support.annotation.NonNull;
27import android.support.v4.graphics.drawable.DrawableCompat;
28import android.util.Property;
29
30import java.util.ArrayList;
31
32/**
33 * Generic drawable class that can be composed of multiple children. Whenever the bounds changes
34 * for this class, it updates those of it's children.
35 */
36@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
37public class CompositeDrawable extends Drawable implements Drawable.Callback {
38
39    static class CompositeState extends Drawable.ConstantState {
40
41        final ArrayList<ChildDrawable> mChildren;
42
43        CompositeState() {
44            mChildren = new ArrayList<ChildDrawable>();
45        }
46
47        CompositeState(CompositeState other, CompositeDrawable parent, Resources res) {
48            final int n = other.mChildren.size();
49            mChildren = new ArrayList<ChildDrawable>(n);
50            for (int k = 0; k < n; k++) {
51                mChildren.add(new ChildDrawable(other.mChildren.get(k), parent, res));
52            }
53        }
54
55        @NonNull
56        @Override
57        public Drawable newDrawable() {
58            return new CompositeDrawable(this);
59        }
60
61        @Override
62        public int getChangingConfigurations() {
63            return 0;
64        }
65
66    }
67
68    CompositeState mState;
69    boolean mMutated = false;
70
71    public CompositeDrawable() {
72        mState = new CompositeState();
73    }
74
75    CompositeDrawable(CompositeState state) {
76        mState = state;
77    }
78
79    @Override
80    public ConstantState getConstantState() {
81        return mState;
82    }
83
84    @Override
85    public Drawable mutate() {
86        if (!mMutated && super.mutate() == this) {
87            mState = new CompositeState(mState, this, null);
88            final ArrayList<ChildDrawable> children = mState.mChildren;
89            for (int i = 0, n = children.size(); i < n; i++) {
90                final Drawable dr = children.get(i).mDrawable;
91                if (dr != null) {
92                    dr.mutate();
93                }
94            }
95            mMutated = true;
96        }
97        return this;
98    }
99
100    /**
101     * Adds the supplied region.
102     */
103    public void addChildDrawable(Drawable drawable) {
104        mState.mChildren.add(new ChildDrawable(drawable, this));
105    }
106
107    /**
108     * Returns the {@link Drawable} for the given index.
109     */
110    public Drawable getDrawable(int index) {
111        return mState.mChildren.get(index).mDrawable;
112    }
113
114    /**
115     * Returns the {@link ChildDrawable} at the given index.
116     */
117    public ChildDrawable getChildAt(int index) {
118        return mState.mChildren.get(index);
119    }
120
121    /**
122     * Removes the child corresponding to the given index.
123     */
124    public void removeChild(int index) {
125        mState.mChildren.remove(index);
126    }
127
128    /**
129     * Removes the given region.
130     */
131    public void removeDrawable(Drawable drawable) {
132        final ArrayList<ChildDrawable> children = mState.mChildren;
133        for (int i = 0; i < children.size(); i++) {
134            if (drawable == children.get(i).mDrawable) {
135                children.get(i).mDrawable.setCallback(null);
136                children.remove(i);
137                return;
138            }
139        }
140    }
141
142    /**
143     * Returns the total number of children.
144     */
145    public int getChildCount() {
146        return mState.mChildren.size();
147    }
148
149    @Override
150    public void draw(Canvas canvas) {
151        final ArrayList<ChildDrawable> children = mState.mChildren;
152        for (int i = 0; i < children.size(); i++) {
153            children.get(i).mDrawable.draw(canvas);
154        }
155    }
156
157    @Override
158    protected void onBoundsChange(Rect bounds) {
159        super.onBoundsChange(bounds);
160        updateBounds(bounds);
161    }
162
163    @Override
164    public void setColorFilter(ColorFilter colorFilter) {
165        final ArrayList<ChildDrawable> children = mState.mChildren;
166        for (int i = 0; i < children.size(); i++) {
167            children.get(i).mDrawable.setColorFilter(colorFilter);
168        }
169    }
170
171    @Override
172    public int getOpacity() {
173        return PixelFormat.UNKNOWN;
174    }
175
176    @Override
177    public void setAlpha(int alpha) {
178        final ArrayList<ChildDrawable> children = mState.mChildren;
179        for (int i = 0; i < children.size(); i++) {
180            children.get(i).mDrawable.setAlpha(alpha);
181        }
182    }
183
184    /**
185     * @return Alpha value between 0(inclusive) and 255(inclusive)
186     */
187    public int getAlpha() {
188        final Drawable dr = getFirstNonNullDrawable();
189        if (dr != null) {
190            return DrawableCompat.getAlpha(dr);
191        } else {
192            return 0xFF;
193        }
194    }
195
196    final Drawable getFirstNonNullDrawable() {
197        final ArrayList<ChildDrawable> children = mState.mChildren;
198        for (int i = 0, n = children.size(); i < n; i++) {
199            final Drawable dr = children.get(i).mDrawable;
200            if (dr != null) {
201                return dr;
202            }
203        }
204        return null;
205    }
206
207    @Override
208    public void invalidateDrawable(Drawable who) {
209        invalidateSelf();
210    }
211
212    @Override
213    public void scheduleDrawable(Drawable who, Runnable what, long when) {
214        scheduleSelf(what, when);
215    }
216
217    @Override
218    public void unscheduleDrawable(Drawable who, Runnable what) {
219        unscheduleSelf(what);
220    }
221
222    /**
223     * Updates the bounds based on the {@link BoundsRule}.
224     */
225    void updateBounds(Rect bounds) {
226        final ArrayList<ChildDrawable> children = mState.mChildren;
227        for (int i = 0; i < children.size(); i++) {
228            ChildDrawable childDrawable = children.get(i);
229            childDrawable.updateBounds(bounds);
230        }
231    }
232
233    /**
234     * Wrapper class holding a drawable object and {@link BoundsRule} to update drawable bounds
235     * when parent bound changes.
236     */
237    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
238    public static final class ChildDrawable {
239        private final BoundsRule boundsRule = new BoundsRule();
240        private final Drawable mDrawable;
241        private final Rect adjustedBounds = new Rect();
242        private final CompositeDrawable mParent;
243
244        public ChildDrawable(Drawable drawable, CompositeDrawable parent) {
245            this.mDrawable = drawable;
246            this.mParent = parent;
247            drawable.setCallback(parent);
248        }
249
250        ChildDrawable(ChildDrawable orig, CompositeDrawable parent, Resources res) {
251            final Drawable dr = orig.mDrawable;
252            final Drawable clone;
253            if (dr != null) {
254                final ConstantState cs = dr.getConstantState();
255                if (res != null) {
256                    clone = cs.newDrawable(res);
257                } else {
258                    clone = cs.newDrawable();
259                }
260                clone.setCallback(parent);
261                DrawableCompat.setLayoutDirection(clone, DrawableCompat.getLayoutDirection(dr));
262                clone.setBounds(dr.getBounds());
263                clone.setLevel(dr.getLevel());
264            } else {
265                clone = null;
266            }
267            mDrawable = clone;
268            mParent = parent;
269        }
270
271        /**
272         * Returns the instance of {@link BoundsRule}.
273         */
274        public BoundsRule getBoundsRule() {
275            return this.boundsRule;
276        }
277
278        /**
279         * Returns the {@link Drawable}.
280         */
281        public Drawable getDrawable() {
282            return mDrawable;
283        }
284
285        /**
286         * Updates the bounds based on the {@link BoundsRule}.
287         */
288        void updateBounds(Rect bounds) {
289            boundsRule.calculateBounds(bounds, adjustedBounds);
290            mDrawable.setBounds(adjustedBounds);
291        }
292
293        /**
294         * After changing the {@link BoundsRule}, user should call this function
295         * for the drawable to recalculate its bounds.
296         */
297        public void recomputeBounds() {
298            updateBounds(mParent.getBounds());
299        }
300
301        /**
302         * Implementation of {@link Property} for overrideTop attribute.
303         */
304        public static final Property<CompositeDrawable.ChildDrawable, Integer> TOP_ABSOLUTE
305                = new Property<CompositeDrawable.ChildDrawable, Integer>(Integer.class, "absoluteTop") {
306
307            @Override
308            public void set(CompositeDrawable.ChildDrawable obj, Integer value) {
309                if (obj.getBoundsRule().mTop == null) {
310                    obj.getBoundsRule().mTop = BoundsRule.absoluteValue(value);
311                } else {
312                    obj.getBoundsRule().mTop.setAbsoluteValue(value);
313                }
314
315                obj.recomputeBounds();
316            }
317
318            @Override
319            public Integer get(CompositeDrawable.ChildDrawable obj) {
320                return obj.getBoundsRule().mTop.getAbsoluteValue();
321            }
322        };
323
324
325        /**
326         * Implementation of {@link Property} for overrideBottom attribute.
327         */
328        public static final Property<CompositeDrawable.ChildDrawable, Integer> BOTTOM_ABSOLUTE
329                = new Property<CompositeDrawable.ChildDrawable, Integer>(
330                Integer.class, "absoluteBottom") {
331
332            @Override
333            public void set(CompositeDrawable.ChildDrawable obj, Integer value) {
334                if (obj.getBoundsRule().mBottom == null) {
335                    obj.getBoundsRule().mBottom = BoundsRule.absoluteValue(value);
336                } else {
337                    obj.getBoundsRule().mBottom.setAbsoluteValue(value);
338                }
339
340                obj.recomputeBounds();
341            }
342
343            @Override
344            public Integer get(CompositeDrawable.ChildDrawable obj) {
345                return obj.getBoundsRule().mBottom.getAbsoluteValue();
346            }
347        };
348
349
350        /**
351         * Implementation of {@link Property} for overrideLeft attribute.
352         */
353        public static final Property<CompositeDrawable.ChildDrawable, Integer> LEFT_ABSOLUTE
354                = new Property<CompositeDrawable.ChildDrawable, Integer>(
355                Integer.class, "absoluteLeft") {
356
357            @Override
358            public void set(CompositeDrawable.ChildDrawable obj, Integer value) {
359                if (obj.getBoundsRule().mLeft == null) {
360                    obj.getBoundsRule().mLeft = BoundsRule.absoluteValue(value);
361                } else {
362                    obj.getBoundsRule().mLeft.setAbsoluteValue(value);
363                }
364
365                obj.recomputeBounds();
366            }
367
368            @Override
369            public Integer get(CompositeDrawable.ChildDrawable obj) {
370                return obj.getBoundsRule().mLeft.getAbsoluteValue();
371            }
372        };
373
374        /**
375         * Implementation of {@link Property} for overrideRight attribute.
376         */
377        public static final Property<CompositeDrawable.ChildDrawable, Integer> RIGHT_ABSOLUTE
378                = new Property<CompositeDrawable.ChildDrawable, Integer>(
379                Integer.class, "absoluteRight") {
380
381            @Override
382            public void set(CompositeDrawable.ChildDrawable obj, Integer value) {
383                if (obj.getBoundsRule().mRight == null) {
384                    obj.getBoundsRule().mRight = BoundsRule.absoluteValue(value);
385                } else {
386                    obj.getBoundsRule().mRight.setAbsoluteValue(value);
387                }
388
389                obj.recomputeBounds();
390            }
391
392            @Override
393            public Integer get(CompositeDrawable.ChildDrawable obj) {
394                return obj.getBoundsRule().mRight.getAbsoluteValue();
395            }
396        };
397
398        /**
399         * Implementation of {@link Property} for overwriting the bottom attribute of
400         * {@link BoundsRule} associated with this {@link ChildDrawable}. This allows users to
401         * change the bounds rules as a percentage of parent size. This is preferable over
402         * {@see PROPERTY_TOP_ABSOLUTE} when the exact start/end position of scroll movement
403         * isn't available at compile time.
404         */
405        public static final Property<CompositeDrawable.ChildDrawable, Float> TOP_FRACTION
406                = new Property<CompositeDrawable.ChildDrawable, Float>(Float.class, "fractionTop") {
407
408            @Override
409            public void set(CompositeDrawable.ChildDrawable obj, Float value) {
410                if (obj.getBoundsRule().mTop == null) {
411                    obj.getBoundsRule().mTop = BoundsRule.inheritFromParent(value);
412                } else {
413                    obj.getBoundsRule().mTop.setFraction(value);
414                }
415
416                obj.recomputeBounds();
417            }
418
419            @Override
420            public Float get(CompositeDrawable.ChildDrawable obj) {
421                return obj.getBoundsRule().mTop.getFraction();
422            }
423        };
424
425        /**
426         * Implementation of {@link Property} for overwriting the bottom attribute of
427         * {@link BoundsRule} associated with this {@link ChildDrawable}. This allows users to
428         * change the bounds rules as a percentage of parent size. This is preferable over
429         * {@see PROPERTY_BOTTOM_ABSOLUTE} when the exact start/end position of scroll movement
430         * isn't available at compile time.
431         */
432        public static final Property<CompositeDrawable.ChildDrawable, Float> BOTTOM_FRACTION
433                = new Property<CompositeDrawable.ChildDrawable, Float>(
434                Float.class, "fractionBottom") {
435
436            @Override
437            public void set(CompositeDrawable.ChildDrawable obj, Float value) {
438                if (obj.getBoundsRule().mBottom == null) {
439                    obj.getBoundsRule().mBottom = BoundsRule.inheritFromParent(value);
440                } else {
441                    obj.getBoundsRule().mBottom.setFraction(value);
442                }
443
444                obj.recomputeBounds();
445            }
446
447            @Override
448            public Float get(CompositeDrawable.ChildDrawable obj) {
449                return obj.getBoundsRule().mBottom.getFraction();
450            }
451        };
452
453        /**
454         * Implementation of {@link Property} for overwriting the bottom attribute of
455         * {@link BoundsRule} associated with this {@link ChildDrawable}. This allows users to
456         * change the bounds rules as a percentage of parent size. This is preferable over
457         * {@see PROPERTY_LEFT_ABSOLUTE} when the exact start/end position of scroll movement
458         * isn't available at compile time.
459         */
460        public static final Property<CompositeDrawable.ChildDrawable, Float> LEFT_FRACTION
461                = new Property<CompositeDrawable.ChildDrawable, Float>(Float.class, "fractionLeft") {
462
463            @Override
464            public void set(CompositeDrawable.ChildDrawable obj, Float value) {
465                if (obj.getBoundsRule().mLeft == null) {
466                    obj.getBoundsRule().mLeft = BoundsRule.inheritFromParent(value);
467                } else {
468                    obj.getBoundsRule().mLeft.setFraction(value);
469                }
470
471                obj.recomputeBounds();
472            }
473
474            @Override
475            public Float get(CompositeDrawable.ChildDrawable obj) {
476                return obj.getBoundsRule().mLeft.getFraction();
477            }
478        };
479
480        /**
481         * Implementation of {@link Property} for overwriting the bottom attribute of
482         * {@link BoundsRule} associated with this {@link ChildDrawable}. This allows users to
483         * change the bounds rules as a percentage of parent size. This is preferable over
484         * {@see PROPERTY_RIGHT_ABSOLUTE} when the exact start/end position of scroll movement
485         * isn't available at compile time.
486         */
487        public static final Property<CompositeDrawable.ChildDrawable, Float> RIGHT_FRACTION
488                = new Property<CompositeDrawable.ChildDrawable, Float>(
489                Float.class, "fractoinRight") {
490
491            @Override
492            public void set(CompositeDrawable.ChildDrawable obj, Float value) {
493                if (obj.getBoundsRule().mRight == null) {
494                    obj.getBoundsRule().mRight = BoundsRule.inheritFromParent(value);
495                } else {
496                    obj.getBoundsRule().mRight.setFraction(value);
497                }
498
499                obj.recomputeBounds();
500            }
501
502            @Override
503            public Float get(CompositeDrawable.ChildDrawable obj) {
504                return obj.getBoundsRule().mRight.getFraction();
505            }
506        };
507    }
508}
509