| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.internal.graphics.drawable; |
| |
| import android.animation.ValueAnimator; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.content.res.Resources; |
| import android.content.res.Resources.Theme; |
| import android.content.res.TypedArray; |
| import android.graphics.drawable.Animatable; |
| import android.graphics.drawable.Drawable; |
| import android.graphics.drawable.DrawableContainer; |
| import android.util.AttributeSet; |
| |
| import com.android.internal.R; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import java.io.IOException; |
| |
| /** |
| * An internal DrawableContainer class, used to draw different things depending on animation scale. |
| * i.e: animation scale can be 0 in battery saver mode. |
| * This class contains 2 drawable, one is animatable, the other is static. When animation scale is |
| * not 0, the animatable drawable will the drawn. Otherwise, the static drawable will be drawn. |
| * <p>This class implements Animatable since ProgressBar can pick this up similarly as an |
| * AnimatedVectorDrawable. |
| * <p>It can be defined in an XML file with the {@code <AnimationScaleListDrawable>} |
| * element. |
| */ |
| public class AnimationScaleListDrawable extends DrawableContainer implements Animatable { |
| private static final String TAG = "AnimationScaleListDrawable"; |
| private AnimationScaleListState mAnimationScaleListState; |
| private boolean mMutated; |
| |
| public AnimationScaleListDrawable() { |
| this(null, null); |
| } |
| |
| private AnimationScaleListDrawable(@Nullable AnimationScaleListState state, |
| @Nullable Resources res) { |
| // Every scale list drawable has its own constant state. |
| final AnimationScaleListState newState = new AnimationScaleListState(state, this, res); |
| setConstantState(newState); |
| onStateChange(getState()); |
| } |
| |
| /** |
| * Set the current drawable according to the animation scale. If scale is 0, then pick the |
| * static drawable, otherwise, pick the animatable drawable. |
| */ |
| @Override |
| protected boolean onStateChange(int[] stateSet) { |
| final boolean changed = super.onStateChange(stateSet); |
| int idx = mAnimationScaleListState.getCurrentDrawableIndexBasedOnScale(); |
| return selectDrawable(idx) || changed; |
| } |
| |
| |
| @Override |
| public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, |
| @NonNull AttributeSet attrs, @Nullable Theme theme) |
| throws XmlPullParserException, IOException { |
| final TypedArray a = obtainAttributes(r, theme, attrs, |
| R.styleable.AnimationScaleListDrawable); |
| updateDensity(r); |
| a.recycle(); |
| |
| inflateChildElements(r, parser, attrs, theme); |
| |
| onStateChange(getState()); |
| } |
| |
| /** |
| * Inflates child elements from XML. |
| */ |
| private void inflateChildElements(@NonNull Resources r, @NonNull XmlPullParser parser, |
| @NonNull AttributeSet attrs, @Nullable Theme theme) |
| throws XmlPullParserException, IOException { |
| final AnimationScaleListState state = mAnimationScaleListState; |
| final int innerDepth = parser.getDepth() + 1; |
| int type; |
| int depth; |
| while ((type = parser.next()) != XmlPullParser.END_DOCUMENT |
| && ((depth = parser.getDepth()) >= innerDepth |
| || type != XmlPullParser.END_TAG)) { |
| if (type != XmlPullParser.START_TAG) { |
| continue; |
| } |
| |
| if (depth > innerDepth || !parser.getName().equals("item")) { |
| continue; |
| } |
| |
| // Either pick up the android:drawable attribute. |
| final TypedArray a = obtainAttributes(r, theme, attrs, |
| R.styleable.AnimationScaleListDrawableItem); |
| Drawable dr = a.getDrawable(R.styleable.AnimationScaleListDrawableItem_drawable); |
| a.recycle(); |
| |
| // Or parse the child element under <item>. |
| if (dr == null) { |
| while ((type = parser.next()) == XmlPullParser.TEXT) { |
| } |
| if (type != XmlPullParser.START_TAG) { |
| throw new XmlPullParserException( |
| parser.getPositionDescription() |
| + ": <item> tag requires a 'drawable' attribute or " |
| + "child tag defining a drawable"); |
| } |
| dr = Drawable.createFromXmlInner(r, parser, attrs, theme); |
| } |
| |
| state.addDrawable(dr); |
| } |
| } |
| |
| @Override |
| public Drawable mutate() { |
| if (!mMutated && super.mutate() == this) { |
| mAnimationScaleListState.mutate(); |
| mMutated = true; |
| } |
| return this; |
| } |
| |
| @Override |
| public void clearMutated() { |
| super.clearMutated(); |
| mMutated = false; |
| } |
| |
| @Override |
| public void start() { |
| Drawable dr = getCurrent(); |
| if (dr != null && dr instanceof Animatable) { |
| ((Animatable) dr).start(); |
| } |
| } |
| |
| @Override |
| public void stop() { |
| Drawable dr = getCurrent(); |
| if (dr != null && dr instanceof Animatable) { |
| ((Animatable) dr).stop(); |
| } |
| } |
| |
| @Override |
| public boolean isRunning() { |
| boolean result = false; |
| Drawable dr = getCurrent(); |
| if (dr != null && dr instanceof Animatable) { |
| result = ((Animatable) dr).isRunning(); |
| } |
| return result; |
| } |
| |
| static class AnimationScaleListState extends DrawableContainerState { |
| int[] mThemeAttrs = null; |
| // The index of the last static drawable. |
| int mStaticDrawableIndex = -1; |
| // The index of the last animatable drawable. |
| int mAnimatableDrawableIndex = -1; |
| |
| AnimationScaleListState(AnimationScaleListState orig, AnimationScaleListDrawable owner, |
| Resources res) { |
| super(orig, owner, res); |
| |
| if (orig != null) { |
| // Perform a shallow copy and rely on mutate() to deep-copy. |
| mThemeAttrs = orig.mThemeAttrs; |
| |
| mStaticDrawableIndex = orig.mStaticDrawableIndex; |
| mAnimatableDrawableIndex = orig.mAnimatableDrawableIndex; |
| } |
| |
| } |
| |
| void mutate() { |
| mThemeAttrs = mThemeAttrs != null ? mThemeAttrs.clone() : null; |
| } |
| |
| /** |
| * Add the drawable into the container. |
| * This class only keep track one animatable drawable, and one static. If there are multiple |
| * defined in the XML, then pick the last one. |
| */ |
| int addDrawable(Drawable drawable) { |
| final int pos = addChild(drawable); |
| if (drawable instanceof Animatable) { |
| mAnimatableDrawableIndex = pos; |
| } else { |
| mStaticDrawableIndex = pos; |
| } |
| return pos; |
| } |
| |
| @Override |
| public Drawable newDrawable() { |
| return new AnimationScaleListDrawable(this, null); |
| } |
| |
| @Override |
| public Drawable newDrawable(Resources res) { |
| return new AnimationScaleListDrawable(this, res); |
| } |
| |
| @Override |
| public boolean canApplyTheme() { |
| return mThemeAttrs != null || super.canApplyTheme(); |
| } |
| |
| public int getCurrentDrawableIndexBasedOnScale() { |
| if (ValueAnimator.getDurationScale() == 0) { |
| return mStaticDrawableIndex; |
| } |
| return mAnimatableDrawableIndex; |
| } |
| } |
| |
| @Override |
| public void applyTheme(@NonNull Theme theme) { |
| super.applyTheme(theme); |
| |
| onStateChange(getState()); |
| } |
| |
| @Override |
| protected void setConstantState(@NonNull DrawableContainerState state) { |
| super.setConstantState(state); |
| |
| if (state instanceof AnimationScaleListState) { |
| mAnimationScaleListState = (AnimationScaleListState) state; |
| } |
| } |
| } |
| |