am 91c091cf: Merge "State based animators for Views"
* commit '91c091cf7b732796d26971de6a508966e24d40a0': State based animators for Views
This commit is contained in:
@@ -21,6 +21,7 @@ import android.content.res.TypedArray;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.content.res.Resources.NotFoundException;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.StateSet;
|
||||
import android.util.TypedValue;
|
||||
import android.util.Xml;
|
||||
import android.view.animation.AnimationUtils;
|
||||
@@ -87,9 +88,86 @@ public class AnimatorInflater {
|
||||
}
|
||||
}
|
||||
|
||||
public static StateListAnimator loadStateListAnimator(Context context, int id)
|
||||
throws NotFoundException {
|
||||
XmlResourceParser parser = null;
|
||||
try {
|
||||
parser = context.getResources().getAnimation(id);
|
||||
return createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser));
|
||||
} catch (XmlPullParserException ex) {
|
||||
Resources.NotFoundException rnf =
|
||||
new Resources.NotFoundException(
|
||||
"Can't load state list animator resource ID #0x" +
|
||||
Integer.toHexString(id)
|
||||
);
|
||||
rnf.initCause(ex);
|
||||
throw rnf;
|
||||
} catch (IOException ex) {
|
||||
Resources.NotFoundException rnf =
|
||||
new Resources.NotFoundException(
|
||||
"Can't load state list animator resource ID #0x" +
|
||||
Integer.toHexString(id)
|
||||
);
|
||||
rnf.initCause(ex);
|
||||
throw rnf;
|
||||
} finally {
|
||||
if (parser != null) {
|
||||
parser.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static StateListAnimator createStateListAnimatorFromXml(Context context,
|
||||
XmlPullParser parser, AttributeSet attributeSet)
|
||||
throws IOException, XmlPullParserException {
|
||||
int type;
|
||||
StateListAnimator stateListAnimator = new StateListAnimator();
|
||||
|
||||
while (true) {
|
||||
type = parser.next();
|
||||
switch (type) {
|
||||
case XmlPullParser.END_DOCUMENT:
|
||||
case XmlPullParser.END_TAG:
|
||||
return stateListAnimator;
|
||||
|
||||
case XmlPullParser.START_TAG:
|
||||
// parse item
|
||||
Animator animator = null;
|
||||
if ("item".equals(parser.getName())) {
|
||||
int attributeCount = parser.getAttributeCount();
|
||||
int[] states = new int[attributeCount];
|
||||
int stateIndex = 0;
|
||||
for (int i = 0; i < attributeCount; i++) {
|
||||
int attrName = attributeSet.getAttributeNameResource(i);
|
||||
if (attrName == com.android.internal.R.attr.animation) {
|
||||
animator = loadAnimator(context,
|
||||
attributeSet.getAttributeResourceValue(i, 0));
|
||||
} else {
|
||||
states[stateIndex++] =
|
||||
attributeSet.getAttributeBooleanValue(i, false) ?
|
||||
attrName : -attrName;
|
||||
}
|
||||
|
||||
}
|
||||
if (animator == null) {
|
||||
animator = createAnimatorFromXml(context, parser);
|
||||
}
|
||||
|
||||
if (animator == null) {
|
||||
throw new Resources.NotFoundException(
|
||||
"animation state item must have a valid animation");
|
||||
}
|
||||
stateListAnimator
|
||||
.addState(StateSet.trimStateSet(states, stateIndex), animator);
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Animator createAnimatorFromXml(Context c, XmlPullParser parser)
|
||||
throws XmlPullParserException, IOException {
|
||||
|
||||
return createAnimatorFromXml(c, parser, Xml.asAttributeSet(parser), null, 0);
|
||||
}
|
||||
|
||||
|
||||
209
core/java/android/animation/StateListAnimator.java
Normal file
209
core/java/android/animation/StateListAnimator.java
Normal file
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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 android.animation;
|
||||
|
||||
import android.util.StateSet;
|
||||
import android.view.View;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Lets you define a number of Animators that will run on the attached View depending on the View's
|
||||
* drawable state.
|
||||
* <p>
|
||||
* It can be defined in an XML file with the <code><selector></code> element.
|
||||
* Each State Animator is defined in a nested <code><item></code> element.
|
||||
*
|
||||
* @attr ref android.R.styleable#DrawableStates_state_focused
|
||||
* @attr ref android.R.styleable#DrawableStates_state_window_focused
|
||||
* @attr ref android.R.styleable#DrawableStates_state_enabled
|
||||
* @attr ref android.R.styleable#DrawableStates_state_checkable
|
||||
* @attr ref android.R.styleable#DrawableStates_state_checked
|
||||
* @attr ref android.R.styleable#DrawableStates_state_selected
|
||||
* @attr ref android.R.styleable#DrawableStates_state_activated
|
||||
* @attr ref android.R.styleable#DrawableStates_state_active
|
||||
* @attr ref android.R.styleable#DrawableStates_state_single
|
||||
* @attr ref android.R.styleable#DrawableStates_state_first
|
||||
* @attr ref android.R.styleable#DrawableStates_state_middle
|
||||
* @attr ref android.R.styleable#DrawableStates_state_last
|
||||
* @attr ref android.R.styleable#DrawableStates_state_pressed
|
||||
* @attr ref android.R.styleable#StateListAnimatorItem_animation
|
||||
*/
|
||||
public class StateListAnimator {
|
||||
|
||||
private final ArrayList<Tuple> mTuples = new ArrayList<Tuple>();
|
||||
|
||||
private Tuple mLastMatch = null;
|
||||
|
||||
private Animator mRunningAnimator = null;
|
||||
|
||||
private WeakReference<View> mViewRef;
|
||||
|
||||
private AnimatorListenerAdapter mAnimatorListener = new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (mRunningAnimator == animation) {
|
||||
mRunningAnimator = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Associates the given animator with the provided drawable state specs so that it will be run
|
||||
* when the View's drawable state matches the specs.
|
||||
*
|
||||
* @param specs The drawable state specs to match against
|
||||
* @param animator The animator to run when the specs match
|
||||
*/
|
||||
public void addState(int[] specs, Animator animator) {
|
||||
Tuple tuple = new Tuple(specs, animator);
|
||||
tuple.mAnimator.addListener(mAnimatorListener);
|
||||
mTuples.add(tuple);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current {@link android.animation.Animator} which is started because of a state
|
||||
* change.
|
||||
*
|
||||
* @return The currently running Animator or null if no Animator is running
|
||||
* @hide
|
||||
*/
|
||||
public Animator getRunningAnimator() {
|
||||
return mRunningAnimator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public View getTarget() {
|
||||
return mViewRef == null ? null : mViewRef.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by View
|
||||
* @hide
|
||||
*/
|
||||
public void setTarget(View view) {
|
||||
final View current = getTarget();
|
||||
if (current == view) {
|
||||
return;
|
||||
}
|
||||
if (current != null) {
|
||||
clearTarget();
|
||||
}
|
||||
if (view != null) {
|
||||
mViewRef = new WeakReference<View>(view);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void clearTarget() {
|
||||
final int size = mTuples.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
mTuples.get(i).mAnimator.setTarget(null);
|
||||
}
|
||||
|
||||
mViewRef = null;
|
||||
mLastMatch = null;
|
||||
mRunningAnimator = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by View
|
||||
* @hide
|
||||
*/
|
||||
public void setState(int[] state) {
|
||||
Tuple match = null;
|
||||
final int count = mTuples.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final Tuple tuple = mTuples.get(i);
|
||||
if (StateSet.stateSetMatches(tuple.mSpecs, state)) {
|
||||
match = tuple;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match == mLastMatch) {
|
||||
return;
|
||||
}
|
||||
if (mLastMatch != null) {
|
||||
cancel(mLastMatch);
|
||||
}
|
||||
mLastMatch = match;
|
||||
if (match != null) {
|
||||
start(match);
|
||||
}
|
||||
}
|
||||
|
||||
private void start(Tuple match) {
|
||||
match.mAnimator.setTarget(getTarget());
|
||||
mRunningAnimator = match.mAnimator;
|
||||
match.mAnimator.start();
|
||||
}
|
||||
|
||||
private void cancel(Tuple lastMatch) {
|
||||
lastMatch.mAnimator.cancel();
|
||||
lastMatch.mAnimator.setTarget(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public ArrayList<Tuple> getTuples() {
|
||||
return mTuples;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is an animation running for a recent state change, ends it.
|
||||
* <p>
|
||||
* This causes the animation to assign the end value(s) to the View.
|
||||
*/
|
||||
public void jumpToCurrentState() {
|
||||
if (mRunningAnimator != null) {
|
||||
mRunningAnimator.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static class Tuple {
|
||||
|
||||
final int[] mSpecs;
|
||||
|
||||
final Animator mAnimator;
|
||||
|
||||
private Tuple(int[] specs, Animator animator) {
|
||||
mSpecs = specs;
|
||||
mAnimator = animator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public int[] getSpecs() {
|
||||
return mSpecs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public Animator getAnimator() {
|
||||
return mAnimator;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
package android.view;
|
||||
|
||||
import android.animation.AnimatorInflater;
|
||||
import android.animation.RevealAnimator;
|
||||
import android.animation.StateListAnimator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
@@ -667,6 +669,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
* @attr ref android.R.styleable#View_scrollbarTrackVertical
|
||||
* @attr ref android.R.styleable#View_scrollbarAlwaysDrawHorizontalTrack
|
||||
* @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack
|
||||
* @attr ref android.R.styleable#View_stateListAnimator
|
||||
* @attr ref android.R.styleable#View_sharedElementName
|
||||
* @attr ref android.R.styleable#View_soundEffectsEnabled
|
||||
* @attr ref android.R.styleable#View_tag
|
||||
@@ -3257,6 +3260,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
*/
|
||||
private Outline mOutline;
|
||||
|
||||
/**
|
||||
* Animator that automatically runs based on state changes.
|
||||
*/
|
||||
private StateListAnimator mStateListAnimator;
|
||||
|
||||
/**
|
||||
* When this view has focus and the next focus is {@link #FOCUS_LEFT},
|
||||
* the user may specify which view to go to next.
|
||||
@@ -3995,6 +4003,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
case R.styleable.View_nestedScrollingEnabled:
|
||||
setNestedScrollingEnabled(a.getBoolean(attr, false));
|
||||
break;
|
||||
case R.styleable.View_stateListAnimator:
|
||||
setStateListAnimator(AnimatorInflater.loadStateListAnimator(context,
|
||||
a.getResourceId(attr, 0)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10619,6 +10631,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
startRadius, endRadius, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current StateListAnimator if exists.
|
||||
*
|
||||
* @return StateListAnimator or null if it does not exists
|
||||
* @see #setStateListAnimator(android.animation.StateListAnimator)
|
||||
*/
|
||||
public StateListAnimator getStateListAnimator() {
|
||||
return mStateListAnimator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches the provided StateListAnimator to this View.
|
||||
* <p>
|
||||
* Any previously attached StateListAnimator will be detached.
|
||||
*
|
||||
* @param stateListAnimator The StateListAnimator to update the view
|
||||
* @see {@link android.animation.StateListAnimator}
|
||||
*/
|
||||
public void setStateListAnimator(StateListAnimator stateListAnimator) {
|
||||
if (mStateListAnimator == stateListAnimator) {
|
||||
return;
|
||||
}
|
||||
if (mStateListAnimator != null) {
|
||||
mStateListAnimator.setTarget(null);
|
||||
}
|
||||
mStateListAnimator = stateListAnimator;
|
||||
if (stateListAnimator != null) {
|
||||
stateListAnimator.setTarget(this);
|
||||
if (isAttachedToWindow()) {
|
||||
stateListAnimator.setState(getDrawableState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the outline of the view, which defines the shape of the shadow it
|
||||
* casts.
|
||||
@@ -12835,7 +12881,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
destroyLayer(false);
|
||||
|
||||
cleanupDraw();
|
||||
|
||||
mCurrentAnimation = null;
|
||||
}
|
||||
|
||||
@@ -15489,9 +15534,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
/**
|
||||
* This function is called whenever the state of the view changes in such
|
||||
* a way that it impacts the state of drawables being shown.
|
||||
*
|
||||
* <p>Be sure to call through to the superclass when overriding this
|
||||
* function.
|
||||
* <p>
|
||||
* If the View has a StateListAnimator, it will also be called to run necessary state
|
||||
* change animations.
|
||||
* <p>
|
||||
* Be sure to call through to the superclass when overriding this function.
|
||||
*
|
||||
* @see Drawable#setState(int[])
|
||||
*/
|
||||
@@ -15500,6 +15547,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
if (d != null && d.isStateful()) {
|
||||
d.setState(getDrawableState());
|
||||
}
|
||||
|
||||
if (mStateListAnimator != null) {
|
||||
mStateListAnimator.setState(getDrawableState());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -15644,11 +15695,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
/**
|
||||
* Call {@link Drawable#jumpToCurrentState() Drawable.jumpToCurrentState()}
|
||||
* on all Drawable objects associated with this view.
|
||||
* <p>
|
||||
* Also calls {@link StateListAnimator#jumpToCurrentState()} if there is a StateListAnimator
|
||||
* attached to this view.
|
||||
*/
|
||||
public void jumpDrawablesToCurrentState() {
|
||||
if (mBackground != null) {
|
||||
mBackground.jumpToCurrentState();
|
||||
}
|
||||
if (mStateListAnimator != null) {
|
||||
mStateListAnimator.jumpToCurrentState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user