Use safe access to OnPreDrawListener.
Bug 32472451 It is important to remove an OnPreDrawListener from the correct ViewTreeObserver. When a View is added to the view hierarchy, the initial ViewTreeObserver is not active. The listener must then be removed from the current OnPreDrawListener. When a View has been removed from the hierarchy, it is important to remove the listener from the orignal ViewTreeObserver. Test: cts-tradefed run singleCommand cts -d --skip-preconditions --skip-connectivity-check -m CtsUsageStatsTestCases Test: cts-tradefed run singleCommand cts -d --skip-preconditions --skip-connectivity-check -m CtsFragmentTestCases Change-Id: I735f71d2d9c84e86ef846aab0088a8651300fbe8
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
OneShotPreDrawListener.add(sceneRoot, () -> {
|
||||
if (enterTransition != null) {
|
||||
enterTransition.removeTarget(nonExistentView);
|
||||
ArrayList<View> views = configureEnteringExitingViews(
|
||||
enterTransition, inFragment, sharedElementsIn, nonExistentView);
|
||||
enteringViews.addAll(views);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
OneShotPreDrawListener.add(sceneRoot, () -> {
|
||||
ArrayMap<String, View> inSharedElements = captureInSharedElements(
|
||||
nameOverrides, finalSharedElementTransition, fragments);
|
||||
|
||||
if (inSharedElements != null) {
|
||||
sharedElementsIn.addAll(inSharedElements.values());
|
||||
sharedElementsIn.add(nonExistentView);
|
||||
}
|
||||
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);
|
||||
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;
|
||||
}
|
||||
});
|
||||
final View inEpicenterView = getInEpicenterView(inSharedElements,
|
||||
fragments, enterTransition, inIsPop);
|
||||
if (inEpicenterView != null) {
|
||||
inEpicenterView.getBoundsOnScreen(inEpicenter);
|
||||
}
|
||||
}
|
||||
});
|
||||
return sharedElementTransition;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user