blob: 62f18eac4aa4be83add35620b3ca7e0e05f0ea7c [file] [log] [blame]
/*
* 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;
}
}
}