1 /* 2 * Copyright (C) 2020 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 androidx.constraintlayout.motion.widget; 18 19 import android.graphics.Rect; 20 import android.util.Log; 21 import android.view.MotionEvent; 22 import android.view.View; 23 24 import androidx.constraintlayout.widget.ConstraintLayout; 25 import androidx.constraintlayout.widget.ConstraintSet; 26 import androidx.constraintlayout.widget.SharedValues; 27 28 import java.util.ArrayList; 29 import java.util.HashSet; 30 31 /** 32 * Container for ViewTransitions. It dispatches the run of a ViewTransition. 33 * It receives animate calls 34 */ 35 public class ViewTransitionController { 36 private final MotionLayout mMotionLayout; 37 private ArrayList<ViewTransition> mViewTransitions = new ArrayList<>(); 38 private HashSet<View> mRelatedViews; 39 private String mTAG = "ViewTransitionController"; 40 ViewTransitionController(MotionLayout layout)41 public ViewTransitionController(MotionLayout layout) { 42 mMotionLayout = layout; 43 } 44 45 /** 46 * Add a ViewTransition 47 * @param viewTransition 48 */ add(ViewTransition viewTransition)49 public void add(ViewTransition viewTransition) { 50 mViewTransitions.add(viewTransition); 51 mRelatedViews = null; 52 53 if (viewTransition.getStateTransition() == ViewTransition.ONSTATE_SHARED_VALUE_SET) { 54 listenForSharedVariable(viewTransition, true); 55 } else if (viewTransition.getStateTransition() 56 == ViewTransition.ONSTATE_SHARED_VALUE_UNSET) { 57 listenForSharedVariable(viewTransition, false); 58 } 59 } 60 remove(int id)61 void remove(int id) { 62 ViewTransition del = null; 63 for (ViewTransition viewTransition : mViewTransitions) { 64 if (viewTransition.getId() == id) { 65 del = viewTransition; 66 break; 67 } 68 } 69 if (del != null) { 70 mRelatedViews = null; 71 mViewTransitions.remove(del); 72 } 73 } 74 viewTransition(ViewTransition vt, View... view)75 private void viewTransition(ViewTransition vt, View... view) { 76 int currentId = mMotionLayout.getCurrentState(); 77 if (vt.mViewTransitionMode != ViewTransition.VIEWTRANSITIONMODE_NOSTATE) { 78 if (currentId == -1) { 79 Log.w(mTAG, "No support for ViewTransition within transition yet. Currently: " 80 + mMotionLayout.toString()); 81 return; 82 } 83 ConstraintSet current = mMotionLayout.getConstraintSet(currentId); 84 if (current == null) { 85 return; 86 } 87 vt.applyTransition(this, mMotionLayout, currentId, current, view); 88 } else { 89 vt.applyTransition(this, mMotionLayout, currentId, null, view); 90 } 91 } 92 enableViewTransition(int id, boolean enable)93 void enableViewTransition(int id, boolean enable) { 94 for (ViewTransition viewTransition : mViewTransitions) { 95 if (viewTransition.getId() == id) { 96 viewTransition.setEnabled(enable); 97 break; 98 } 99 } 100 } 101 isViewTransitionEnabled(int id)102 boolean isViewTransitionEnabled(int id) { 103 for (ViewTransition viewTransition : mViewTransitions) { 104 if (viewTransition.getId() == id) { 105 return viewTransition.isEnabled(); 106 } 107 } 108 return false; 109 } 110 111 /** 112 * Support call from MotionLayout.viewTransition 113 * 114 * @param id the id of a ViewTransition 115 * @param views the list of views to transition simultaneously 116 */ viewTransition(int id, View... views)117 void viewTransition(int id, View... views) { 118 ViewTransition vt = null; 119 ArrayList<View> list = new ArrayList<>(); 120 for (ViewTransition viewTransition : mViewTransitions) { 121 if (viewTransition.getId() == id) { 122 vt = viewTransition; 123 for (View view : views) { 124 if (viewTransition.checkTags(view)) { 125 list.add(view); 126 } 127 } 128 if (!list.isEmpty()) { 129 viewTransition(vt, list.toArray(new View[0])); 130 list.clear(); 131 } 132 } 133 } 134 if (vt == null) { 135 Log.e(mTAG, " Could not find ViewTransition"); 136 return; 137 } 138 } 139 140 /** 141 * this gets Touch events on the MotionLayout and can fire transitions on down or up 142 * 143 * @param event 144 */ touchEvent(MotionEvent event)145 void touchEvent(MotionEvent event) { 146 int currentId = mMotionLayout.getCurrentState(); 147 if (currentId == -1) { 148 return; 149 } 150 if (mRelatedViews == null) { 151 mRelatedViews = new HashSet<>(); 152 for (ViewTransition viewTransition : mViewTransitions) { 153 int count = mMotionLayout.getChildCount(); 154 for (int i = 0; i < count; i++) { 155 View view = mMotionLayout.getChildAt(i); 156 if (viewTransition.matchesView(view)) { 157 @SuppressWarnings("unused") int id = view.getId(); 158 mRelatedViews.add(view); 159 } 160 } 161 } 162 } 163 164 float x = event.getX(); 165 float y = event.getY(); 166 Rect rec = new Rect(); 167 int action = event.getAction(); 168 if (mAnimations != null && !mAnimations.isEmpty()) { 169 for (ViewTransition.Animate animation : mAnimations) { 170 animation.reactTo(action, x, y); 171 } 172 } 173 switch (action) { 174 case MotionEvent.ACTION_UP: 175 case MotionEvent.ACTION_DOWN: 176 177 ConstraintSet current = mMotionLayout.getConstraintSet(currentId); 178 for (ViewTransition viewTransition : mViewTransitions) { 179 if (viewTransition.supports(action)) { 180 for (View view : mRelatedViews) { 181 if (!viewTransition.matchesView(view)) { 182 continue; 183 } 184 view.getHitRect(rec); 185 if (rec.contains((int) x, (int) y)) { 186 viewTransition.applyTransition(this, mMotionLayout, 187 currentId, current, view); 188 } 189 190 } 191 } 192 } 193 break; 194 } 195 } 196 197 ArrayList<ViewTransition.Animate> mAnimations; 198 ArrayList<ViewTransition.Animate> mRemoveList = new ArrayList<>(); 199 addAnimation(ViewTransition.Animate animation)200 void addAnimation(ViewTransition.Animate animation) { 201 if (mAnimations == null) { 202 mAnimations = new ArrayList<>(); 203 } 204 mAnimations.add(animation); 205 } 206 removeAnimation(ViewTransition.Animate animation)207 void removeAnimation(ViewTransition.Animate animation) { 208 mRemoveList.add(animation); 209 } 210 211 /** 212 * Called by motionLayout during draw to allow ViewTransitions to asynchronously animate 213 */ animate()214 void animate() { 215 if (mAnimations == null) { 216 return; 217 } 218 for (ViewTransition.Animate animation : mAnimations) { 219 animation.mutate(); 220 } 221 mAnimations.removeAll(mRemoveList); 222 mRemoveList.clear(); 223 if (mAnimations.isEmpty()) { 224 mAnimations = null; 225 } 226 } 227 invalidate()228 void invalidate() { 229 mMotionLayout.invalidate(); 230 } 231 applyViewTransition(int viewTransitionId, MotionController motionController)232 boolean applyViewTransition(int viewTransitionId, MotionController motionController) { 233 for (ViewTransition viewTransition : mViewTransitions) { 234 if (viewTransition.getId() == viewTransitionId) { 235 viewTransition.mKeyFrames.addAllFrames(motionController); 236 return true; 237 } 238 } 239 return false; 240 } 241 listenForSharedVariable(ViewTransition viewTransition, boolean isSet)242 private void listenForSharedVariable(ViewTransition viewTransition, boolean isSet) { 243 int listen_for_id = viewTransition.getSharedValueID(); 244 int listen_for_value = viewTransition.getSharedValue(); 245 246 ConstraintLayout.getSharedValues().addListener(viewTransition.getSharedValueID(), 247 new SharedValues.SharedValuesListener() { 248 @Override 249 public void onNewValue(int id, int value, int oldValue) { 250 int current_value = viewTransition.getSharedValueCurrent(); 251 viewTransition.setSharedValueCurrent(value); 252 if (listen_for_id == id && current_value != value) { 253 if (isSet) { 254 if (listen_for_value == value) { 255 int count = mMotionLayout.getChildCount(); 256 257 for (int i = 0; i < count; i++) { 258 View view = mMotionLayout.getChildAt(i); 259 if (viewTransition.matchesView(view)) { 260 int currentId = mMotionLayout.getCurrentState(); 261 ConstraintSet current = 262 mMotionLayout.getConstraintSet(currentId); 263 viewTransition.applyTransition( 264 ViewTransitionController.this, mMotionLayout, 265 currentId, current, view); 266 } 267 } 268 } 269 } else { // not set 270 if (listen_for_value != value) { 271 int count = mMotionLayout.getChildCount(); 272 for (int i = 0; i < count; i++) { 273 View view = mMotionLayout.getChildAt(i); 274 if (viewTransition.matchesView(view)) { 275 int currentId = mMotionLayout.getCurrentState(); 276 ConstraintSet current = 277 mMotionLayout.getConstraintSet(currentId); 278 viewTransition.applyTransition( 279 ViewTransitionController.this, mMotionLayout, 280 currentId, current, view); 281 } 282 } 283 } 284 } 285 } 286 } 287 }); 288 } 289 290 } 291