ViewTransitionController.java
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.constraintlayout.motion.widget;
import android.graphics.Rect;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
import androidx.constraintlayout.widget.SharedValues;
import java.util.ArrayList;
import java.util.HashSet;
/**
* Container for ViewTransitions. It dispatches the run of a ViewTransition.
* It receives animate calls
*/
public class ViewTransitionController {
private final MotionLayout mMotionLayout;
private ArrayList<ViewTransition> mViewTransitions = new ArrayList<>();
private HashSet<View> mRelatedViews;
private String mTAG = "ViewTransitionController";
public ViewTransitionController(MotionLayout layout) {
mMotionLayout = layout;
}
/**
* Add a ViewTransition
* @param viewTransition
*/
public void add(ViewTransition viewTransition) {
mViewTransitions.add(viewTransition);
mRelatedViews = null;
if (viewTransition.getStateTransition() == ViewTransition.ONSTATE_SHARED_VALUE_SET) {
listenForSharedVariable(viewTransition, true);
} else if (viewTransition.getStateTransition()
== ViewTransition.ONSTATE_SHARED_VALUE_UNSET) {
listenForSharedVariable(viewTransition, false);
}
}
void remove(int id) {
ViewTransition del = null;
for (ViewTransition viewTransition : mViewTransitions) {
if (viewTransition.getId() == id) {
del = viewTransition;
break;
}
}
if (del != null) {
mRelatedViews = null;
mViewTransitions.remove(del);
}
}
private void viewTransition(ViewTransition vt, View... view) {
int currentId = mMotionLayout.getCurrentState();
if (vt.mViewTransitionMode != ViewTransition.VIEWTRANSITIONMODE_NOSTATE) {
if (currentId == -1) {
Log.w(mTAG, "No support for ViewTransition within transition yet. Currently: "
+ mMotionLayout.toString());
return;
}
ConstraintSet current = mMotionLayout.getConstraintSet(currentId);
if (current == null) {
return;
}
vt.applyTransition(this, mMotionLayout, currentId, current, view);
} else {
vt.applyTransition(this, mMotionLayout, currentId, null, view);
}
}
void enableViewTransition(int id, boolean enable) {
for (ViewTransition viewTransition : mViewTransitions) {
if (viewTransition.getId() == id) {
viewTransition.setEnabled(enable);
break;
}
}
}
boolean isViewTransitionEnabled(int id) {
for (ViewTransition viewTransition : mViewTransitions) {
if (viewTransition.getId() == id) {
return viewTransition.isEnabled();
}
}
return false;
}
/**
* Support call from MotionLayout.viewTransition
*
* @param id the id of a ViewTransition
* @param views the list of views to transition simultaneously
*/
void viewTransition(int id, View... views) {
ViewTransition vt = null;
ArrayList<View> list = new ArrayList<>();
for (ViewTransition viewTransition : mViewTransitions) {
if (viewTransition.getId() == id) {
vt = viewTransition;
for (View view : views) {
if (viewTransition.checkTags(view)) {
list.add(view);
}
}
if (!list.isEmpty()) {
viewTransition(vt, list.toArray(new View[0]));
list.clear();
}
}
}
if (vt == null) {
Log.e(mTAG, " Could not find ViewTransition");
return;
}
}
/**
* this gets Touch events on the MotionLayout and can fire transitions on down or up
*
* @param event
*/
void touchEvent(MotionEvent event) {
int currentId = mMotionLayout.getCurrentState();
if (currentId == -1) {
return;
}
if (mRelatedViews == null) {
mRelatedViews = new HashSet<>();
for (ViewTransition viewTransition : mViewTransitions) {
int count = mMotionLayout.getChildCount();
for (int i = 0; i < count; i++) {
View view = mMotionLayout.getChildAt(i);
if (viewTransition.matchesView(view)) {
@SuppressWarnings("unused") int id = view.getId();
mRelatedViews.add(view);
}
}
}
}
float x = event.getX();
float y = event.getY();
Rect rec = new Rect();
int action = event.getAction();
if (mAnimations != null && !mAnimations.isEmpty()) {
for (ViewTransition.Animate animation : mAnimations) {
animation.reactTo(action, x, y);
}
}
switch (action) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_DOWN:
ConstraintSet current = mMotionLayout.getConstraintSet(currentId);
for (ViewTransition viewTransition : mViewTransitions) {
if (viewTransition.supports(action)) {
for (View view : mRelatedViews) {
if (!viewTransition.matchesView(view)) {
continue;
}
view.getHitRect(rec);
if (rec.contains((int) x, (int) y)) {
viewTransition.applyTransition(this, mMotionLayout,
currentId, current, view);
}
}
}
}
break;
}
}
ArrayList<ViewTransition.Animate> mAnimations;
ArrayList<ViewTransition.Animate> mRemoveList = new ArrayList<>();
void addAnimation(ViewTransition.Animate animation) {
if (mAnimations == null) {
mAnimations = new ArrayList<>();
}
mAnimations.add(animation);
}
void removeAnimation(ViewTransition.Animate animation) {
mRemoveList.add(animation);
}
/**
* Called by motionLayout during draw to allow ViewTransitions to asynchronously animate
*/
void animate() {
if (mAnimations == null) {
return;
}
for (ViewTransition.Animate animation : mAnimations) {
animation.mutate();
}
mAnimations.removeAll(mRemoveList);
mRemoveList.clear();
if (mAnimations.isEmpty()) {
mAnimations = null;
}
}
void invalidate() {
mMotionLayout.invalidate();
}
boolean applyViewTransition(int viewTransitionId, MotionController motionController) {
for (ViewTransition viewTransition : mViewTransitions) {
if (viewTransition.getId() == viewTransitionId) {
viewTransition.mKeyFrames.addAllFrames(motionController);
return true;
}
}
return false;
}
private void listenForSharedVariable(ViewTransition viewTransition, boolean isSet) {
int listen_for_id = viewTransition.getSharedValueID();
int listen_for_value = viewTransition.getSharedValue();
ConstraintLayout.getSharedValues().addListener(viewTransition.getSharedValueID(),
new SharedValues.SharedValuesListener() {
@Override
public void onNewValue(int id, int value, int oldValue) {
int current_value = viewTransition.getSharedValueCurrent();
viewTransition.setSharedValueCurrent(value);
if (listen_for_id == id && current_value != value) {
if (isSet) {
if (listen_for_value == value) {
int count = mMotionLayout.getChildCount();
for (int i = 0; i < count; i++) {
View view = mMotionLayout.getChildAt(i);
if (viewTransition.matchesView(view)) {
int currentId = mMotionLayout.getCurrentState();
ConstraintSet current =
mMotionLayout.getConstraintSet(currentId);
viewTransition.applyTransition(
ViewTransitionController.this, mMotionLayout,
currentId, current, view);
}
}
}
} else { // not set
if (listen_for_value != value) {
int count = mMotionLayout.getChildCount();
for (int i = 0; i < count; i++) {
View view = mMotionLayout.getChildAt(i);
if (viewTransition.matchesView(view)) {
int currentId = mMotionLayout.getCurrentState();
ConstraintSet current =
mMotionLayout.getConstraintSet(currentId);
viewTransition.applyTransition(
ViewTransitionController.this, mMotionLayout,
currentId, current, view);
}
}
}
}
}
}
});
}
}