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