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 AnimationScaleListDrawable()52 public AnimationScaleListDrawable() { 53 this(null, null); 54 } 55 AnimationScaleListDrawable(@ullable AnimationScaleListState state, @Nullable Resources res)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 onStateChange(int[] stateSet)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 inflate(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)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 */ inflateChildElements(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)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 mutate()135 public Drawable mutate() { 136 if (!mMutated && super.mutate() == this) { 137 mAnimationScaleListState.mutate(); 138 mMutated = true; 139 } 140 return this; 141 } 142 143 @Override clearMutated()144 public void clearMutated() { 145 super.clearMutated(); 146 mMutated = false; 147 } 148 149 @Override start()150 public void start() { 151 Drawable dr = getCurrent(); 152 if (dr != null && dr instanceof Animatable) { 153 ((Animatable) dr).start(); 154 } 155 } 156 157 @Override stop()158 public void stop() { 159 Drawable dr = getCurrent(); 160 if (dr != null && dr instanceof Animatable) { 161 ((Animatable) dr).stop(); 162 } 163 } 164 165 @Override isRunning()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 AnimationScaleListState(AnimationScaleListState orig, AnimationScaleListDrawable owner, Resources res)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 mutate()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 */ addDrawable(Drawable drawable)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 newDrawable()216 public Drawable newDrawable() { 217 return new AnimationScaleListDrawable(this, null); 218 } 219 220 @Override newDrawable(Resources res)221 public Drawable newDrawable(Resources res) { 222 return new AnimationScaleListDrawable(this, res); 223 } 224 225 @Override canApplyTheme()226 public boolean canApplyTheme() { 227 return mThemeAttrs != null || super.canApplyTheme(); 228 } 229 getCurrentDrawableIndexBasedOnScale()230 public int getCurrentDrawableIndexBasedOnScale() { 231 if (ValueAnimator.getDurationScale() == 0) { 232 return mStaticDrawableIndex; 233 } 234 return mAnimatableDrawableIndex; 235 } 236 } 237 238 @Override applyTheme(@onNull Theme theme)239 public void applyTheme(@NonNull Theme theme) { 240 super.applyTheme(theme); 241 242 onStateChange(getState()); 243 } 244 245 @Override setConstantState(@onNull DrawableContainerState state)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