diff options
7 files changed, 220 insertions, 165 deletions
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index ec21882f431e..af41db01418e 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -37,6 +37,8 @@ import android.view.ViewTreeObserver; import android.view.Window; import android.widget.ImageView; +import com.android.internal.view.OneShotPreDrawListener; + import java.util.ArrayList; import java.util.Collection; @@ -570,16 +572,9 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { protected void scheduleSetSharedElementEnd(final ArrayList<View> snapshots) { final View decorView = getDecor(); if (decorView != null) { - decorView.getViewTreeObserver().addOnPreDrawListener( - new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - decorView.getViewTreeObserver().removeOnPreDrawListener(this); - notifySharedElementEnd(snapshots); - return true; - } - } - ); + OneShotPreDrawListener.add(decorView, () -> { + notifySharedElementEnd(snapshots); + }); } } @@ -816,6 +811,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { if (moveWithParent && !isInTransitionGroup(parent, decor)) { GhostViewListeners listener = new GhostViewListeners(view, parent, decor); parent.getViewTreeObserver().addOnPreDrawListener(listener); + parent.addOnAttachStateChangeListener(listener); mGhostViewListeners.add(listener); } } @@ -842,8 +838,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { int numListeners = mGhostViewListeners.size(); for (int i = 0; i < numListeners; i++) { GhostViewListeners listener = mGhostViewListeners.get(i); - ViewGroup parent = (ViewGroup) listener.getView().getParent(); - parent.getViewTreeObserver().removeOnPreDrawListener(listener); + listener.removeListener(); } mGhostViewListeners.clear(); @@ -874,15 +869,9 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { protected void scheduleGhostVisibilityChange(final int visibility) { final View decorView = getDecor(); if (decorView != null) { - decorView.getViewTreeObserver() - .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - decorView.getViewTreeObserver().removeOnPreDrawListener(this); - setGhostVisibility(visibility); - return true; - } - }); + OneShotPreDrawListener.add(decorView, () -> { + setGhostVisibility(visibility); + }); } } @@ -988,16 +977,19 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { } } - private static class GhostViewListeners implements ViewTreeObserver.OnPreDrawListener { + private static class GhostViewListeners implements ViewTreeObserver.OnPreDrawListener, + View.OnAttachStateChangeListener { private View mView; private ViewGroup mDecor; private View mParent; private Matrix mMatrix = new Matrix(); + private ViewTreeObserver mViewTreeObserver; public GhostViewListeners(View view, View parent, ViewGroup decor) { mView = view; mParent = parent; mDecor = decor; + mViewTreeObserver = parent.getViewTreeObserver(); } public View getView() { @@ -1008,13 +1000,32 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { public boolean onPreDraw() { GhostView ghostView = GhostView.getGhost(mView); if (ghostView == null) { - mParent.getViewTreeObserver().removeOnPreDrawListener(this); + removeListener(); } else { GhostView.calculateMatrix(mView, mDecor, mMatrix); ghostView.setMatrix(mMatrix); } return true; } + + public void removeListener() { + if (mViewTreeObserver.isAlive()) { + mViewTreeObserver.removeOnPreDrawListener(this); + } else { + mParent.getViewTreeObserver().removeOnPreDrawListener(this); + } + mParent.removeOnAttachStateChangeListener(this); + } + + @Override + public void onViewAttachedToWindow(View v) { + mViewTreeObserver = v.getViewTreeObserver(); + } + + @Override + public void onViewDetachedFromWindow(View v) { + removeListener(); + } } static class SharedElementOriginalState { diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java index 60046b5932ee..f2616ffa2465 100644 --- a/core/java/android/app/ActivityTransitionState.java +++ b/core/java/android/app/ActivityTransitionState.java @@ -22,9 +22,10 @@ import android.transition.Transition; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver; import android.view.Window; +import com.android.internal.view.OneShotPreDrawListener; + import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -321,18 +322,12 @@ class ActivityTransitionState { } if (delayExitBack && decor != null) { final ViewGroup finalDecor = decor; - decor.getViewTreeObserver().addOnPreDrawListener( - new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - finalDecor.getViewTreeObserver().removeOnPreDrawListener(this); - if (mReturnExitCoordinator != null) { - mReturnExitCoordinator.startExit(activity.mResultCode, - activity.mResultData); - } - return true; - } - }); + OneShotPreDrawListener.add(decor, () -> { + if (mReturnExitCoordinator != null) { + mReturnExitCoordinator.startExit(activity.mResultCode, + activity.mResultData); + } + }); } else { mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData); } diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index 27a02003669d..3464c4d8cbeb 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -30,10 +30,11 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewGroupOverlay; import android.view.ViewTreeObserver; -import android.view.ViewTreeObserver.OnPreDrawListener; import android.view.Window; import android.view.accessibility.AccessibilityEvent; +import com.android.internal.view.OneShotPreDrawListener; + import java.util.ArrayList; /** @@ -58,7 +59,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { private boolean mAreViewsReady; private boolean mIsViewsTransitionStarted; private Transition mEnterViewsTransition; - private OnPreDrawListener mViewsReadyListener; + private OneShotPreDrawListener mViewsReadyListener; private final boolean mIsCrossTask; public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver, @@ -74,12 +75,17 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle); final View decorView = getDecor(); if (decorView != null) { - decorView.getViewTreeObserver().addOnPreDrawListener( + final ViewTreeObserver viewTreeObserver = decorView.getViewTreeObserver(); + viewTreeObserver.addOnPreDrawListener( new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { if (mIsReadyForTransition) { - decorView.getViewTreeObserver().removeOnPreDrawListener(this); + if (viewTreeObserver.isAlive()) { + viewTreeObserver.removeOnPreDrawListener(this); + } else { + decorView.getViewTreeObserver().removeOnPreDrawListener(this); + } } return mIsReadyForTransition; } @@ -147,16 +153,10 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { (sharedElements.isEmpty() || !sharedElements.valueAt(0).isLayoutRequested()))) { viewsReady(sharedElements); } else { - mViewsReadyListener = new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - mViewsReadyListener = null; - decor.getViewTreeObserver().removeOnPreDrawListener(this); - viewsReady(sharedElements); - return true; - } - }; - decor.getViewTreeObserver().addOnPreDrawListener(mViewsReadyListener); + mViewsReadyListener = OneShotPreDrawListener.add(decor, () -> { + mViewsReadyListener = null; + viewsReady(sharedElements); + }); decor.invalidate(); } } @@ -206,19 +206,13 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { moveSharedElementsToOverlay(); mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); } else if (decorView != null) { - decorView.getViewTreeObserver() - .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - decorView.getViewTreeObserver().removeOnPreDrawListener(this); - if (mResultReceiver != null) { - Bundle state = captureSharedElementState(); - moveSharedElementsToOverlay(); - mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); - } - return true; - } - }); + OneShotPreDrawListener.add(decorView, () -> { + if (mResultReceiver != null) { + Bundle state = captureSharedElementState(); + moveSharedElementsToOverlay(); + mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); + } + }); } if (allowOverlappingTransitions()) { startEnterTransitionOnly(); @@ -271,7 +265,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { mIsReadyForTransition = true; final ViewGroup decor = getDecor(); if (decor != null && mViewsReadyListener != null) { - decor.getViewTreeObserver().removeOnPreDrawListener(mViewsReadyListener); + mViewsReadyListener.removeListener(); mViewsReadyListener = null; } showViews(mTransitioningViews, true); @@ -457,20 +451,11 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { public void onSharedElementsReady() { final View decorView = getDecor(); if (decorView != null) { - decorView.getViewTreeObserver() - .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - decorView.getViewTreeObserver().removeOnPreDrawListener(this); - startTransition(new Runnable() { - @Override - public void run() { - startSharedElementTransition(sharedElementState); - } - }); - return false; - } - }); + OneShotPreDrawListener.add(decorView, () -> { + startTransition(() -> { + startSharedElementTransition(sharedElementState); + }); + }); decorView.invalidate(); } } diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index b5b6e4aa1892..6a79e6cfdf9c 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -34,9 +34,10 @@ import android.transition.Transition; import android.transition.TransitionManager; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver; import android.view.Window; +import com.android.internal.view.OneShotPreDrawListener; + import java.util.ArrayList; /** @@ -168,15 +169,9 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { }); final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle, mSharedElementNames); - decorView.getViewTreeObserver() - .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - decorView.getViewTreeObserver().removeOnPreDrawListener(this); - setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots); - return true; - } - }); + OneShotPreDrawListener.add(decorView, () -> { + setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots); + }); setGhostVisibility(View.INVISIBLE); scheduleGhostVisibilityChange(View.INVISIBLE); if (mListener != null) { diff --git a/core/java/android/app/FragmentTransition.java b/core/java/android/app/FragmentTransition.java index d27dff5e2227..088fd08d97a8 100644 --- a/core/java/android/app/FragmentTransition.java +++ b/core/java/android/app/FragmentTransition.java @@ -24,7 +24,8 @@ import android.util.ArrayMap; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver; + +import com.android.internal.view.OneShotPreDrawListener; import java.util.ArrayList; import java.util.Collection; @@ -320,16 +321,9 @@ class FragmentTransition { && exitingFragment.mHidden && exitingFragment.mHiddenChanged) { exitingFragment.setHideReplaced(true); final View fragmentView = exitingFragment.getView(); - final ViewGroup container = exitingFragment.mContainer; - container.getViewTreeObserver().addOnPreDrawListener( - new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - container.getViewTreeObserver().removeOnPreDrawListener(this); - setViewVisibility(exitingViews, View.INVISIBLE); - return true; - } - }); + OneShotPreDrawListener.add(exitingFragment.mContainer, () -> { + setViewVisibility(exitingViews, View.INVISIBLE); + }); exitTransition.addListener(new Transition.TransitionListenerAdapter() { @Override public void onTransitionEnd(Transition transition) { @@ -364,30 +358,22 @@ class FragmentTransition { final Transition enterTransition, final ArrayList<View> enteringViews, final Transition exitTransition, final ArrayList<View> exitingViews) { - sceneRoot.getViewTreeObserver().addOnPreDrawListener( - new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this); - - if (enterTransition != null) { - enterTransition.removeTarget(nonExistentView); - ArrayList<View> views = configureEnteringExitingViews( - enterTransition, inFragment, sharedElementsIn, nonExistentView); - enteringViews.addAll(views); - } - - if (exitingViews != null) { - ArrayList<View> tempExiting = new ArrayList<>(); - tempExiting.add(nonExistentView); - replaceTargets(exitTransition, exitingViews, tempExiting); - exitingViews.clear(); - exitingViews.add(nonExistentView); - } + OneShotPreDrawListener.add(sceneRoot, () -> { + if (enterTransition != null) { + enterTransition.removeTarget(nonExistentView); + ArrayList<View> views = configureEnteringExitingViews( + enterTransition, inFragment, sharedElementsIn, nonExistentView); + enteringViews.addAll(views); + } - return true; - } - }); + if (exitingViews != null) { + ArrayList<View> tempExiting = new ArrayList<>(); + tempExiting.add(nonExistentView); + replaceTargets(exitTransition, exitingViews, tempExiting); + exitingViews.clear(); + exitingViews.add(nonExistentView); + } + }); } /** @@ -541,19 +527,13 @@ class FragmentTransition { epicenterView = null; } - sceneRoot.getViewTreeObserver().addOnPreDrawListener( - new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this); - callSharedElementStartEnd(inFragment, outFragment, inIsPop, - inSharedElements, false); - if (epicenterView != null) { - epicenterView.getBoundsOnScreen(epicenter); - } - return true; - } - }); + OneShotPreDrawListener.add(sceneRoot, () -> { + callSharedElementStartEnd(inFragment, outFragment, inIsPop, + inSharedElements, false); + if (epicenterView != null) { + epicenterView.getBoundsOnScreen(epicenter); + } + }); return sharedElementTransition; } @@ -643,36 +623,30 @@ class FragmentTransition { TransitionSet finalSharedElementTransition = sharedElementTransition; - sceneRoot.getViewTreeObserver().addOnPreDrawListener( - new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this); - ArrayMap<String, View> inSharedElements = captureInSharedElements( - nameOverrides, finalSharedElementTransition, fragments); - - if (inSharedElements != null) { - sharedElementsIn.addAll(inSharedElements.values()); - sharedElementsIn.add(nonExistentView); - } + OneShotPreDrawListener.add(sceneRoot, () -> { + ArrayMap<String, View> inSharedElements = captureInSharedElements( + nameOverrides, finalSharedElementTransition, fragments); - callSharedElementStartEnd(inFragment, outFragment, inIsPop, - inSharedElements, false); - if (finalSharedElementTransition != null) { - finalSharedElementTransition.getTargets().clear(); - finalSharedElementTransition.getTargets().addAll(sharedElementsIn); - replaceTargets(finalSharedElementTransition, sharedElementsOut, - sharedElementsIn); - - final View inEpicenterView = getInEpicenterView(inSharedElements, - fragments, enterTransition, inIsPop); - if (inEpicenterView != null) { - inEpicenterView.getBoundsOnScreen(inEpicenter); - } - } - return true; - } - }); + if (inSharedElements != null) { + sharedElementsIn.addAll(inSharedElements.values()); + sharedElementsIn.add(nonExistentView); + } + + callSharedElementStartEnd(inFragment, outFragment, inIsPop, + inSharedElements, false); + if (finalSharedElementTransition != null) { + finalSharedElementTransition.getTargets().clear(); + finalSharedElementTransition.getTargets().addAll(sharedElementsIn); + replaceTargets(finalSharedElementTransition, sharedElementsOut, + sharedElementsIn); + + final View inEpicenterView = getInEpicenterView(inSharedElements, + fragments, enterTransition, inIsPop); + if (inEpicenterView != null) { + inEpicenterView.getBoundsOnScreen(inEpicenter); + } + } + }); return sharedElementTransition; } diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java index 479f49368191..6e4d78ded4f2 100644 --- a/core/java/android/transition/TransitionManager.java +++ b/core/java/android/transition/TransitionManager.java @@ -242,14 +242,20 @@ public class TransitionManager { Transition mTransition; ViewGroup mSceneRoot; + final ViewTreeObserver mViewTreeObserver; MultiListener(Transition transition, ViewGroup sceneRoot) { mTransition = transition; mSceneRoot = sceneRoot; + mViewTreeObserver = mSceneRoot.getViewTreeObserver(); } private void removeListeners() { - mSceneRoot.getViewTreeObserver().removeOnPreDrawListener(this); + if (mViewTreeObserver.isAlive()) { + mViewTreeObserver.removeOnPreDrawListener(this); + } else { + mSceneRoot.getViewTreeObserver().removeOnPreDrawListener(this); + } mSceneRoot.removeOnAttachStateChangeListener(this); } diff --git a/core/java/com/android/internal/view/OneShotPreDrawListener.java b/core/java/com/android/internal/view/OneShotPreDrawListener.java new file mode 100644 index 000000000000..98ffd82af887 --- /dev/null +++ b/core/java/com/android/internal/view/OneShotPreDrawListener.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016 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 com.android.internal.view; + +import android.view.View; +import android.view.ViewTreeObserver; + +/** + * An OnPreDrawListener that will remove itself after one OnPreDraw call. Typical + * usage is: + * <pre><code> + * OneShotPreDrawListener.add(view, () -> { view.doSomething(); }) + * </code></pre> + * <p> + * The onPreDraw always returns true. + * <p> + * The listener will also remove itself from the ViewTreeObserver when the view + * is detached from the view hierarchy. In that case, the Runnable will never be + * executed. + */ +public class OneShotPreDrawListener implements ViewTreeObserver.OnPreDrawListener, + View.OnAttachStateChangeListener { + private final View mView; + private ViewTreeObserver mViewTreeObserver; + private final Runnable mRunnable; + + private OneShotPreDrawListener(View view, Runnable runnable) { + mView = view; + mViewTreeObserver = view.getViewTreeObserver(); + mRunnable = runnable; + } + + /** + * Creates a OneShotPreDrawListener and adds it to view's ViewTreeObserver. + * @param view The view whose ViewTreeObserver the OnPreDrawListener should listen. + * @param runnable The Runnable to execute in the OnPreDraw (once) + * @return The added OneShotPreDrawListener. It can be removed prior to + * the onPreDraw by calling {@link #removeListener()}. + */ + public static OneShotPreDrawListener add(View view, Runnable runnable) { + OneShotPreDrawListener listener = new OneShotPreDrawListener(view, runnable); + view.getViewTreeObserver().addOnPreDrawListener(listener); + view.addOnAttachStateChangeListener(listener); + return listener; + } + + @Override + public boolean onPreDraw() { + removeListener(); + mRunnable.run(); + return true; + } + + /** + * Removes the listener from the ViewTreeObserver. This is useful to call if the + * callback should be removed prior to {@link #onPreDraw()}. + */ + public void removeListener() { + if (mViewTreeObserver.isAlive()) { + mViewTreeObserver.removeOnPreDrawListener(this); + } else { + mView.getViewTreeObserver().removeOnPreDrawListener(this); + } + mView.removeOnAttachStateChangeListener(this); + } + + @Override + public void onViewAttachedToWindow(View v) { + mViewTreeObserver = v.getViewTreeObserver(); + } + + @Override + public void onViewDetachedFromWindow(View v) { + removeListener(); + } +} |