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 26 import java.util.ArrayList; 27 28 /** 29 * 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. 33 * 34 * <p>An overlay requested from a ViewGroup is of type {@link ViewGroupOverlay}, 35 * which also supports adding and removing views.</p> 36 * 37 * @see View#getOverlay() View.getOverlay() 38 * @see ViewGroup#getOverlay() ViewGroup.getOverlay() 39 * @see ViewGroupOverlay 40 */ 41 public class ViewOverlay { 42 43 /** 44 * 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. 47 */ 48 OverlayViewGroup mOverlayViewGroup; 49 ViewOverlay(Context context, View hostView)50 ViewOverlay(Context context, View hostView) { 51 mOverlayViewGroup = new OverlayViewGroup(context, hostView); 52 } 53 54 /** 55 * Used internally by View and ViewGroup to handle drawing and invalidation 56 * of the overlay 57 * @return 58 */ 59 @UnsupportedAppUsage getOverlayView()60 ViewGroup getOverlayView() { 61 return mOverlayViewGroup; 62 } 63 64 /** 65 * Adds a {@link Drawable} to the overlay. The bounds of the drawable should be relative to 66 * the host view. Any drawable added to the overlay should be removed when it is no longer 67 * 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. 70 * 71 * @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. 74 * @see #remove(Drawable) 75 */ add(@onNull Drawable drawable)76 public void add(@NonNull Drawable drawable) { 77 mOverlayViewGroup.add(drawable); 78 } 79 80 /** 81 * 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. 84 * 85 * @param drawable The {@link Drawable} to be removed from the overlay. 86 * @see #add(Drawable) 87 */ remove(@onNull Drawable drawable)88 public void remove(@NonNull Drawable drawable) { 89 mOverlayViewGroup.remove(drawable); 90 } 91 92 /** 93 * Removes all content from the overlay. 94 */ clear()95 public void clear() { 96 mOverlayViewGroup.clear(); 97 } 98 99 @UnsupportedAppUsage isEmpty()100 boolean isEmpty() { 101 return mOverlayViewGroup.isEmpty(); 102 } 103 104 /** 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() 124 */ 125 static class OverlayViewGroup extends ViewGroup { 126 127 /** 128 * The View for which this is an overlay. Invalidations of the overlay are redirected to 129 * this host view. 130 */ 131 final View mHostView; 132 133 /** 134 * The set of drawables to draw when the overlay is rendered. 135 */ 136 ArrayList<Drawable> mDrawables = null; 137 OverlayViewGroup(Context context, View hostView)138 OverlayViewGroup(Context context, View hostView) { 139 super(context); 140 mHostView = hostView; 141 mAttachInfo = mHostView.mAttachInfo; 142 143 mRight = hostView.getWidth(); 144 mBottom = hostView.getHeight(); 145 // pass right+bottom directly to RenderNode, since not going through setters 146 mRenderNode.setLeftTopRightBottom(0, 0, mRight, mBottom); 147 } 148 add(@onNull Drawable drawable)149 public void add(@NonNull Drawable drawable) { 150 if (drawable == null) { 151 throw new IllegalArgumentException("drawable must be non-null"); 152 } 153 if (mDrawables == null) { 154 mDrawables = new ArrayList<>(); 155 } 156 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); 161 } 162 } 163 remove(@onNull Drawable drawable)164 public void remove(@NonNull Drawable drawable) { 165 if (drawable == null) { 166 throw new IllegalArgumentException("drawable must be non-null"); 167 } 168 if (mDrawables != null) { 169 mDrawables.remove(drawable); 170 invalidate(drawable.getBounds()); 171 drawable.setCallback(null); 172 } 173 } 174 175 @Override verifyDrawable(@onNull Drawable who)176 protected boolean verifyDrawable(@NonNull Drawable who) { 177 return super.verifyDrawable(who) || (mDrawables != null && mDrawables.contains(who)); 178 } 179 add(@onNull View child)180 public void add(@NonNull View child) { 181 if (child == null) { 182 throw new IllegalArgumentException("view must be non-null"); 183 } 184 185 if (child.getParent() instanceof ViewGroup) { 186 ViewGroup parent = (ViewGroup) child.getParent(); 187 if (parent != mHostView && parent.getParent() != null && 188 parent.mAttachInfo != null) { 189 // 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); 199 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 } 207 } 208 super.addView(child); 209 } 210 remove(@onNull View view)211 public void remove(@NonNull View view) { 212 if (view == null) { 213 throw new IllegalArgumentException("view must be non-null"); 214 } 215 216 super.removeView(view); 217 } 218 clear()219 public void clear() { 220 removeAllViews(); 221 if (mDrawables != null) { 222 for (Drawable drawable : mDrawables) { 223 drawable.setCallback(null); 224 } 225 mDrawables.clear(); 226 } 227 } 228 isEmpty()229 boolean isEmpty() { 230 if (getChildCount() == 0 && 231 (mDrawables == null || mDrawables.size() == 0)) { 232 return true; 233 } 234 return false; 235 } 236 237 @Override invalidateDrawable(@onNull Drawable drawable)238 public void invalidateDrawable(@NonNull Drawable drawable) { 239 invalidate(drawable.getBounds()); 240 } 241 242 @Override dispatchDraw(Canvas canvas)243 protected void dispatchDraw(Canvas canvas) { 244 /* 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 254 super.dispatchDraw(canvas); 255 256 canvas.insertInorderBarrier(); 257 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 onLayout(boolean changed, int l, int t, int r, int b)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 invalidate(Rect dirty)278 public void invalidate(Rect dirty) { 279 super.invalidate(dirty); 280 if (mHostView != null) { 281 mHostView.invalidate(dirty); 282 } 283 } 284 285 @Override invalidate(int l, int t, int r, int b)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 invalidate()294 public void invalidate() { 295 super.invalidate(); 296 if (mHostView != null) { 297 mHostView.invalidate(); 298 } 299 } 300 301 /** @hide */ 302 @Override invalidate(boolean invalidateCache)303 public void invalidate(boolean invalidateCache) { 304 super.invalidate(invalidateCache); 305 if (mHostView != null) { 306 mHostView.invalidate(invalidateCache); 307 } 308 } 309 310 @Override invalidateViewProperty(boolean invalidateParent, boolean forceRedraw)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 invalidateParentCaches()319 protected void invalidateParentCaches() { 320 super.invalidateParentCaches(); 321 if (mHostView != null) { 322 mHostView.invalidateParentCaches(); 323 } 324 } 325 326 @Override invalidateParentIfNeeded()327 protected void invalidateParentIfNeeded() { 328 super.invalidateParentIfNeeded(); 329 if (mHostView != null) { 330 mHostView.invalidateParentIfNeeded(); 331 } 332 } 333 334 @Override onDescendantInvalidated(@onNull View child, @NonNull View target)335 public void onDescendantInvalidated(@NonNull View child, @NonNull View target) { 336 if (mHostView != null) { 337 if (mHostView instanceof ViewGroup) { 338 // Propagate invalidate through the host... 339 ((ViewGroup) mHostView).onDescendantInvalidated(mHostView, target); 340 341 // ...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 } 349 } 350 } 351 352 @Override invalidateChildInParent(int[] location, Rect dirty)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 } 367 } 368 369 } 370