• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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 android.view;
17 
18 import android.animation.LayoutTransition;
19 import android.annotation.NonNull;
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.content.Context;
22 import android.graphics.Canvas;
23 import android.graphics.Rect;
24 import android.graphics.drawable.Drawable;
25 import android.os.Build;
26 
27 import java.util.ArrayList;
28 
29 /**
30  * An overlay is an extra layer that sits on top of a View (the "host view")
31  * which is drawn after all other content in that view (including children,
32  * if the view is a ViewGroup). Interaction with the overlay layer is done
33  * by adding and removing drawables.
34  *
35  * <p>An overlay requested from a ViewGroup is of type {@link ViewGroupOverlay},
36  * which also supports adding and removing views.</p>
37  *
38  * @see View#getOverlay() View.getOverlay()
39  * @see ViewGroup#getOverlay() ViewGroup.getOverlay()
40  * @see ViewGroupOverlay
41  */
42 public class ViewOverlay {
43 
44     /**
45      * The actual container for the drawables (and views, if it's a ViewGroupOverlay).
46      * All of the management and rendering details for the overlay are handled in
47      * OverlayViewGroup.
48      */
49     OverlayViewGroup mOverlayViewGroup;
50 
ViewOverlay(Context context, View hostView)51     ViewOverlay(Context context, View hostView) {
52         mOverlayViewGroup = new OverlayViewGroup(context, hostView);
53     }
54 
55     /**
56      * Used internally by View and ViewGroup to handle drawing and invalidation
57      * of the overlay
58      * @return
59      */
60     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getOverlayView()61     ViewGroup getOverlayView() {
62         return mOverlayViewGroup;
63     }
64 
65     /**
66      * Adds a {@link Drawable} to the overlay. The bounds of the drawable should be relative to
67      * the host view. Any drawable added to the overlay should be removed when it is no longer
68      * needed or no longer visible. Adding an already existing {@link Drawable}
69      * is a no-op. Passing <code>null</code> parameter will result in an
70      * {@link IllegalArgumentException} being thrown.
71      *
72      * @param drawable The {@link Drawable} to be added to the overlay. This drawable will be
73      * drawn when the view redraws its overlay. {@link Drawable}s will be drawn in the order that
74      * they were added.
75      * @see #remove(Drawable)
76      */
add(@onNull Drawable drawable)77     public void add(@NonNull Drawable drawable) {
78         mOverlayViewGroup.add(drawable);
79     }
80 
81     /**
82      * Removes the specified {@link Drawable} from the overlay. Removing a {@link Drawable} that was
83      * not added with {@link #add(Drawable)} is a no-op. Passing <code>null</code> parameter will
84      * result in an {@link IllegalArgumentException} being thrown.
85      *
86      * @param drawable The {@link Drawable} to be removed from the overlay.
87      * @see #add(Drawable)
88      */
remove(@onNull Drawable drawable)89     public void remove(@NonNull Drawable drawable) {
90         mOverlayViewGroup.remove(drawable);
91     }
92 
93     /**
94      * Removes all content from the overlay.
95      */
clear()96     public void clear() {
97         mOverlayViewGroup.clear();
98     }
99 
100     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
isEmpty()101     boolean isEmpty() {
102         return mOverlayViewGroup.isEmpty();
103     }
104 
105     /**
106      * OverlayViewGroup is a container that View and ViewGroup use to host
107      * drawables and views added to their overlays  ({@link ViewOverlay} and
108      * {@link ViewGroupOverlay}, respectively). Drawables are added to the overlay
109      * via the add/remove methods in ViewOverlay, Views are added/removed via
110      * ViewGroupOverlay. These drawable and view objects are
111      * drawn whenever the view itself is drawn; first the view draws its own
112      * content (and children, if it is a ViewGroup), then it draws its overlay
113      * (if it has one).
114      *
115      * <p>Besides managing and drawing the list of drawables, this class serves
116      * two purposes:
117      * (1) it noops layout calls because children are absolutely positioned and
118      * (2) it forwards all invalidation calls to its host view. The invalidation
119      * redirect is necessary because the overlay is not a child of the host view
120      * and invalidation cannot therefore follow the normal path up through the
121      * parent hierarchy.</p>
122      *
123      * @see View#getOverlay()
124      * @see ViewGroup#getOverlay()
125      */
126     static class OverlayViewGroup extends ViewGroup {
127 
128         /**
129          * The View for which this is an overlay. Invalidations of the overlay are redirected to
130          * this host view.
131          */
132         final View mHostView;
133 
134         /**
135          * The set of drawables to draw when the overlay is rendered.
136          */
137         ArrayList<Drawable> mDrawables = null;
138 
OverlayViewGroup(Context context, View hostView)139         OverlayViewGroup(Context context, View hostView) {
140             super(context);
141             mHostView = hostView;
142             mAttachInfo = mHostView.mAttachInfo;
143 
144             mRight = hostView.getWidth();
145             mBottom = hostView.getHeight();
146             // pass right+bottom directly to RenderNode, since not going through setters
147             mRenderNode.setLeftTopRightBottom(0, 0, mRight, mBottom);
148         }
149 
add(@onNull Drawable drawable)150         public void add(@NonNull Drawable drawable) {
151             if (drawable == null) {
152                 throw new IllegalArgumentException("drawable must be non-null");
153             }
154             if (mDrawables == null) {
155                 mDrawables = new ArrayList<>();
156             }
157             if (!mDrawables.contains(drawable)) {
158                 // Make each drawable unique in the overlay; can't add it more than once
159                 mDrawables.add(drawable);
160                 invalidate(drawable.getBounds());
161                 drawable.setCallback(this);
162             }
163         }
164 
remove(@onNull Drawable drawable)165         public void remove(@NonNull Drawable drawable) {
166             if (drawable == null) {
167                 throw new IllegalArgumentException("drawable must be non-null");
168             }
169             if (mDrawables != null) {
170                 mDrawables.remove(drawable);
171                 invalidate(drawable.getBounds());
172                 drawable.setCallback(null);
173             }
174         }
175 
176         @Override
verifyDrawable(@onNull Drawable who)177         protected boolean verifyDrawable(@NonNull Drawable who) {
178             return super.verifyDrawable(who) || (mDrawables != null && mDrawables.contains(who));
179         }
180 
add(@onNull View child)181         public void add(@NonNull View child) {
182             if (child == null) {
183                 throw new IllegalArgumentException("view must be non-null");
184             }
185 
186             if (child.getParent() instanceof ViewGroup) {
187                 ViewGroup parent = (ViewGroup) child.getParent();
188                 if (parent != mHostView && parent.getParent() != null &&
189                         parent.mAttachInfo != null) {
190                     // Moving to different container; figure out how to position child such that
191                     // it is in the same location on the screen
192                     int[] parentLocation = new int[2];
193                     int[] hostViewLocation = new int[2];
194                     parent.getLocationOnScreen(parentLocation);
195                     mHostView.getLocationOnScreen(hostViewLocation);
196                     child.offsetLeftAndRight(parentLocation[0] - hostViewLocation[0]);
197                     child.offsetTopAndBottom(parentLocation[1] - hostViewLocation[1]);
198                 }
199                 parent.removeView(child);
200                 if (parent.getLayoutTransition() != null) {
201                     // LayoutTransition will cause the child to delay removal - cancel it
202                     parent.getLayoutTransition().cancel(LayoutTransition.DISAPPEARING);
203                 }
204                 // fail-safe if view is still attached for any reason
205                 if (child.getParent() != null) {
206                     child.mParent = null;
207                 }
208             }
209             super.addView(child);
210         }
211 
remove(@onNull View view)212         public void remove(@NonNull View view) {
213             if (view == null) {
214                 throw new IllegalArgumentException("view must be non-null");
215             }
216 
217             super.removeView(view);
218         }
219 
clear()220         public void clear() {
221             removeAllViews();
222             if (mDrawables != null) {
223                 for (Drawable drawable : mDrawables) {
224                     drawable.setCallback(null);
225                 }
226                 mDrawables.clear();
227             }
228         }
229 
isEmpty()230         boolean isEmpty() {
231             if (getChildCount() == 0 &&
232                     (mDrawables == null || mDrawables.size() == 0)) {
233                 return true;
234             }
235             return false;
236         }
237 
238         @Override
invalidateDrawable(@onNull Drawable drawable)239         public void invalidateDrawable(@NonNull Drawable drawable) {
240             invalidate(drawable.getBounds());
241         }
242 
243         @Override
dispatchDraw(Canvas canvas)244         protected void dispatchDraw(Canvas canvas) {
245             /*
246              * The OverlayViewGroup doesn't draw with a DisplayList, because
247              * draw(Canvas, View, long) is never called on it. This is fine, since it doesn't need
248              * RenderNode/DisplayList features, and can just draw into the owner's Canvas.
249              *
250              * This means that we need to insert reorder barriers manually though, so that children
251              * of the OverlayViewGroup can cast shadows and Z reorder with each other.
252              */
253             canvas.enableZ();
254 
255             super.dispatchDraw(canvas);
256 
257             canvas.disableZ();
258             final int numDrawables = (mDrawables == null) ? 0 : mDrawables.size();
259             for (int i = 0; i < numDrawables; ++i) {
260                 mDrawables.get(i).draw(canvas);
261             }
262         }
263 
264         @Override
onLayout(boolean changed, int l, int t, int r, int b)265         protected void onLayout(boolean changed, int l, int t, int r, int b) {
266             // Noop: children are positioned absolutely
267         }
268 
269         /*
270          The following invalidation overrides exist for the purpose of redirecting invalidation to
271          the host view. The overlay is not parented to the host view (since a View cannot be a
272          parent), so the invalidation cannot proceed through the normal parent hierarchy.
273          There is a built-in assumption that the overlay exactly covers the host view, therefore
274          the invalidation rectangles received do not need to be adjusted when forwarded to
275          the host view.
276          */
277 
278         @Override
invalidate(Rect dirty)279         public void invalidate(Rect dirty) {
280             super.invalidate(dirty);
281             if (mHostView != null) {
282                 mHostView.invalidate(dirty);
283             }
284         }
285 
286         @Override
invalidate(int l, int t, int r, int b)287         public void invalidate(int l, int t, int r, int b) {
288             super.invalidate(l, t, r, b);
289             if (mHostView != null) {
290                 mHostView.invalidate(l, t, r, b);
291             }
292         }
293 
294         @Override
invalidate()295         public void invalidate() {
296             super.invalidate();
297             if (mHostView != null) {
298                 mHostView.invalidate();
299             }
300         }
301 
302         /** @hide */
303         @Override
invalidate(boolean invalidateCache)304         public void invalidate(boolean invalidateCache) {
305             super.invalidate(invalidateCache);
306             if (mHostView != null) {
307                 mHostView.invalidate(invalidateCache);
308             }
309         }
310 
311         @Override
invalidateViewProperty(boolean invalidateParent, boolean forceRedraw)312         void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
313             super.invalidateViewProperty(invalidateParent, forceRedraw);
314             if (mHostView != null) {
315                 mHostView.invalidateViewProperty(invalidateParent, forceRedraw);
316             }
317         }
318 
319         @Override
invalidateParentCaches()320         protected void invalidateParentCaches() {
321             super.invalidateParentCaches();
322             if (mHostView != null) {
323                 mHostView.invalidateParentCaches();
324             }
325         }
326 
327         @Override
invalidateParentIfNeeded()328         protected void invalidateParentIfNeeded() {
329             super.invalidateParentIfNeeded();
330             if (mHostView != null) {
331                 mHostView.invalidateParentIfNeeded();
332             }
333         }
334 
335         @Override
onDescendantInvalidated(@onNull View child, @NonNull View target)336         public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
337             if (mHostView != null) {
338                 if (mHostView instanceof ViewGroup) {
339                     // Propagate invalidate through the host...
340                     ((ViewGroup) mHostView).onDescendantInvalidated(mHostView, target);
341 
342                     // ...and also this view, since it will hold the descendant, and must later
343                     // propagate the calls to update display lists if dirty
344                     super.onDescendantInvalidated(child, target);
345                 } else {
346                     // Can't use onDescendantInvalidated because host isn't a ViewGroup - fall back
347                     // to invalidating.
348                     invalidate();
349                 }
350             }
351         }
352 
353         @Override
invalidateChildInParent(int[] location, Rect dirty)354         public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
355             if (mHostView != null) {
356                 dirty.offset(location[0], location[1]);
357                 if (mHostView instanceof ViewGroup) {
358                     location[0] = 0;
359                     location[1] = 0;
360                     super.invalidateChildInParent(location, dirty);
361                     return ((ViewGroup) mHostView).invalidateChildInParent(location, dirty);
362                 } else {
363                     invalidate(dirty);
364                 }
365             }
366             return null;
367         }
368     }
369 
370 }
371