1 /*
2  * Copyright (C) 2016 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 package com.android.internal.view;
17 
18 import android.annotation.NonNull;
19 import android.view.View;
20 import android.view.ViewTreeObserver;
21 
22 /**
23  * An OnPreDrawListener that will remove itself after one OnPreDraw call. Typical
24  * usage is:
25  * <pre><code>
26  *     OneShotPreDrawListener.add(view, () -> { view.doSomething(); })
27  * </code></pre>
28  * <p>
29  * The listener will also remove itself from the ViewTreeObserver when the view
30  * is detached from the view hierarchy. In that case, the Runnable will never be
31  * executed.
32  */
33 public class OneShotPreDrawListener implements ViewTreeObserver.OnPreDrawListener,
34         View.OnAttachStateChangeListener {
35     private final View mView;
36     private ViewTreeObserver mViewTreeObserver;
37     private final Runnable mRunnable;
38     private final boolean mReturnValue;
39 
OneShotPreDrawListener(@onNull View view, boolean returnValue, @NonNull Runnable runnable)40     private OneShotPreDrawListener(@NonNull View view, boolean returnValue,
41             @NonNull Runnable runnable) {
42         mView = view;
43         mViewTreeObserver = view.getViewTreeObserver();
44         mRunnable = runnable;
45         mReturnValue = returnValue;
46     }
47 
48     /**
49      * Creates a OneShotPreDrawListener and adds it to view's ViewTreeObserver. The
50      * return value from the OnPreDrawListener is {@code true}.
51      *
52      * @param view The view whose ViewTreeObserver the OnPreDrawListener should listen.
53      * @param runnable The Runnable to execute in the OnPreDraw (once)
54      * @return The added OneShotPreDrawListener. It can be removed prior to
55      * the onPreDraw by calling {@link #removeListener()}.
56      */
add(@onNull View view, @NonNull Runnable runnable)57     public static OneShotPreDrawListener add(@NonNull View view, @NonNull Runnable runnable) {
58         return add(view, true, runnable);
59     }
60 
61     /**
62      * Creates a OneShotPreDrawListener and adds it to view's ViewTreeObserver.
63      *
64      * @param view The view whose ViewTreeObserver the OnPreDrawListener should listen.
65      * @param returnValue The value to be returned from the OnPreDrawListener.
66      * @param runnable The Runnable to execute in the OnPreDraw (once)
67      * @return The added OneShotPreDrawListener. It can be removed prior to
68      * the onPreDraw by calling {@link #removeListener()}.
69      */
add(@onNull View view, boolean returnValue, @NonNull Runnable runnable)70     public static OneShotPreDrawListener add(@NonNull View view, boolean returnValue,
71             @NonNull Runnable runnable) {
72         OneShotPreDrawListener listener = new OneShotPreDrawListener(view, returnValue, runnable);
73         view.getViewTreeObserver().addOnPreDrawListener(listener);
74         view.addOnAttachStateChangeListener(listener);
75         return listener;
76     }
77 
78     @Override
onPreDraw()79     public boolean onPreDraw() {
80         removeListener();
81         mRunnable.run();
82         return mReturnValue;
83     }
84 
85     /**
86      * Removes the listener from the ViewTreeObserver. This is useful to call if the
87      * callback should be removed prior to {@link #onPreDraw()}.
88      */
removeListener()89     public void removeListener() {
90         if (mViewTreeObserver.isAlive()) {
91             mViewTreeObserver.removeOnPreDrawListener(this);
92         } else {
93             mView.getViewTreeObserver().removeOnPreDrawListener(this);
94         }
95         mView.removeOnAttachStateChangeListener(this);
96     }
97 
98     @Override
onViewAttachedToWindow(@onNull View v)99     public void onViewAttachedToWindow(@NonNull View v) {
100         mViewTreeObserver = v.getViewTreeObserver();
101     }
102 
103     @Override
onViewDetachedFromWindow(@onNull View v)104     public void onViewDetachedFromWindow(@NonNull View v) {
105         removeListener();
106     }
107 }
108