blob: e23c687af49b27afbc53a667534c60922f08325b [file] [log] [blame]
Chet Haase91cedf12013-03-11 07:56:30 -07001/*
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 */
16package android.view;
17
Chet Haasece08ce52013-06-06 07:27:53 -070018import android.animation.LayoutTransition;
Kirill Grouchnikovb4b939c2016-03-08 10:37:06 -050019import android.annotation.NonNull;
Mathew Inwooda570dee2018-08-17 14:56:00 +010020import android.annotation.UnsupportedAppUsage;
Chet Haase91cedf12013-03-11 07:56:30 -070021import android.content.Context;
22import android.graphics.Canvas;
23import android.graphics.Rect;
24import android.graphics.drawable.Drawable;
25
26import java.util.ArrayList;
27
28/**
Chet Haaseedf6f4b2013-03-26 07:55:30 -070029 * An overlay is an extra layer that sits on top of a View (the "host view")
30 * which is drawn after all other content in that view (including children,
31 * if the view is a ViewGroup). Interaction with the overlay layer is done
32 * by adding and removing drawables.
Chet Haase91cedf12013-03-11 07:56:30 -070033 *
Chet Haaseedf6f4b2013-03-26 07:55:30 -070034 * <p>An overlay requested from a ViewGroup is of type {@link ViewGroupOverlay},
35 * which also supports adding and removing views.</p>
Chet Haase91cedf12013-03-11 07:56:30 -070036 *
Chet Haaseedf6f4b2013-03-26 07:55:30 -070037 * @see View#getOverlay() View.getOverlay()
38 * @see ViewGroup#getOverlay() ViewGroup.getOverlay()
39 * @see ViewGroupOverlay
Chet Haase91cedf12013-03-11 07:56:30 -070040 */
Chet Haaseedf6f4b2013-03-26 07:55:30 -070041public class ViewOverlay {
Chet Haase91cedf12013-03-11 07:56:30 -070042
43 /**
Chet Haaseedf6f4b2013-03-26 07:55:30 -070044 * The actual container for the drawables (and views, if it's a ViewGroupOverlay).
45 * All of the management and rendering details for the overlay are handled in
46 * OverlayViewGroup.
Chet Haase91cedf12013-03-11 07:56:30 -070047 */
Chet Haaseedf6f4b2013-03-26 07:55:30 -070048 OverlayViewGroup mOverlayViewGroup;
Chet Haase91cedf12013-03-11 07:56:30 -070049
Chet Haaseedf6f4b2013-03-26 07:55:30 -070050 ViewOverlay(Context context, View hostView) {
51 mOverlayViewGroup = new OverlayViewGroup(context, hostView);
Chet Haase91cedf12013-03-11 07:56:30 -070052 }
53
Chet Haaseedf6f4b2013-03-26 07:55:30 -070054 /**
55 * Used internally by View and ViewGroup to handle drawing and invalidation
56 * of the overlay
57 * @return
58 */
Mathew Inwooda570dee2018-08-17 14:56:00 +010059 @UnsupportedAppUsage
Chet Haaseedf6f4b2013-03-26 07:55:30 -070060 ViewGroup getOverlayView() {
61 return mOverlayViewGroup;
62 }
63
64 /**
Kirill Grouchnikovb4b939c2016-03-08 10:37:06 -050065 * Adds a {@link Drawable} to the overlay. The bounds of the drawable should be relative to
Chet Haaseedf6f4b2013-03-26 07:55:30 -070066 * the host view. Any drawable added to the overlay should be removed when it is no longer
Kirill Grouchnikovb4b939c2016-03-08 10:37:06 -050067 * needed or no longer visible. Adding an already existing {@link Drawable}
68 * is a no-op. Passing <code>null</code> parameter will result in an
69 * {@link IllegalArgumentException} being thrown.
Chet Haaseedf6f4b2013-03-26 07:55:30 -070070 *
Kirill Grouchnikovb4b939c2016-03-08 10:37:06 -050071 * @param drawable The {@link Drawable} to be added to the overlay. This drawable will be
72 * drawn when the view redraws its overlay. {@link Drawable}s will be drawn in the order that
73 * they were added.
Chet Haaseedf6f4b2013-03-26 07:55:30 -070074 * @see #remove(Drawable)
75 */
Kirill Grouchnikovb4b939c2016-03-08 10:37:06 -050076 public void add(@NonNull Drawable drawable) {
Chet Haaseedf6f4b2013-03-26 07:55:30 -070077 mOverlayViewGroup.add(drawable);
Chet Haase91cedf12013-03-11 07:56:30 -070078 }
79
Chet Haaseedf6f4b2013-03-26 07:55:30 -070080 /**
Kirill Grouchnikovb4b939c2016-03-08 10:37:06 -050081 * Removes the specified {@link Drawable} from the overlay. Removing a {@link Drawable} that was
82 * not added with {@link #add(Drawable)} is a no-op. Passing <code>null</code> parameter will
83 * result in an {@link IllegalArgumentException} being thrown.
Chet Haaseedf6f4b2013-03-26 07:55:30 -070084 *
Kirill Grouchnikovb4b939c2016-03-08 10:37:06 -050085 * @param drawable The {@link Drawable} to be removed from the overlay.
Chet Haaseedf6f4b2013-03-26 07:55:30 -070086 * @see #add(Drawable)
87 */
Kirill Grouchnikovb4b939c2016-03-08 10:37:06 -050088 public void remove(@NonNull Drawable drawable) {
Chet Haaseedf6f4b2013-03-26 07:55:30 -070089 mOverlayViewGroup.remove(drawable);
Chet Haase91cedf12013-03-11 07:56:30 -070090 }
91
Chet Haaseedf6f4b2013-03-26 07:55:30 -070092 /**
93 * Removes all content from the overlay.
94 */
Chet Haase91cedf12013-03-11 07:56:30 -070095 public void clear() {
Chet Haaseedf6f4b2013-03-26 07:55:30 -070096 mOverlayViewGroup.clear();
Chet Haase91cedf12013-03-11 07:56:30 -070097 }
98
Mathew Inwooda570dee2018-08-17 14:56:00 +010099 @UnsupportedAppUsage
Chet Haase91cedf12013-03-11 07:56:30 -0700100 boolean isEmpty() {
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700101 return mOverlayViewGroup.isEmpty();
Chet Haase91cedf12013-03-11 07:56:30 -0700102 }
103
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700104 /**
105 * OverlayViewGroup is a container that View and ViewGroup use to host
106 * drawables and views added to their overlays ({@link ViewOverlay} and
107 * {@link ViewGroupOverlay}, respectively). Drawables are added to the overlay
108 * via the add/remove methods in ViewOverlay, Views are added/removed via
109 * ViewGroupOverlay. These drawable and view objects are
110 * drawn whenever the view itself is drawn; first the view draws its own
111 * content (and children, if it is a ViewGroup), then it draws its overlay
112 * (if it has one).
113 *
114 * <p>Besides managing and drawing the list of drawables, this class serves
115 * two purposes:
116 * (1) it noops layout calls because children are absolutely positioned and
117 * (2) it forwards all invalidation calls to its host view. The invalidation
118 * redirect is necessary because the overlay is not a child of the host view
119 * and invalidation cannot therefore follow the normal path up through the
120 * parent hierarchy.</p>
121 *
122 * @see View#getOverlay()
123 * @see ViewGroup#getOverlay()
Chet Haase91cedf12013-03-11 07:56:30 -0700124 */
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700125 static class OverlayViewGroup extends ViewGroup {
Chet Haase91cedf12013-03-11 07:56:30 -0700126
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700127 /**
128 * The View for which this is an overlay. Invalidations of the overlay are redirected to
129 * this host view.
130 */
Kirill Grouchnikovb4b939c2016-03-08 10:37:06 -0500131 final View mHostView;
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700132
133 /**
134 * The set of drawables to draw when the overlay is rendered.
135 */
136 ArrayList<Drawable> mDrawables = null;
137
138 OverlayViewGroup(Context context, View hostView) {
139 super(context);
140 mHostView = hostView;
141 mAttachInfo = mHostView.mAttachInfo;
Chris Craik2180ba72014-12-01 17:53:34 -0800142
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700143 mRight = hostView.getWidth();
144 mBottom = hostView.getHeight();
Chris Craik2180ba72014-12-01 17:53:34 -0800145 // pass right+bottom directly to RenderNode, since not going through setters
146 mRenderNode.setLeftTopRightBottom(0, 0, mRight, mBottom);
Chet Haase91cedf12013-03-11 07:56:30 -0700147 }
Chet Haase91cedf12013-03-11 07:56:30 -0700148
Kirill Grouchnikovb4b939c2016-03-08 10:37:06 -0500149 public void add(@NonNull Drawable drawable) {
150 if (drawable == null) {
151 throw new IllegalArgumentException("drawable must be non-null");
152 }
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700153 if (mDrawables == null) {
Kirill Grouchnikovb4b939c2016-03-08 10:37:06 -0500154 mDrawables = new ArrayList<>();
Chet Haase91cedf12013-03-11 07:56:30 -0700155 }
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700156 if (!mDrawables.contains(drawable)) {
157 // Make each drawable unique in the overlay; can't add it more than once
158 mDrawables.add(drawable);
159 invalidate(drawable.getBounds());
160 drawable.setCallback(this);
Chet Haase9c17fe62013-03-22 17:05:55 -0700161 }
Chet Haase91cedf12013-03-11 07:56:30 -0700162 }
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700163
Kirill Grouchnikovb4b939c2016-03-08 10:37:06 -0500164 public void remove(@NonNull Drawable drawable) {
165 if (drawable == null) {
166 throw new IllegalArgumentException("drawable must be non-null");
167 }
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700168 if (mDrawables != null) {
169 mDrawables.remove(drawable);
170 invalidate(drawable.getBounds());
171 drawable.setCallback(null);
172 }
173 }
174
Alan Viverette39de9bf2013-12-11 13:15:47 -0800175 @Override
Alan Viverettef6d87ec2016-03-11 10:09:14 -0500176 protected boolean verifyDrawable(@NonNull Drawable who) {
Alan Viverette39de9bf2013-12-11 13:15:47 -0800177 return super.verifyDrawable(who) || (mDrawables != null && mDrawables.contains(who));
178 }
179
Kirill Grouchnikovb4b939c2016-03-08 10:37:06 -0500180 public void add(@NonNull View child) {
181 if (child == null) {
182 throw new IllegalArgumentException("view must be non-null");
183 }
184
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700185 if (child.getParent() instanceof ViewGroup) {
186 ViewGroup parent = (ViewGroup) child.getParent();
Chet Haaseface7422013-04-15 15:15:59 -0700187 if (parent != mHostView && parent.getParent() != null &&
188 parent.mAttachInfo != null) {
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700189 // Moving to different container; figure out how to position child such that
190 // it is in the same location on the screen
191 int[] parentLocation = new int[2];
192 int[] hostViewLocation = new int[2];
193 parent.getLocationOnScreen(parentLocation);
194 mHostView.getLocationOnScreen(hostViewLocation);
195 child.offsetLeftAndRight(parentLocation[0] - hostViewLocation[0]);
196 child.offsetTopAndBottom(parentLocation[1] - hostViewLocation[1]);
197 }
198 parent.removeView(child);
Chet Haasece08ce52013-06-06 07:27:53 -0700199 if (parent.getLayoutTransition() != null) {
200 // LayoutTransition will cause the child to delay removal - cancel it
201 parent.getLayoutTransition().cancel(LayoutTransition.DISAPPEARING);
202 }
203 // fail-safe if view is still attached for any reason
204 if (child.getParent() != null) {
205 child.mParent = null;
206 }
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700207 }
208 super.addView(child);
209 }
210
Kirill Grouchnikovb4b939c2016-03-08 10:37:06 -0500211 public void remove(@NonNull View view) {
212 if (view == null) {
213 throw new IllegalArgumentException("view must be non-null");
214 }
215
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700216 super.removeView(view);
217 }
218
219 public void clear() {
220 removeAllViews();
Chet Haased8b0b232013-05-20 06:46:11 -0700221 if (mDrawables != null) {
Kirill Grouchnikov22351c32016-03-14 08:34:36 -0400222 for (Drawable drawable : mDrawables) {
223 drawable.setCallback(null);
224 }
Chet Haased8b0b232013-05-20 06:46:11 -0700225 mDrawables.clear();
226 }
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700227 }
228
229 boolean isEmpty() {
230 if (getChildCount() == 0 &&
231 (mDrawables == null || mDrawables.size() == 0)) {
232 return true;
233 }
234 return false;
235 }
236
237 @Override
Alan Viverettef6d87ec2016-03-11 10:09:14 -0500238 public void invalidateDrawable(@NonNull Drawable drawable) {
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700239 invalidate(drawable.getBounds());
240 }
241
242 @Override
243 protected void dispatchDraw(Canvas canvas) {
Chris Craik36d9a6d2017-01-12 19:17:19 -0800244 /*
245 * The OverlayViewGroup doesn't draw with a DisplayList, because
246 * draw(Canvas, View, long) is never called on it. This is fine, since it doesn't need
247 * RenderNode/DisplayList features, and can just draw into the owner's Canvas.
248 *
249 * This means that we need to insert reorder barriers manually though, so that children
250 * of the OverlayViewGroup can cast shadows and Z reorder with each other.
251 */
252 canvas.insertReorderBarrier();
253
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700254 super.dispatchDraw(canvas);
Chris Craik36d9a6d2017-01-12 19:17:19 -0800255
256 canvas.insertInorderBarrier();
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700257 final int numDrawables = (mDrawables == null) ? 0 : mDrawables.size();
258 for (int i = 0; i < numDrawables; ++i) {
259 mDrawables.get(i).draw(canvas);
260 }
261 }
262
263 @Override
264 protected void onLayout(boolean changed, int l, int t, int r, int b) {
265 // Noop: children are positioned absolutely
266 }
267
268 /*
269 The following invalidation overrides exist for the purpose of redirecting invalidation to
270 the host view. The overlay is not parented to the host view (since a View cannot be a
271 parent), so the invalidation cannot proceed through the normal parent hierarchy.
272 There is a built-in assumption that the overlay exactly covers the host view, therefore
273 the invalidation rectangles received do not need to be adjusted when forwarded to
274 the host view.
275 */
276
277 @Override
278 public void invalidate(Rect dirty) {
279 super.invalidate(dirty);
280 if (mHostView != null) {
281 mHostView.invalidate(dirty);
282 }
283 }
284
285 @Override
286 public void invalidate(int l, int t, int r, int b) {
287 super.invalidate(l, t, r, b);
288 if (mHostView != null) {
289 mHostView.invalidate(l, t, r, b);
290 }
291 }
292
293 @Override
294 public void invalidate() {
295 super.invalidate();
296 if (mHostView != null) {
297 mHostView.invalidate();
298 }
299 }
300
Chris Craik3f06c6d2017-01-09 18:19:48 +0000301 /** @hide */
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700302 @Override
Chris Craik3f06c6d2017-01-09 18:19:48 +0000303 public void invalidate(boolean invalidateCache) {
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700304 super.invalidate(invalidateCache);
305 if (mHostView != null) {
306 mHostView.invalidate(invalidateCache);
307 }
308 }
309
310 @Override
311 void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
312 super.invalidateViewProperty(invalidateParent, forceRedraw);
313 if (mHostView != null) {
314 mHostView.invalidateViewProperty(invalidateParent, forceRedraw);
315 }
316 }
317
318 @Override
319 protected void invalidateParentCaches() {
320 super.invalidateParentCaches();
321 if (mHostView != null) {
322 mHostView.invalidateParentCaches();
323 }
324 }
325
326 @Override
327 protected void invalidateParentIfNeeded() {
328 super.invalidateParentIfNeeded();
329 if (mHostView != null) {
330 mHostView.invalidateParentIfNeeded();
331 }
332 }
333
Chris Craik49e6c732014-03-31 12:34:11 -0700334 @Override
Chris Craik9de95db2017-01-18 17:59:23 -0800335 public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
Chris Craikbc44b1a2017-04-25 13:21:36 -0700336 if (mHostView != null) {
337 if (mHostView instanceof ViewGroup) {
338 // Propagate invalidate through the host...
339 ((ViewGroup) mHostView).onDescendantInvalidated(mHostView, target);
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700340
Chris Craikbc44b1a2017-04-25 13:21:36 -0700341 // ...and also this view, since it will hold the descendant, and must later
342 // propagate the calls to update display lists if dirty
343 super.onDescendantInvalidated(child, target);
344 } else {
345 // Can't use onDescendantInvalidated because host isn't a ViewGroup - fall back
346 // to invalidating.
347 invalidate();
348 }
Chet Haasee4a2d7c2013-06-21 17:49:36 -0700349 }
Chet Haasee4a2d7c2013-06-21 17:49:36 -0700350 }
351
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700352 @Override
353 public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
354 if (mHostView != null) {
355 dirty.offset(location[0], location[1]);
356 if (mHostView instanceof ViewGroup) {
357 location[0] = 0;
358 location[1] = 0;
359 super.invalidateChildInParent(location, dirty);
360 return ((ViewGroup) mHostView).invalidateChildInParent(location, dirty);
361 } else {
362 invalidate(dirty);
363 }
364 }
365 return null;
366 }
Chet Haase91cedf12013-03-11 07:56:30 -0700367 }
Chet Haaseedf6f4b2013-03-26 07:55:30 -0700368
Chet Haase91cedf12013-03-11 07:56:30 -0700369}