1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.animation; 18 19 import android.util.StateSet; 20 import android.view.View; 21 22 import java.lang.ref.WeakReference; 23 import java.util.ArrayList; 24 25 /** 26 * Lets you define a number of Animators that will run on the attached View depending on the View's 27 * drawable state. 28 * <p> 29 * It can be defined in an XML file with the <code><selector></code> element. 30 * Each State Animator is defined in a nested <code><item></code> element. 31 * 32 * @attr ref android.R.styleable#DrawableStates_state_focused 33 * @attr ref android.R.styleable#DrawableStates_state_window_focused 34 * @attr ref android.R.styleable#DrawableStates_state_enabled 35 * @attr ref android.R.styleable#DrawableStates_state_checkable 36 * @attr ref android.R.styleable#DrawableStates_state_checked 37 * @attr ref android.R.styleable#DrawableStates_state_selected 38 * @attr ref android.R.styleable#DrawableStates_state_activated 39 * @attr ref android.R.styleable#DrawableStates_state_active 40 * @attr ref android.R.styleable#DrawableStates_state_single 41 * @attr ref android.R.styleable#DrawableStates_state_first 42 * @attr ref android.R.styleable#DrawableStates_state_middle 43 * @attr ref android.R.styleable#DrawableStates_state_last 44 * @attr ref android.R.styleable#DrawableStates_state_pressed 45 * @attr ref android.R.styleable#StateListAnimatorItem_animation 46 */ 47 public class StateListAnimator { 48 49 private final ArrayList<Tuple> mTuples = new ArrayList<Tuple>(); 50 51 private Tuple mLastMatch = null; 52 53 private Animator mRunningAnimator = null; 54 55 private WeakReference<View> mViewRef; 56 57 private AnimatorListenerAdapter mAnimatorListener = new AnimatorListenerAdapter() { 58 @Override 59 public void onAnimationEnd(Animator animation) { 60 animation.setTarget(null); 61 if (mRunningAnimator == animation) { 62 mRunningAnimator = null; 63 } 64 } 65 }; 66 67 /** 68 * Associates the given animator with the provided drawable state specs so that it will be run 69 * when the View's drawable state matches the specs. 70 * 71 * @param specs The drawable state specs to match against 72 * @param animator The animator to run when the specs match 73 */ addState(int[] specs, Animator animator)74 public void addState(int[] specs, Animator animator) { 75 Tuple tuple = new Tuple(specs, animator); 76 tuple.mAnimator.addListener(mAnimatorListener); 77 mTuples.add(tuple); 78 } 79 80 /** 81 * Returns the current {@link android.animation.Animator} which is started because of a state 82 * change. 83 * 84 * @return The currently running Animator or null if no Animator is running 85 * @hide 86 */ getRunningAnimator()87 public Animator getRunningAnimator() { 88 return mRunningAnimator; 89 } 90 91 /** 92 * @hide 93 */ getTarget()94 public View getTarget() { 95 return mViewRef == null ? null : mViewRef.get(); 96 } 97 98 /** 99 * Called by View 100 * @hide 101 */ setTarget(View view)102 public void setTarget(View view) { 103 final View current = getTarget(); 104 if (current == view) { 105 return; 106 } 107 if (current != null) { 108 clearTarget(); 109 } 110 if (view != null) { 111 mViewRef = new WeakReference<View>(view); 112 } 113 114 } 115 clearTarget()116 private void clearTarget() { 117 final int size = mTuples.size(); 118 for (int i = 0; i < size; i++) { 119 mTuples.get(i).mAnimator.setTarget(null); 120 } 121 122 mViewRef = null; 123 mLastMatch = null; 124 mRunningAnimator = null; 125 } 126 127 /** 128 * Called by View 129 * @hide 130 */ setState(int[] state)131 public void setState(int[] state) { 132 Tuple match = null; 133 final int count = mTuples.size(); 134 for (int i = 0; i < count; i++) { 135 final Tuple tuple = mTuples.get(i); 136 if (StateSet.stateSetMatches(tuple.mSpecs, state)) { 137 match = tuple; 138 break; 139 } 140 } 141 if (match == mLastMatch) { 142 return; 143 } 144 if (mLastMatch != null) { 145 cancel(); 146 } 147 mLastMatch = match; 148 if (match != null) { 149 start(match); 150 } 151 } 152 start(Tuple match)153 private void start(Tuple match) { 154 match.mAnimator.setTarget(getTarget()); 155 mRunningAnimator = match.mAnimator; 156 mRunningAnimator.start(); 157 } 158 cancel()159 private void cancel() { 160 if (mRunningAnimator != null) { 161 mRunningAnimator.cancel(); 162 mRunningAnimator = null; 163 } 164 } 165 166 /** 167 * @hide 168 */ getTuples()169 public ArrayList<Tuple> getTuples() { 170 return mTuples; 171 } 172 173 /** 174 * If there is an animation running for a recent state change, ends it. 175 * <p> 176 * This causes the animation to assign the end value(s) to the View. 177 */ jumpToCurrentState()178 public void jumpToCurrentState() { 179 if (mRunningAnimator != null) { 180 mRunningAnimator.end(); 181 } 182 } 183 184 /** 185 * @hide 186 */ 187 public static class Tuple { 188 189 final int[] mSpecs; 190 191 final Animator mAnimator; 192 Tuple(int[] specs, Animator animator)193 private Tuple(int[] specs, Animator animator) { 194 mSpecs = specs; 195 mAnimator = animator; 196 } 197 198 /** 199 * @hide 200 */ getSpecs()201 public int[] getSpecs() { 202 return mSpecs; 203 } 204 205 /** 206 * @hide 207 */ getAnimator()208 public Animator getAnimator() { 209 return mAnimator; 210 } 211 } 212 } 213