CompositeDrawable.java revision 738deb3f9c75ea32dff1bf335753703e40e87f39
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        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>(
306                        Integer.class, "absoluteTop") {
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                if (obj.getBoundsRule().mTop == null) {
321                    return obj.mParent.getBounds().top;
322                }
323                return obj.getBoundsRule().mTop.getAbsoluteValue();
324            }
325        };
326
327        /**
328         * Implementation of {@link Property} for overrideBottom attribute.
329         */
330        public static final Property<CompositeDrawable.ChildDrawable, Integer> BOTTOM_ABSOLUTE =
331                new Property<CompositeDrawable.ChildDrawable, Integer>(
332                        Integer.class, "absoluteBottom") {
333            @Override
334            public void set(CompositeDrawable.ChildDrawable obj, Integer value) {
335                if (obj.getBoundsRule().mBottom == null) {
336                    obj.getBoundsRule().mBottom = BoundsRule.absoluteValue(value);
337                } else {
338                    obj.getBoundsRule().mBottom.setAbsoluteValue(value);
339                }
340
341                obj.recomputeBounds();
342            }
343
344            @Override
345            public Integer get(CompositeDrawable.ChildDrawable obj) {
346                if (obj.getBoundsRule().mBottom == null) {
347                    return obj.mParent.getBounds().bottom;
348                }
349                return obj.getBoundsRule().mBottom.getAbsoluteValue();
350            }
351        };
352
353
354        /**
355         * Implementation of {@link Property} for overrideLeft attribute.
356         */
357        public static final Property<CompositeDrawable.ChildDrawable, Integer> LEFT_ABSOLUTE =
358                new Property<CompositeDrawable.ChildDrawable, Integer>(
359                        Integer.class, "absoluteLeft") {
360            @Override
361            public void set(CompositeDrawable.ChildDrawable obj, Integer value) {
362                if (obj.getBoundsRule().mLeft == null) {
363                    obj.getBoundsRule().mLeft = BoundsRule.absoluteValue(value);
364                } else {
365                    obj.getBoundsRule().mLeft.setAbsoluteValue(value);
366                }
367
368                obj.recomputeBounds();
369            }
370
371            @Override
372            public Integer get(CompositeDrawable.ChildDrawable obj) {
373                if (obj.getBoundsRule().mLeft == null) {
374                    return obj.mParent.getBounds().left;
375                }
376                return obj.getBoundsRule().mLeft.getAbsoluteValue();
377            }
378        };
379
380        /**
381         * Implementation of {@link Property} for overrideRight attribute.
382         */
383        public static final Property<CompositeDrawable.ChildDrawable, Integer> RIGHT_ABSOLUTE =
384                new Property<CompositeDrawable.ChildDrawable, Integer>(
385                        Integer.class, "absoluteRight") {
386            @Override
387            public void set(CompositeDrawable.ChildDrawable obj, Integer value) {
388                if (obj.getBoundsRule().mRight == null) {
389                    obj.getBoundsRule().mRight = BoundsRule.absoluteValue(value);
390                } else {
391                    obj.getBoundsRule().mRight.setAbsoluteValue(value);
392                }
393
394                obj.recomputeBounds();
395            }
396
397            @Override
398            public Integer get(CompositeDrawable.ChildDrawable obj) {
399                if (obj.getBoundsRule().mRight == null) {
400                    return obj.mParent.getBounds().right;
401                }
402                return obj.getBoundsRule().mRight.getAbsoluteValue();
403            }
404        };
405
406        /**
407         * Implementation of {@link Property} for overwriting the bottom attribute of
408         * {@link BoundsRule} associated with this {@link ChildDrawable}. This allows users to
409         * change the bounds rules as a percentage of parent size. This is preferable over
410         * {@see PROPERTY_TOP_ABSOLUTE} when the exact start/end position of scroll movement
411         * isn't available at compile time.
412         */
413        public static final Property<CompositeDrawable.ChildDrawable, Float> TOP_FRACTION =
414                new Property<CompositeDrawable.ChildDrawable, Float>(Float.class, "fractionTop") {
415            @Override
416            public void set(CompositeDrawable.ChildDrawable obj, Float value) {
417                if (obj.getBoundsRule().mTop == null) {
418                    obj.getBoundsRule().mTop = BoundsRule.inheritFromParent(value);
419                } else {
420                    obj.getBoundsRule().mTop.setFraction(value);
421                }
422
423                obj.recomputeBounds();
424            }
425
426            @Override
427            public Float get(CompositeDrawable.ChildDrawable obj) {
428                if (obj.getBoundsRule().mTop == null) {
429                    return 0f;
430                }
431                return obj.getBoundsRule().mTop.getFraction();
432            }
433        };
434
435        /**
436         * Implementation of {@link Property} for overwriting the bottom attribute of
437         * {@link BoundsRule} associated with this {@link ChildDrawable}. This allows users to
438         * change the bounds rules as a percentage of parent size. This is preferable over
439         * {@see PROPERTY_BOTTOM_ABSOLUTE} when the exact start/end position of scroll movement
440         * isn't available at compile time.
441         */
442        public static final Property<CompositeDrawable.ChildDrawable, Float> BOTTOM_FRACTION =
443                new Property<CompositeDrawable.ChildDrawable, Float>(
444                        Float.class, "fractionBottom") {
445            @Override
446            public void set(CompositeDrawable.ChildDrawable obj, Float value) {
447                if (obj.getBoundsRule().mBottom == null) {
448                    obj.getBoundsRule().mBottom = BoundsRule.inheritFromParent(value);
449                } else {
450                    obj.getBoundsRule().mBottom.setFraction(value);
451                }
452
453                obj.recomputeBounds();
454            }
455
456            @Override
457            public Float get(CompositeDrawable.ChildDrawable obj) {
458                if (obj.getBoundsRule().mBottom == null) {
459                    return 1f;
460                }
461                return obj.getBoundsRule().mBottom.getFraction();
462            }
463        };
464
465        /**
466         * Implementation of {@link Property} for overwriting the bottom attribute of
467         * {@link BoundsRule} associated with this {@link ChildDrawable}. This allows users to
468         * change the bounds rules as a percentage of parent size. This is preferable over
469         * {@see PROPERTY_LEFT_ABSOLUTE} when the exact start/end position of scroll movement
470         * isn't available at compile time.
471         */
472        public static final Property<CompositeDrawable.ChildDrawable, Float> LEFT_FRACTION =
473                new Property<CompositeDrawable.ChildDrawable, Float>(Float.class, "fractionLeft") {
474            @Override
475            public void set(CompositeDrawable.ChildDrawable obj, Float value) {
476                if (obj.getBoundsRule().mLeft == null) {
477                    obj.getBoundsRule().mLeft = BoundsRule.inheritFromParent(value);
478                } else {
479                    obj.getBoundsRule().mLeft.setFraction(value);
480                }
481
482                obj.recomputeBounds();
483            }
484
485            @Override
486            public Float get(CompositeDrawable.ChildDrawable obj) {
487                if (obj.getBoundsRule().mLeft == null) {
488                    return 0f;
489                }
490                return obj.getBoundsRule().mLeft.getFraction();
491            }
492        };
493
494        /**
495         * Implementation of {@link Property} for overwriting the bottom attribute of
496         * {@link BoundsRule} associated with this {@link ChildDrawable}. This allows users to
497         * change the bounds rules as a percentage of parent size. This is preferable over
498         * {@see PROPERTY_RIGHT_ABSOLUTE} when the exact start/end position of scroll movement
499         * isn't available at compile time.
500         */
501        public static final Property<CompositeDrawable.ChildDrawable, Float> RIGHT_FRACTION =
502                new Property<CompositeDrawable.ChildDrawable, Float>(Float.class, "fractoinRight") {
503            @Override
504            public void set(CompositeDrawable.ChildDrawable obj, Float value) {
505                if (obj.getBoundsRule().mRight == null) {
506                    obj.getBoundsRule().mRight = BoundsRule.inheritFromParent(value);
507                } else {
508                    obj.getBoundsRule().mRight.setFraction(value);
509                }
510
511                obj.recomputeBounds();
512            }
513
514            @Override
515            public Float get(CompositeDrawable.ChildDrawable obj) {
516                if (obj.getBoundsRule().mRight == null) {
517                    return 1f;
518                }
519                return obj.getBoundsRule().mRight.getFraction();
520            }
521        };
522    }
523}
524