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