/*
 * 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.

This class implements Animatable since ProgressBar can pick this up similarly as an AnimatedVectorDrawable.

It can be defined in an XML file with the <AnimationScaleListDrawable> element.

/** * 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.
/** * 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.
/** * 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.
/** * 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; } } }