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