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.content.pm.ActivityInfo.Config; 20 import android.content.res.ConstantState; 21 import android.util.StateSet; 22 import android.view.View; 23 24 import java.lang.ref.WeakReference; 25 import java.util.ArrayList; 26 27 /** 28 * Lets you define a number of Animators that will run on the attached View depending on the View's 29 * drawable state. 30 * <p> 31 * It can be defined in an XML file with the <code><selector></code> element. 32 * Each State Animator is defined in a nested <code><item></code> element. 33 * 34 * @attr ref android.R.styleable#DrawableStates_state_focused 35 * @attr ref android.R.styleable#DrawableStates_state_window_focused 36 * @attr ref android.R.styleable#DrawableStates_state_enabled 37 * @attr ref android.R.styleable#DrawableStates_state_checkable 38 * @attr ref android.R.styleable#DrawableStates_state_checked 39 * @attr ref android.R.styleable#DrawableStates_state_selected 40 * @attr ref android.R.styleable#DrawableStates_state_activated 41 * @attr ref android.R.styleable#DrawableStates_state_active 42 * @attr ref android.R.styleable#DrawableStates_state_single 43 * @attr ref android.R.styleable#DrawableStates_state_first 44 * @attr ref android.R.styleable#DrawableStates_state_middle 45 * @attr ref android.R.styleable#DrawableStates_state_last 46 * @attr ref android.R.styleable#DrawableStates_state_pressed 47 * @attr ref android.R.styleable#StateListAnimatorItem_animation 48 */ 49 public class StateListAnimator implements Cloneable { 50 51 private ArrayList<Tuple> mTuples = new ArrayList<Tuple>(); 52 private Tuple mLastMatch = null; 53 private Animator mRunningAnimator = null; 54 private WeakReference<View> mViewRef; 55 private StateListAnimatorConstantState mConstantState; 56 private AnimatorListenerAdapter mAnimatorListener; 57 private @Config int mChangingConfigurations; 58 StateListAnimator()59 public StateListAnimator() { 60 initAnimatorListener(); 61 } 62 initAnimatorListener()63 private void initAnimatorListener() { 64 mAnimatorListener = new AnimatorListenerAdapter() { 65 @Override 66 public void onAnimationEnd(Animator animation) { 67 animation.setTarget(null); 68 if (mRunningAnimator == animation) { 69 mRunningAnimator = null; 70 } 71 } 72 }; 73 } 74 75 /** 76 * Associates the given animator with the provided drawable state specs so that it will be run 77 * when the View's drawable state matches the specs. 78 * 79 * @param specs The drawable state specs to match against 80 * @param animator The animator to run when the specs match 81 */ addState(int[] specs, Animator animator)82 public void addState(int[] specs, Animator animator) { 83 Tuple tuple = new Tuple(specs, animator); 84 tuple.mAnimator.addListener(mAnimatorListener); 85 mTuples.add(tuple); 86 mChangingConfigurations |= animator.getChangingConfigurations(); 87 } 88 89 /** 90 * Returns the current {@link android.animation.Animator} which is started because of a state 91 * change. 92 * 93 * @return The currently running Animator or null if no Animator is running 94 * @hide 95 */ getRunningAnimator()96 public Animator getRunningAnimator() { 97 return mRunningAnimator; 98 } 99 100 /** 101 * @hide 102 */ getTarget()103 public View getTarget() { 104 return mViewRef == null ? null : mViewRef.get(); 105 } 106 107 /** 108 * Called by View 109 * @hide 110 */ setTarget(View view)111 public void setTarget(View view) { 112 final View current = getTarget(); 113 if (current == view) { 114 return; 115 } 116 if (current != null) { 117 clearTarget(); 118 } 119 if (view != null) { 120 mViewRef = new WeakReference<View>(view); 121 } 122 123 } 124 clearTarget()125 private void clearTarget() { 126 final int size = mTuples.size(); 127 for (int i = 0; i < size; i++) { 128 mTuples.get(i).mAnimator.setTarget(null); 129 } 130 mViewRef = null; 131 mLastMatch = null; 132 mRunningAnimator = null; 133 } 134 135 @Override clone()136 public StateListAnimator clone() { 137 try { 138 StateListAnimator clone = (StateListAnimator) super.clone(); 139 clone.mTuples = new ArrayList<Tuple>(mTuples.size()); 140 clone.mLastMatch = null; 141 clone.mRunningAnimator = null; 142 clone.mViewRef = null; 143 clone.mAnimatorListener = null; 144 clone.initAnimatorListener(); 145 final int tupleSize = mTuples.size(); 146 for (int i = 0; i < tupleSize; i++) { 147 final Tuple tuple = mTuples.get(i); 148 final Animator animatorClone = tuple.mAnimator.clone(); 149 animatorClone.removeListener(mAnimatorListener); 150 clone.addState(tuple.mSpecs, animatorClone); 151 } 152 clone.setChangingConfigurations(getChangingConfigurations()); 153 return clone; 154 } catch (CloneNotSupportedException e) { 155 throw new AssertionError("cannot clone state list animator", e); 156 } 157 } 158 159 /** 160 * Called by View 161 * @hide 162 */ setState(int[] state)163 public void setState(int[] state) { 164 Tuple match = null; 165 final int count = mTuples.size(); 166 for (int i = 0; i < count; i++) { 167 final Tuple tuple = mTuples.get(i); 168 if (StateSet.stateSetMatches(tuple.mSpecs, state)) { 169 match = tuple; 170 break; 171 } 172 } 173 if (match == mLastMatch) { 174 return; 175 } 176 if (mLastMatch != null) { 177 cancel(); 178 } 179 mLastMatch = match; 180 if (match != null) { 181 start(match); 182 } 183 } 184 start(Tuple match)185 private void start(Tuple match) { 186 match.mAnimator.setTarget(getTarget()); 187 mRunningAnimator = match.mAnimator; 188 mRunningAnimator.start(); 189 } 190 cancel()191 private void cancel() { 192 if (mRunningAnimator != null) { 193 mRunningAnimator.cancel(); 194 mRunningAnimator = null; 195 } 196 } 197 198 /** 199 * @hide 200 */ getTuples()201 public ArrayList<Tuple> getTuples() { 202 return mTuples; 203 } 204 205 /** 206 * If there is an animation running for a recent state change, ends it. 207 * <p> 208 * This causes the animation to assign the end value(s) to the View. 209 */ jumpToCurrentState()210 public void jumpToCurrentState() { 211 if (mRunningAnimator != null) { 212 mRunningAnimator.end(); 213 } 214 } 215 216 /** 217 * Return a mask of the configuration parameters for which this animator may change, requiring 218 * that it be re-created. The default implementation returns whatever was provided through 219 * {@link #setChangingConfigurations(int)} or 0 by default. 220 * 221 * @return Returns a mask of the changing configuration parameters, as defined by 222 * {@link android.content.pm.ActivityInfo}. 223 * 224 * @see android.content.pm.ActivityInfo 225 * @hide 226 */ getChangingConfigurations()227 public @Config int getChangingConfigurations() { 228 return mChangingConfigurations; 229 } 230 231 /** 232 * Set a mask of the configuration parameters for which this animator may change, requiring 233 * that it should be recreated from resources instead of being cloned. 234 * 235 * @param configs A mask of the changing configuration parameters, as 236 * defined by {@link android.content.pm.ActivityInfo}. 237 * 238 * @see android.content.pm.ActivityInfo 239 * @hide 240 */ setChangingConfigurations(@onfig int configs)241 public void setChangingConfigurations(@Config int configs) { 242 mChangingConfigurations = configs; 243 } 244 245 /** 246 * Sets the changing configurations value to the union of the current changing configurations 247 * and the provided configs. 248 * This method is called while loading the animator. 249 * @hide 250 */ appendChangingConfigurations(@onfig int configs)251 public void appendChangingConfigurations(@Config int configs) { 252 mChangingConfigurations |= configs; 253 } 254 255 /** 256 * Return a {@link android.content.res.ConstantState} instance that holds the shared state of 257 * this Animator. 258 * <p> 259 * This constant state is used to create new instances of this animator when needed. Default 260 * implementation creates a new {@link StateListAnimatorConstantState}. You can override this 261 * method to provide your custom logic or return null if you don't want this animator to be 262 * cached. 263 * 264 * @return The {@link android.content.res.ConstantState} associated to this Animator. 265 * @see android.content.res.ConstantState 266 * @see #clone() 267 * @hide 268 */ createConstantState()269 public ConstantState<StateListAnimator> createConstantState() { 270 return new StateListAnimatorConstantState(this); 271 } 272 273 /** 274 * @hide 275 */ 276 public static class Tuple { 277 278 final int[] mSpecs; 279 280 final Animator mAnimator; 281 Tuple(int[] specs, Animator animator)282 private Tuple(int[] specs, Animator animator) { 283 mSpecs = specs; 284 mAnimator = animator; 285 } 286 287 /** 288 * @hide 289 */ getSpecs()290 public int[] getSpecs() { 291 return mSpecs; 292 } 293 294 /** 295 * @hide 296 */ getAnimator()297 public Animator getAnimator() { 298 return mAnimator; 299 } 300 } 301 302 /** 303 * Creates a constant state which holds changing configurations information associated with the 304 * given Animator. 305 * <p> 306 * When new instance is called, default implementation clones the Animator. 307 */ 308 private static class StateListAnimatorConstantState 309 extends ConstantState<StateListAnimator> { 310 311 final StateListAnimator mAnimator; 312 313 @Config int mChangingConf; 314 StateListAnimatorConstantState(StateListAnimator animator)315 public StateListAnimatorConstantState(StateListAnimator animator) { 316 mAnimator = animator; 317 mAnimator.mConstantState = this; 318 mChangingConf = mAnimator.getChangingConfigurations(); 319 } 320 321 @Override getChangingConfigurations()322 public @Config int getChangingConfigurations() { 323 return mChangingConf; 324 } 325 326 @Override newInstance()327 public StateListAnimator newInstance() { 328 final StateListAnimator clone = mAnimator.clone(); 329 clone.mConstantState = this; 330 return clone; 331 } 332 } 333 } 334