1/* 2 * Copyright (C) 2016 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.support.transition; 18 19import android.animation.TimeInterpolator; 20import android.support.annotation.RestrictTo; 21import android.util.AndroidRuntimeException; 22import android.view.View; 23import android.view.ViewGroup; 24 25import java.util.ArrayList; 26 27import static android.support.annotation.RestrictTo.Scope.GROUP_ID; 28 29class TransitionSetPort extends TransitionPort { 30 31 /** 32 * A flag used to indicate that the child transitions of this set 33 * should all start at the same time. 34 */ 35 public static final int ORDERING_TOGETHER = 0; 36 37 /** 38 * A flag used to indicate that the child transitions of this set should 39 * play in sequence; when one child transition ends, the next child 40 * transition begins. Note that a transition does not end until all 41 * instances of it (which are playing on all applicable targets of the 42 * transition) end. 43 */ 44 public static final int ORDERING_SEQUENTIAL = 1; 45 46 ArrayList<TransitionPort> mTransitions = new ArrayList<TransitionPort>(); 47 48 int mCurrentListeners; 49 50 boolean mStarted = false; 51 52 private boolean mPlayTogether = true; 53 54 public TransitionSetPort() { 55 } 56 57 public int getOrdering() { 58 return mPlayTogether ? ORDERING_TOGETHER : ORDERING_SEQUENTIAL; 59 } 60 61 public TransitionSetPort setOrdering(int ordering) { 62 switch (ordering) { 63 case ORDERING_SEQUENTIAL: 64 mPlayTogether = false; 65 break; 66 case ORDERING_TOGETHER: 67 mPlayTogether = true; 68 break; 69 default: 70 throw new AndroidRuntimeException("Invalid parameter for TransitionSet " + 71 "ordering: " + ordering); 72 } 73 return this; 74 } 75 76 public TransitionSetPort addTransition(TransitionPort transition) { 77 if (transition != null) { 78 mTransitions.add(transition); 79 transition.mParent = this; 80 if (mDuration >= 0) { 81 transition.setDuration(mDuration); 82 } 83 } 84 return this; 85 } 86 87 /** 88 * Setting a non-negative duration on a TransitionSet causes all of the child 89 * transitions (current and future) to inherit this duration. 90 * 91 * @param duration The length of the animation, in milliseconds. 92 * @return This transitionSet object. 93 */ 94 @Override 95 public TransitionSetPort setDuration(long duration) { 96 super.setDuration(duration); 97 if (mDuration >= 0) { 98 int numTransitions = mTransitions.size(); 99 for (int i = 0; i < numTransitions; ++i) { 100 mTransitions.get(i).setDuration(duration); 101 } 102 } 103 return this; 104 } 105 106 @Override 107 public TransitionSetPort setStartDelay(long startDelay) { 108 return (TransitionSetPort) super.setStartDelay(startDelay); 109 } 110 111 @Override 112 public TransitionSetPort setInterpolator(TimeInterpolator interpolator) { 113 return (TransitionSetPort) super.setInterpolator(interpolator); 114 } 115 116 @Override 117 public TransitionSetPort addTarget(View target) { 118 return (TransitionSetPort) super.addTarget(target); 119 } 120 121 @Override 122 public TransitionSetPort addTarget(int targetId) { 123 return (TransitionSetPort) super.addTarget(targetId); 124 } 125 126 @Override 127 public TransitionSetPort addListener(TransitionListener listener) { 128 return (TransitionSetPort) super.addListener(listener); 129 } 130 131 @Override 132 public TransitionSetPort removeTarget(int targetId) { 133 return (TransitionSetPort) super.removeTarget(targetId); 134 } 135 136 @Override 137 public TransitionSetPort removeTarget(View target) { 138 return (TransitionSetPort) super.removeTarget(target); 139 } 140 141 @Override 142 public TransitionSetPort removeListener(TransitionListener listener) { 143 return (TransitionSetPort) super.removeListener(listener); 144 } 145 146 public TransitionSetPort removeTransition(TransitionPort transition) { 147 mTransitions.remove(transition); 148 transition.mParent = null; 149 return this; 150 } 151 152 /** 153 * Sets up listeners for each of the child transitions. This is used to 154 * determine when this transition set is finished (all child transitions 155 * must finish first). 156 */ 157 private void setupStartEndListeners() { 158 TransitionSetListener listener = new TransitionSetListener(this); 159 for (TransitionPort childTransition : mTransitions) { 160 childTransition.addListener(listener); 161 } 162 mCurrentListeners = mTransitions.size(); 163 } 164 165 /** 166 * @hide 167 */ 168 @RestrictTo(GROUP_ID) 169 @Override 170 protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues, 171 TransitionValuesMaps endValues) { 172 for (TransitionPort childTransition : mTransitions) { 173 childTransition.createAnimators(sceneRoot, startValues, endValues); 174 } 175 } 176 177 /** 178 * @hide 179 */ 180 @RestrictTo(GROUP_ID) 181 @Override 182 protected void runAnimators() { 183 if (mTransitions.isEmpty()) { 184 start(); 185 end(); 186 return; 187 } 188 setupStartEndListeners(); 189 if (!mPlayTogether) { 190 // Setup sequence with listeners 191 // TODO: Need to add listeners in such a way that we can remove them later if canceled 192 for (int i = 1; i < mTransitions.size(); ++i) { 193 TransitionPort previousTransition = mTransitions.get(i - 1); 194 final TransitionPort nextTransition = mTransitions.get(i); 195 previousTransition.addListener(new TransitionListenerAdapter() { 196 @Override 197 public void onTransitionEnd(TransitionPort transition) { 198 nextTransition.runAnimators(); 199 transition.removeListener(this); 200 } 201 }); 202 } 203 TransitionPort firstTransition = mTransitions.get(0); 204 if (firstTransition != null) { 205 firstTransition.runAnimators(); 206 } 207 } else { 208 for (TransitionPort childTransition : mTransitions) { 209 childTransition.runAnimators(); 210 } 211 } 212 } 213 214 @Override 215 public void captureStartValues(TransitionValues transitionValues) { 216 int targetId = transitionValues.view.getId(); 217 if (isValidTarget(transitionValues.view, targetId)) { 218 for (TransitionPort childTransition : mTransitions) { 219 if (childTransition.isValidTarget(transitionValues.view, targetId)) { 220 childTransition.captureStartValues(transitionValues); 221 } 222 } 223 } 224 } 225 226 @Override 227 public void captureEndValues(TransitionValues transitionValues) { 228 int targetId = transitionValues.view.getId(); 229 if (isValidTarget(transitionValues.view, targetId)) { 230 for (TransitionPort childTransition : mTransitions) { 231 if (childTransition.isValidTarget(transitionValues.view, targetId)) { 232 childTransition.captureEndValues(transitionValues); 233 } 234 } 235 } 236 } 237 238 /** @hide */ 239 @RestrictTo(GROUP_ID) 240 @Override 241 public void pause(View sceneRoot) { 242 super.pause(sceneRoot); 243 int numTransitions = mTransitions.size(); 244 for (int i = 0; i < numTransitions; ++i) { 245 mTransitions.get(i).pause(sceneRoot); 246 } 247 } 248 249 /** @hide */ 250 @RestrictTo(GROUP_ID) 251 @Override 252 public void resume(View sceneRoot) { 253 super.resume(sceneRoot); 254 int numTransitions = mTransitions.size(); 255 for (int i = 0; i < numTransitions; ++i) { 256 mTransitions.get(i).resume(sceneRoot); 257 } 258 } 259 260 /** @hide */ 261 @RestrictTo(GROUP_ID) 262 @Override 263 protected void cancel() { 264 super.cancel(); 265 int numTransitions = mTransitions.size(); 266 for (int i = 0; i < numTransitions; ++i) { 267 mTransitions.get(i).cancel(); 268 } 269 } 270 271 @Override 272 TransitionSetPort setSceneRoot(ViewGroup sceneRoot) { 273 super.setSceneRoot(sceneRoot); 274 int numTransitions = mTransitions.size(); 275 for (int i = 0; i < numTransitions; ++i) { 276 mTransitions.get(i).setSceneRoot(sceneRoot); 277 } 278 return (TransitionSetPort) this; 279 } 280 281 @Override 282 void setCanRemoveViews(boolean canRemoveViews) { 283 super.setCanRemoveViews(canRemoveViews); 284 int numTransitions = mTransitions.size(); 285 for (int i = 0; i < numTransitions; ++i) { 286 mTransitions.get(i).setCanRemoveViews(canRemoveViews); 287 } 288 } 289 290 @Override 291 String toString(String indent) { 292 String result = super.toString(indent); 293 for (int i = 0; i < mTransitions.size(); ++i) { 294 result += "\n" + mTransitions.get(i).toString(indent + " "); 295 } 296 return result; 297 } 298 299 @Override 300 public TransitionSetPort clone() { 301 TransitionSetPort clone = (TransitionSetPort) super.clone(); 302 clone.mTransitions = new ArrayList<TransitionPort>(); 303 int numTransitions = mTransitions.size(); 304 for (int i = 0; i < numTransitions; ++i) { 305 clone.addTransition((TransitionPort) mTransitions.get(i).clone()); 306 } 307 return clone; 308 } 309 310 /** 311 * This listener is used to detect when all child transitions are done, at 312 * which point this transition set is also done. 313 */ 314 static class TransitionSetListener extends TransitionListenerAdapter { 315 316 TransitionSetPort mTransitionSet; 317 318 TransitionSetListener(TransitionSetPort transitionSet) { 319 mTransitionSet = transitionSet; 320 } 321 322 @Override 323 public void onTransitionStart(TransitionPort transition) { 324 if (!mTransitionSet.mStarted) { 325 mTransitionSet.start(); 326 mTransitionSet.mStarted = true; 327 } 328 } 329 330 @Override 331 public void onTransitionEnd(TransitionPort transition) { 332 --mTransitionSet.mCurrentListeners; 333 if (mTransitionSet.mCurrentListeners == 0) { 334 // All child trans 335 mTransitionSet.mStarted = false; 336 mTransitionSet.end(); 337 } 338 transition.removeListener(this); 339 } 340 } 341 342} 343