am 91c091cf: Merge "State based animators for Views"

* commit '91c091cf7b732796d26971de6a508966e24d40a0':
  State based animators for Views
This commit is contained in:
Alan Viverette
2014-05-08 00:01:07 +00:00
committed by Android Git Automerger
13 changed files with 535 additions and 5 deletions

View File

@@ -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);
}

View 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>&lt;selector></code> element.
* Each State Animator is defined in a nested <code>&lt;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;
}
}
}

View File

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