Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b4669777 authored by Alan Viverette's avatar Alan Viverette Committed by Android (Google) Code Review
Browse files

Merge "State based animators for Views"

parents ae493d96 f4c5bf30
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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);
+79 −1
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
/*
 * 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;
        }
    }
}
+61 −4
Original line number Diff line number Diff line
@@ -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();
        }
    }
    /**
+34 −0
Original line number Diff line number Diff line
<?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
Loading