diff options
-rw-r--r-- | api/current.txt | 10 | ||||
-rw-r--r-- | core/java/android/animation/AnimatorInflater.java | 80 | ||||
-rw-r--r-- | core/java/android/animation/StateListAnimator.java | 209 | ||||
-rw-r--r-- | core/java/android/view/View.java | 65 | ||||
-rw-r--r-- | core/res/res/anim/button_state_list_anim_quantum.xml | 34 | ||||
-rw-r--r-- | core/res/res/values/attrs.xml | 8 | ||||
-rw-r--r-- | core/res/res/values/dimens_quantum.xml | 3 | ||||
-rw-r--r-- | core/res/res/values/integers.xml | 1 | ||||
-rw-r--r-- | core/res/res/values/public.xml | 1 | ||||
-rw-r--r-- | core/res/res/values/styles_quantum.xml | 2 | ||||
-rw-r--r-- | core/tests/coretests/res/anim/reset_state_anim.xml | 6 | ||||
-rw-r--r-- | core/tests/coretests/res/anim/test_state_anim.xml | 19 | ||||
-rw-r--r-- | core/tests/coretests/src/android/animation/StateListAnimatorTest.java | 102 |
13 files changed, 535 insertions, 5 deletions
diff --git a/api/current.txt b/api/current.txt index 63a8421..e987b08 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1061,6 +1061,7 @@ package android { field public static final int startDelay = 16843746; // 0x10103e2 field public static final int startOffset = 16843198; // 0x10101be field public static final deprecated int startYear = 16843132; // 0x101017c + field public static final int stateListAnimator = 16843860; // 0x1010454 field public static final int stateNotNeeded = 16842774; // 0x1010016 field public static final int state_above_anchor = 16842922; // 0x10100aa field public static final int state_accelerated = 16843547; // 0x101031b @@ -2788,6 +2789,7 @@ package android.animation { public class AnimatorInflater { ctor public AnimatorInflater(); method public static android.animation.Animator loadAnimator(android.content.Context, int) throws android.content.res.Resources.NotFoundException; + method public static android.animation.StateListAnimator loadStateListAnimator(android.content.Context, int) throws android.content.res.Resources.NotFoundException; } public abstract class AnimatorListenerAdapter implements android.animation.Animator.AnimatorListener android.animation.Animator.AnimatorPauseListener { @@ -2984,6 +2986,12 @@ package android.animation { method public android.graphics.Rect evaluate(float, android.graphics.Rect, android.graphics.Rect); } + public class StateListAnimator { + ctor public StateListAnimator(); + method public void addState(int[], android.animation.Animator); + method public void jumpToCurrentState(); + } + public class TimeAnimator extends android.animation.ValueAnimator { ctor public TimeAnimator(); method public void setTimeListener(android.animation.TimeAnimator.TimeListener); @@ -30545,6 +30553,7 @@ package android.view { method public final int getScrollY(); method public java.lang.String getSharedElementName(); method public int getSolidColor(); + method public android.animation.StateListAnimator getStateListAnimator(); method protected int getSuggestedMinimumHeight(); method protected int getSuggestedMinimumWidth(); method public int getSystemUiVisibility(); @@ -30809,6 +30818,7 @@ package android.view { method public void setSelected(boolean); method public void setSharedElementName(java.lang.String); method public void setSoundEffectsEnabled(boolean); + method public void setStateListAnimator(android.animation.StateListAnimator); method public void setSystemUiVisibility(int); method public void setTag(java.lang.Object); method public void setTag(int, java.lang.Object); diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java index 20236aa..933135d 100644 --- a/core/java/android/animation/AnimatorInflater.java +++ b/core/java/android/animation/AnimatorInflater.java @@ -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); } diff --git a/core/java/android/animation/StateListAnimator.java b/core/java/android/animation/StateListAnimator.java new file mode 100644 index 0000000..bc4843d --- /dev/null +++ b/core/java/android/animation/StateListAnimator.java @@ -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; + } + } +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index d8fcfc5..bef96b1 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -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 @@ -3258,6 +3261,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; } } @@ -10620,6 +10632,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * 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. * <p> @@ -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(); + } } /** diff --git a/core/res/res/anim/button_state_list_anim_quantum.xml b/core/res/res/anim/button_state_list_anim_quantum.xml new file mode 100644 index 0000000..01989a4 --- /dev/null +++ b/core/res/res/anim/button_state_list_anim_quantum.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" android:state_enabled="true"> + <set> + <objectAnimator android:propertyName="translationZ" + android:duration="@integer/button_pressed_animation_duration" + android:valueTo="@dimen/button_pressed_z" + android:valueType="floatType"/> + </set> + </item> + <!-- base state --> + <item> + <set> + <objectAnimator android:propertyName="translationZ" + android:duration="@integer/button_pressed_animation_duration" + android:valueTo="0" + android:valueType="floatType"/> + </set> + </item> +</selector>
\ No newline at end of file diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 0326e18..172877f 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2364,6 +2364,9 @@ <!-- Specifies that this view should permit nested scrolling within a compatible ancestor view. --> <attr name="nestedScrollingEnabled" format="boolean" /> + + <!-- Sets the state-based animator for the View. --> + <attr name="stateListAnimator" format="reference"/> </declare-styleable> <!-- Attributes that can be assigned to a tag for a particular View. --> @@ -4255,6 +4258,11 @@ <attr name="drawable" format="reference" /> </declare-styleable> + <!-- Attributes that can be assigned to a StateListAnimator item. --> + <declare-styleable name="StateListAnimatorItem"> + <attr name="animation"/> + </declare-styleable> + <!-- Drawable used to render a geometric shape, with a gradient or a solid color. --> <declare-styleable name="GradientDrawable"> <!-- Indicates whether the drawable should intially be visible. --> diff --git a/core/res/res/values/dimens_quantum.xml b/core/res/res/values/dimens_quantum.xml index cebee12..53e97fd 100644 --- a/core/res/res/values/dimens_quantum.xml +++ b/core/res/res/values/dimens_quantum.xml @@ -49,4 +49,7 @@ <dimen name="floating_window_z">16dp</dimen> <dimen name="floating_window_margin">32dp</dimen> + + <!-- the amount of elevation for pressed button state--> + <dimen name="button_pressed_z">2dp</dimen> </resources> diff --git a/core/res/res/values/integers.xml b/core/res/res/values/integers.xml index dc90bbf..e6748c4 100644 --- a/core/res/res/values/integers.xml +++ b/core/res/res/values/integers.xml @@ -19,4 +19,5 @@ <resources> <integer name="kg_carousel_angle">75</integer> <integer name="kg_glowpad_rotation_offset">0</integer> + <integer name="button_pressed_animation_duration">100</integer> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 8874c30..d0cdefe 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2171,6 +2171,7 @@ <public type="attr" name="actionOverflowMenuStyle" /> <public type="attr" name="documentLaunchMode" /> <public type="attr" name="autoRemoveFromRecents" /> + <public type="attr" name="stateListAnimator" /> <public-padding type="dimen" name="l_resource_pad" end="0x01050010" /> diff --git a/core/res/res/values/styles_quantum.xml b/core/res/res/values/styles_quantum.xml index 01c3017..88a2a9f 100644 --- a/core/res/res/values/styles_quantum.xml +++ b/core/res/res/values/styles_quantum.xml @@ -364,6 +364,7 @@ please see styles_device_defaults.xml. <item name="textColor">?attr/textColorPrimary</item> <item name="minHeight">48dip</item> <item name="minWidth">96dip</item> + <item name="stateListAnimator">@anim/button_state_list_anim_quantum</item> </style> <!-- Small bordered ink button --> @@ -375,6 +376,7 @@ please see styles_device_defaults.xml. <!-- Borderless ink button --> <style name="Widget.Quantum.Button.Borderless"> <item name="background">@drawable/btn_borderless_quantum</item> + <item name="stateListAnimator">@null</item> </style> <!-- Small borderless ink button --> diff --git a/core/tests/coretests/res/anim/reset_state_anim.xml b/core/tests/coretests/res/anim/reset_state_anim.xml new file mode 100644 index 0000000..918d0a3 --- /dev/null +++ b/core/tests/coretests/res/anim/reset_state_anim.xml @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="0" android:valueType="floatType"/> + <objectAnimator android:propertyName="y" android:duration="100" android:valueTo="0" android:valueType="floatType"/> + <objectAnimator android:propertyName="z" android:duration="100" android:valueTo="0" android:valueType="floatType"/> +</set>
\ No newline at end of file diff --git a/core/tests/coretests/res/anim/test_state_anim.xml b/core/tests/coretests/res/anim/test_state_anim.xml new file mode 100644 index 0000000..9e08f68 --- /dev/null +++ b/core/tests/coretests/res/anim/test_state_anim.xml @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true"> + <set> + <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="10" android:valueType="floatType"/> + <objectAnimator android:propertyName="y" android:duration="100" android:valueTo="20" android:valueType="floatType"/> + <objectAnimator android:propertyName="z" android:duration="100" android:valueTo="20" android:valueType="floatType"/> + </set> + </item> + <item android:state_enabled="true" android:state_pressed="false"> + <set> + <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="0" android:valueType="floatType"/> + <objectAnimator android:propertyName="y" android:duration="100" android:valueTo="0" android:valueType="floatType"/> + <objectAnimator android:propertyName="z" android:duration="100" android:valueTo="0" android:valueType="floatType"/> + </set> + </item> + <!-- base state--> + <item android:animation="@anim/reset_state_anim"/> +</selector>
\ No newline at end of file diff --git a/core/tests/coretests/src/android/animation/StateListAnimatorTest.java b/core/tests/coretests/src/android/animation/StateListAnimatorTest.java new file mode 100644 index 0000000..38df78d --- /dev/null +++ b/core/tests/coretests/src/android/animation/StateListAnimatorTest.java @@ -0,0 +1,102 @@ +/* +* 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.test.ActivityInstrumentationTestCase2; +import android.test.UiThreadTest; +import android.util.StateSet; +import android.view.View; +import android.view.ViewGroup; + +import com.android.frameworks.coretests.R; + +import java.util.concurrent.atomic.AtomicInteger; + + +public class StateListAnimatorTest extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> { + + public StateListAnimatorTest() { + super(BasicAnimatorActivity.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + public void testInflateFromAnimator() throws Exception { + StateListAnimator stateListAnimator = AnimatorInflater + .loadStateListAnimator(getActivity(), R.anim.test_state_anim); + assertNotNull("A state list animator should be returned", stateListAnimator); + assertEquals("State list animator should have three items", 3, + stateListAnimator.getTuples().size()); + } + + @UiThreadTest + public void testAttachDetach() throws Exception { + View view = new View(getActivity()); + final AtomicInteger setStateCount = new AtomicInteger(0); + StateListAnimator stateListAnimator = new StateListAnimator() { + @Override + public void setState(int[] state) { + setStateCount.incrementAndGet(); + super.setState(state); + } + }; + view.setStateListAnimator(stateListAnimator); + assertNotNull("State list animator should have a reference to view even if it is detached", + stateListAnimator.getTarget()); + ViewGroup viewGroup = (ViewGroup) getActivity().findViewById(android.R.id.content); + int preSetStateCount = setStateCount.get(); + viewGroup.addView(view); + assertTrue("When view is attached, state list drawable's setState should be called", + preSetStateCount < setStateCount.get()); + + StateListAnimator stateListAnimator2 = new StateListAnimator(); + view.setStateListAnimator(stateListAnimator2); + assertNull("When a new state list animator is assigned, previous one should be detached", + stateListAnimator.getTarget()); + assertNull("Any running animator should be removed on detach", + stateListAnimator.getRunningAnimator()); + assertEquals("The new state list animator should be attached to the view", + view, stateListAnimator2.getTarget()); + viewGroup.removeView(view); + assertNotNull("When view is detached from window, state list animator should still keep the" + + " reference", + stateListAnimator2.getTarget()); + } + + public void testStateListLoading() throws InterruptedException { + StateListAnimator stateListAnimator = AnimatorInflater + .loadStateListAnimator(getActivity(), R.anim.test_state_anim); + assertNotNull("A state list animator should be returned", stateListAnimator); + assertEquals("Steate list animator should have two items", 3, + stateListAnimator.getTuples().size()); + StateListAnimator.Tuple tuple1 = stateListAnimator.getTuples().get(0); + assertEquals("first tuple should have one state", 1, tuple1.getSpecs().length); + assertEquals("first spec in tuple 1 should be pressed", + com.android.internal.R.attr.state_pressed, tuple1.getSpecs()[0]); + + StateListAnimator.Tuple tuple2 = stateListAnimator.getTuples().get(1); + assertEquals("Second tuple should have two specs", 2, tuple2.getSpecs().length); + assertTrue("Tuple two should match the expected state", + StateSet.stateSetMatches(tuple2.getSpecs(), + new int[]{-com.android.internal.R.attr.state_pressed, + com.android.internal.R.attr.state_enabled})); + } +} |