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