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