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