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