| /* |
| * Copyright (C) 2017 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.transition; |
| |
| import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX; |
| |
| import android.annotation.SuppressLint; |
| import android.graphics.Rect; |
| import android.view.View; |
| import android.view.ViewGroup; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.RestrictTo; |
| import androidx.core.os.CancellationSignal; |
| import androidx.fragment.app.Fragment; |
| import androidx.fragment.app.FragmentTransitionImpl; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| |
| /** |
| * @hide |
| */ |
| // This is instantiated in androidx.fragment.app.FragmentTransition |
| @SuppressWarnings("unused") |
| @RestrictTo(LIBRARY_GROUP_PREFIX) |
| @SuppressLint("RestrictedApi") // remove once fragment lib would be released with the new |
| // LIBRARY_GROUP_PREFIX restriction. tracking in b/127286008 |
| public class FragmentTransitionSupport extends FragmentTransitionImpl { |
| |
| @Override |
| public boolean canHandle(@NonNull Object transition) { |
| return transition instanceof Transition; |
| } |
| |
| @Nullable |
| @Override |
| public Object cloneTransition(@Nullable Object transition) { |
| Transition copy = null; |
| if (transition != null) { |
| copy = ((Transition) transition).clone(); |
| } |
| return copy; |
| } |
| |
| @Nullable |
| @Override |
| public Object wrapTransitionInSet(@Nullable Object transition) { |
| if (transition == null) { |
| return null; |
| } |
| TransitionSet transitionSet = new TransitionSet(); |
| transitionSet.addTransition((Transition) transition); |
| return transitionSet; |
| } |
| |
| @Override |
| public void setSharedElementTargets(@NonNull Object transitionObj, |
| @NonNull View nonExistentView, @NonNull ArrayList<View> sharedViews) { |
| TransitionSet transition = (TransitionSet) transitionObj; |
| final List<View> views = transition.getTargets(); |
| views.clear(); |
| final int count = sharedViews.size(); |
| for (int i = 0; i < count; i++) { |
| final View view = sharedViews.get(i); |
| bfsAddViewChildren(views, view); |
| } |
| views.add(nonExistentView); |
| sharedViews.add(nonExistentView); |
| addTargets(transition, sharedViews); |
| } |
| |
| @Override |
| public void setEpicenter(@NonNull Object transitionObj, @NonNull View view) { |
| if (view != null) { |
| Transition transition = (Transition) transitionObj; |
| final Rect epicenter = new Rect(); |
| getBoundsOnScreen(view, epicenter); |
| |
| transition.setEpicenterCallback(new Transition.EpicenterCallback() { |
| @Override |
| public Rect onGetEpicenter(@NonNull Transition transition) { |
| return epicenter; |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void addTargets(@NonNull Object transitionObj, @NonNull ArrayList<View> views) { |
| Transition transition = (Transition) transitionObj; |
| if (transition == null) { |
| return; |
| } |
| if (transition instanceof TransitionSet) { |
| TransitionSet set = (TransitionSet) transition; |
| int numTransitions = set.getTransitionCount(); |
| for (int i = 0; i < numTransitions; i++) { |
| Transition child = set.getTransitionAt(i); |
| addTargets(child, views); |
| } |
| } else if (!hasSimpleTarget(transition)) { |
| List<View> targets = transition.getTargets(); |
| if (isNullOrEmpty(targets)) { |
| // We can just add the target views |
| int numViews = views.size(); |
| for (int i = 0; i < numViews; i++) { |
| transition.addTarget(views.get(i)); |
| } |
| } |
| } |
| } |
| |
| private static boolean hasSimpleTarget(Transition transition) { |
| return !isNullOrEmpty(transition.getTargetIds()) |
| || !isNullOrEmpty(transition.getTargetNames()) |
| || !isNullOrEmpty(transition.getTargetTypes()); |
| } |
| |
| @NonNull |
| @Override |
| public Object mergeTransitionsTogether(@NonNull Object transition1, |
| @NonNull Object transition2, @Nullable Object transition3) { |
| TransitionSet transitionSet = new TransitionSet(); |
| if (transition1 != null) { |
| transitionSet.addTransition((Transition) transition1); |
| } |
| if (transition2 != null) { |
| transitionSet.addTransition((Transition) transition2); |
| } |
| if (transition3 != null) { |
| transitionSet.addTransition((Transition) transition3); |
| } |
| return transitionSet; |
| } |
| |
| @Override |
| public void scheduleHideFragmentView(@NonNull Object exitTransitionObj, |
| final @NonNull View fragmentView, final @NonNull ArrayList<View> exitingViews) { |
| Transition exitTransition = (Transition) exitTransitionObj; |
| exitTransition.addListener(new Transition.TransitionListener() { |
| @Override |
| public void onTransitionStart(@NonNull Transition transition) { |
| // If any of the exiting views are not shared elements, the TransitionManager |
| // adds additional listeners to the this transition. If those listeners are |
| // DisappearListeners for a view that is going away, they can change the state of |
| // views after our onTransitionEnd callback. |
| // We need to make sure this listener gets the onTransitionEnd callback last to |
| // ensure that exiting views are made visible once the Transition is complete. |
| transition.removeListener(this); |
| transition.addListener(this); |
| } |
| |
| @Override |
| public void onTransitionEnd(@NonNull Transition transition) { |
| transition.removeListener(this); |
| fragmentView.setVisibility(View.GONE); |
| final int numViews = exitingViews.size(); |
| for (int i = 0; i < numViews; i++) { |
| exitingViews.get(i).setVisibility(View.VISIBLE); |
| } |
| } |
| |
| @Override |
| public void onTransitionCancel(@NonNull Transition transition) { |
| } |
| |
| @Override |
| public void onTransitionPause(@NonNull Transition transition) { |
| } |
| |
| @Override |
| public void onTransitionResume(@NonNull Transition transition) { |
| } |
| }); |
| } |
| |
| @Nullable |
| @Override |
| public Object mergeTransitionsInSequence(@Nullable Object exitTransitionObj, |
| @Nullable Object enterTransitionObj, @Nullable Object sharedElementTransitionObj) { |
| // First do exit, then enter, but allow shared element transition to happen |
| // during both. |
| Transition staggered = null; |
| final Transition exitTransition = (Transition) exitTransitionObj; |
| final Transition enterTransition = (Transition) enterTransitionObj; |
| final Transition sharedElementTransition = (Transition) sharedElementTransitionObj; |
| if (exitTransition != null && enterTransition != null) { |
| staggered = new TransitionSet() |
| .addTransition(exitTransition) |
| .addTransition(enterTransition) |
| .setOrdering(TransitionSet.ORDERING_SEQUENTIAL); |
| } else if (exitTransition != null) { |
| staggered = exitTransition; |
| } else if (enterTransition != null) { |
| staggered = enterTransition; |
| } |
| if (sharedElementTransition != null) { |
| TransitionSet together = new TransitionSet(); |
| if (staggered != null) { |
| together.addTransition(staggered); |
| } |
| together.addTransition(sharedElementTransition); |
| return together; |
| } else { |
| return staggered; |
| } |
| } |
| |
| @Override |
| public void beginDelayedTransition(@NonNull ViewGroup sceneRoot, @Nullable Object transition) { |
| TransitionManager.beginDelayedTransition(sceneRoot, (Transition) transition); |
| } |
| |
| @Override |
| public void scheduleRemoveTargets(final @NonNull Object overallTransitionObj, |
| final @Nullable Object enterTransition, final @Nullable ArrayList<View> enteringViews, |
| final @Nullable Object exitTransition, final @Nullable ArrayList<View> exitingViews, |
| final @Nullable Object sharedElementTransition, |
| final @Nullable ArrayList<View> sharedElementsIn) { |
| final Transition overallTransition = (Transition) overallTransitionObj; |
| overallTransition.addListener(new TransitionListenerAdapter() { |
| @Override |
| public void onTransitionStart(@NonNull Transition transition) { |
| if (enterTransition != null) { |
| replaceTargets(enterTransition, enteringViews, null); |
| } |
| if (exitTransition != null) { |
| replaceTargets(exitTransition, exitingViews, null); |
| } |
| if (sharedElementTransition != null) { |
| replaceTargets(sharedElementTransition, sharedElementsIn, null); |
| } |
| } |
| |
| @Override |
| public void onTransitionEnd(@NonNull Transition transition) { |
| transition.removeListener(this); |
| } |
| }); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * If either exitingViews or SharedElementsOut contain a view, an |
| * {@link Transition.TransitionListener#onTransitionEnd} listener is added that calls |
| * {@link Runnable#run()} once the Transition ends. |
| * |
| * If {@link CancellationSignal#cancel()} is called on the given signal, the transition calls |
| * {@link Transition#cancel()}. |
| */ |
| @Override |
| public void setListenerForTransitionEnd(@NonNull final Fragment outFragment, |
| @NonNull final Object transition, @NonNull final CancellationSignal signal, |
| @NonNull final Runnable transitionCompleteRunnable) { |
| final Transition realTransition = ((Transition) transition); |
| signal.setOnCancelListener(new CancellationSignal.OnCancelListener() { |
| @Override |
| public void onCancel() { |
| realTransition.cancel(); |
| } |
| }); |
| realTransition.addListener(new Transition.TransitionListener() { |
| @Override |
| public void onTransitionStart(@NonNull Transition transition) { } |
| |
| @Override |
| public void onTransitionEnd(@NonNull Transition transition) { |
| transitionCompleteRunnable.run(); |
| } |
| |
| @Override |
| public void onTransitionCancel(@NonNull Transition transition) { } |
| |
| @Override |
| public void onTransitionPause(@NonNull Transition transition) { } |
| |
| @Override |
| public void onTransitionResume(@NonNull Transition transition) { } |
| }); |
| } |
| |
| @Override |
| public void swapSharedElementTargets(@Nullable Object sharedElementTransitionObj, |
| @Nullable ArrayList<View> sharedElementsOut, |
| @Nullable ArrayList<View> sharedElementsIn) { |
| TransitionSet sharedElementTransition = (TransitionSet) sharedElementTransitionObj; |
| if (sharedElementTransition != null) { |
| sharedElementTransition.getTargets().clear(); |
| sharedElementTransition.getTargets().addAll(sharedElementsIn); |
| replaceTargets(sharedElementTransition, sharedElementsOut, sharedElementsIn); |
| } |
| } |
| |
| @Override |
| public void replaceTargets(@NonNull Object transitionObj, |
| @SuppressLint("UnknownNullness") ArrayList<View> oldTargets, |
| @SuppressLint("UnknownNullness") ArrayList<View> newTargets) { |
| Transition transition = (Transition) transitionObj; |
| if (transition instanceof TransitionSet) { |
| TransitionSet set = (TransitionSet) transition; |
| int numTransitions = set.getTransitionCount(); |
| for (int i = 0; i < numTransitions; i++) { |
| Transition child = set.getTransitionAt(i); |
| replaceTargets(child, oldTargets, newTargets); |
| } |
| } else if (!hasSimpleTarget(transition)) { |
| List<View> targets = transition.getTargets(); |
| if (targets.size() == oldTargets.size() |
| && targets.containsAll(oldTargets)) { |
| // We have an exact match. We must have added these earlier in addTargets |
| final int targetCount = newTargets == null ? 0 : newTargets.size(); |
| for (int i = 0; i < targetCount; i++) { |
| transition.addTarget(newTargets.get(i)); |
| } |
| for (int i = oldTargets.size() - 1; i >= 0; i--) { |
| transition.removeTarget(oldTargets.get(i)); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void addTarget(@NonNull Object transitionObj, @NonNull View view) { |
| if (transitionObj != null) { |
| Transition transition = (Transition) transitionObj; |
| transition.addTarget(view); |
| } |
| } |
| |
| @Override |
| public void removeTarget(@NonNull Object transitionObj, @NonNull View view) { |
| if (transitionObj != null) { |
| Transition transition = (Transition) transitionObj; |
| transition.removeTarget(view); |
| } |
| } |
| |
| @Override |
| public void setEpicenter(@NonNull Object transitionObj, final @NonNull Rect epicenter) { |
| if (transitionObj != null) { |
| Transition transition = (Transition) transitionObj; |
| transition.setEpicenterCallback(new Transition.EpicenterCallback() { |
| @Override |
| public Rect onGetEpicenter(@NonNull Transition transition) { |
| if (epicenter == null || epicenter.isEmpty()) { |
| return null; |
| } |
| return epicenter; |
| } |
| }); |
| } |
| } |
| |
| } |