Home | History | Annotate | Download | only in drawable
      1 /*
      2  * Copyright (C) 2016 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 
     17 package com.android.internal.graphics.drawable;
     18 
     19 import android.animation.ValueAnimator;
     20 import android.annotation.NonNull;
     21 import android.annotation.Nullable;
     22 import android.content.res.Resources;
     23 import android.content.res.Resources.Theme;
     24 import android.content.res.TypedArray;
     25 import android.graphics.drawable.Animatable;
     26 import android.graphics.drawable.Drawable;
     27 import android.graphics.drawable.DrawableContainer;
     28 import android.util.AttributeSet;
     29 
     30 import com.android.internal.R;
     31 
     32 import org.xmlpull.v1.XmlPullParser;
     33 import org.xmlpull.v1.XmlPullParserException;
     34 
     35 import java.io.IOException;
     36 
     37 /**
     38  * An internal DrawableContainer class, used to draw different things depending on animation scale.
     39  * i.e: animation scale can be 0 in battery saver mode.
     40  * This class contains 2 drawable, one is animatable, the other is static. When animation scale is
     41  * not 0, the animatable drawable will the drawn. Otherwise, the static drawable will be drawn.
     42  * <p>This class implements Animatable since ProgressBar can pick this up similarly as an
     43  * AnimatedVectorDrawable.
     44  * <p>It can be defined in an XML file with the {@code <AnimationScaleListDrawable>}
     45  * element.
     46  */
     47 public class AnimationScaleListDrawable extends DrawableContainer implements Animatable {
     48     private static final String TAG = "AnimationScaleListDrawable";
     49     private AnimationScaleListState mAnimationScaleListState;
     50     private boolean mMutated;
     51 
     52     public AnimationScaleListDrawable() {
     53         this(null, null);
     54     }
     55 
     56     private AnimationScaleListDrawable(@Nullable AnimationScaleListState state,
     57             @Nullable Resources res) {
     58         // Every scale list drawable has its own constant state.
     59         final AnimationScaleListState newState = new AnimationScaleListState(state, this, res);
     60         setConstantState(newState);
     61         onStateChange(getState());
     62     }
     63 
     64     /**
     65      * Set the current drawable according to the animation scale. If scale is 0, then pick the
     66      * static drawable, otherwise, pick the animatable drawable.
     67      */
     68     @Override
     69     protected boolean onStateChange(int[] stateSet) {
     70         final boolean changed = super.onStateChange(stateSet);
     71         int idx = mAnimationScaleListState.getCurrentDrawableIndexBasedOnScale();
     72         return selectDrawable(idx) || changed;
     73     }
     74 
     75 
     76     @Override
     77     public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
     78             @NonNull AttributeSet attrs, @Nullable Theme theme)
     79             throws XmlPullParserException, IOException {
     80         final TypedArray a = obtainAttributes(r, theme, attrs,
     81                 R.styleable.AnimationScaleListDrawable);
     82         updateDensity(r);
     83         a.recycle();
     84 
     85         inflateChildElements(r, parser, attrs, theme);
     86 
     87         onStateChange(getState());
     88     }
     89 
     90     /**
     91      * Inflates child elements from XML.
     92      */
     93     private void inflateChildElements(@NonNull Resources r, @NonNull XmlPullParser parser,
     94             @NonNull AttributeSet attrs, @Nullable Theme theme)
     95             throws XmlPullParserException, IOException {
     96         final AnimationScaleListState state = mAnimationScaleListState;
     97         final int innerDepth = parser.getDepth() + 1;
     98         int type;
     99         int depth;
    100         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    101                 && ((depth = parser.getDepth()) >= innerDepth
    102                 || type != XmlPullParser.END_TAG)) {
    103             if (type != XmlPullParser.START_TAG) {
    104                 continue;
    105             }
    106 
    107             if (depth > innerDepth || !parser.getName().equals("item")) {
    108                 continue;
    109             }
    110 
    111             // Either pick up the android:drawable attribute.
    112             final TypedArray a = obtainAttributes(r, theme, attrs,
    113                     R.styleable.AnimationScaleListDrawableItem);
    114             Drawable dr = a.getDrawable(R.styleable.AnimationScaleListDrawableItem_drawable);
    115             a.recycle();
    116 
    117             // Or parse the child element under <item>.
    118             if (dr == null) {
    119                 while ((type = parser.next()) == XmlPullParser.TEXT) {
    120                 }
    121                 if (type != XmlPullParser.START_TAG) {
    122                     throw new XmlPullParserException(
    123                             parser.getPositionDescription()
    124                                     + ": <item> tag requires a 'drawable' attribute or "
    125                                     + "child tag defining a drawable");
    126                 }
    127                 dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
    128             }
    129 
    130             state.addDrawable(dr);
    131         }
    132     }
    133 
    134     @Override
    135     public Drawable mutate() {
    136         if (!mMutated && super.mutate() == this) {
    137             mAnimationScaleListState.mutate();
    138             mMutated = true;
    139         }
    140         return this;
    141     }
    142 
    143     @Override
    144     public void clearMutated() {
    145         super.clearMutated();
    146         mMutated = false;
    147     }
    148 
    149     @Override
    150     public void start() {
    151         Drawable dr = getCurrent();
    152         if (dr != null && dr instanceof Animatable) {
    153             ((Animatable) dr).start();
    154         }
    155     }
    156 
    157     @Override
    158     public void stop() {
    159         Drawable dr = getCurrent();
    160         if (dr != null && dr instanceof Animatable) {
    161             ((Animatable) dr).stop();
    162         }
    163     }
    164 
    165     @Override
    166     public boolean isRunning() {
    167         boolean result = false;
    168         Drawable dr = getCurrent();
    169         if (dr != null && dr instanceof Animatable) {
    170             result = ((Animatable) dr).isRunning();
    171         }
    172         return result;
    173     }
    174 
    175     static class AnimationScaleListState extends DrawableContainerState {
    176         int[] mThemeAttrs = null;
    177         // The index of the last static drawable.
    178         int mStaticDrawableIndex = -1;
    179         // The index of the last animatable drawable.
    180         int mAnimatableDrawableIndex = -1;
    181 
    182         AnimationScaleListState(AnimationScaleListState orig, AnimationScaleListDrawable owner,
    183                 Resources res) {
    184             super(orig, owner, res);
    185 
    186             if (orig != null) {
    187                 // Perform a shallow copy and rely on mutate() to deep-copy.
    188                 mThemeAttrs = orig.mThemeAttrs;
    189 
    190                 mStaticDrawableIndex = orig.mStaticDrawableIndex;
    191                 mAnimatableDrawableIndex = orig.mAnimatableDrawableIndex;
    192             }
    193 
    194         }
    195 
    196         void mutate() {
    197             mThemeAttrs = mThemeAttrs != null ? mThemeAttrs.clone() : null;
    198         }
    199 
    200         /**
    201          * Add the drawable into the container.
    202          * This class only keep track one animatable drawable, and one static. If there are multiple
    203          * defined in the XML, then pick the last one.
    204          */
    205         int addDrawable(Drawable drawable) {
    206             final int pos = addChild(drawable);
    207             if (drawable instanceof Animatable) {
    208                 mAnimatableDrawableIndex = pos;
    209             } else {
    210                 mStaticDrawableIndex = pos;
    211             }
    212             return pos;
    213         }
    214 
    215         @Override
    216         public Drawable newDrawable() {
    217             return new AnimationScaleListDrawable(this, null);
    218         }
    219 
    220         @Override
    221         public Drawable newDrawable(Resources res) {
    222             return new AnimationScaleListDrawable(this, res);
    223         }
    224 
    225         @Override
    226         public boolean canApplyTheme() {
    227             return mThemeAttrs != null || super.canApplyTheme();
    228         }
    229 
    230         public int getCurrentDrawableIndexBasedOnScale() {
    231             if (ValueAnimator.getDurationScale() == 0) {
    232                 return mStaticDrawableIndex;
    233             }
    234             return mAnimatableDrawableIndex;
    235         }
    236     }
    237 
    238     @Override
    239     public void applyTheme(@NonNull Theme theme) {
    240         super.applyTheme(theme);
    241 
    242         onStateChange(getState());
    243     }
    244 
    245     @Override
    246     protected void setConstantState(@NonNull DrawableContainerState state) {
    247         super.setConstantState(state);
    248 
    249         if (state instanceof AnimationScaleListState) {
    250             mAnimationScaleListState = (AnimationScaleListState) state;
    251         }
    252     }
    253 }
    254 
    255