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 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 */ 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 */ 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 */ 80 public void remove(Drawable drawable) { 81 mOverlayViewGroup.remove(drawable); 82 } 83 84 /** 85 * Removes all content from the overlay. 86 */ 87 public void clear() { 88 mOverlayViewGroup.clear(); 89 } 90 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 129 OverlayViewGroup(Context context, View hostView) { 130 super(context); 131 mHostView = hostView; 132 mAttachInfo = mHostView.mAttachInfo; 133 mRight = hostView.getWidth(); 134 mBottom = hostView.getHeight(); 135 } 136 137 public void add(Drawable drawable) { 138 if (mDrawables == null) { 139 140 mDrawables = new ArrayList<Drawable>(); 141 } 142 if (!mDrawables.contains(drawable)) { 143 // Make each drawable unique in the overlay; can't add it more than once 144 mDrawables.add(drawable); 145 invalidate(drawable.getBounds()); 146 drawable.setCallback(this); 147 } 148 } 149 150 public void remove(Drawable drawable) { 151 if (mDrawables != null) { 152 mDrawables.remove(drawable); 153 invalidate(drawable.getBounds()); 154 drawable.setCallback(null); 155 } 156 } 157 158 public void add(View child) { 159 if (child.getParent() instanceof ViewGroup) { 160 ViewGroup parent = (ViewGroup) child.getParent(); 161 if (parent != mHostView && parent.getParent() != null && 162 parent.mAttachInfo != null) { 163 // Moving to different container; figure out how to position child such that 164 // it is in the same location on the screen 165 int[] parentLocation = new int[2]; 166 int[] hostViewLocation = new int[2]; 167 parent.getLocationOnScreen(parentLocation); 168 mHostView.getLocationOnScreen(hostViewLocation); 169 child.offsetLeftAndRight(parentLocation[0] - hostViewLocation[0]); 170 child.offsetTopAndBottom(parentLocation[1] - hostViewLocation[1]); 171 } 172 parent.removeView(child); 173 if (parent.getLayoutTransition() != null) { 174 // LayoutTransition will cause the child to delay removal - cancel it 175 parent.getLayoutTransition().cancel(LayoutTransition.DISAPPEARING); 176 } 177 // fail-safe if view is still attached for any reason 178 if (child.getParent() != null) { 179 child.mParent = null; 180 } 181 } 182 super.addView(child); 183 } 184 185 public void remove(View view) { 186 super.removeView(view); 187 } 188 189 public void clear() { 190 removeAllViews(); 191 if (mDrawables != null) { 192 mDrawables.clear(); 193 } 194 } 195 196 boolean isEmpty() { 197 if (getChildCount() == 0 && 198 (mDrawables == null || mDrawables.size() == 0)) { 199 return true; 200 } 201 return false; 202 } 203 204 @Override 205 public void invalidateDrawable(Drawable drawable) { 206 invalidate(drawable.getBounds()); 207 } 208 209 @Override 210 protected void dispatchDraw(Canvas canvas) { 211 super.dispatchDraw(canvas); 212 final int numDrawables = (mDrawables == null) ? 0 : mDrawables.size(); 213 for (int i = 0; i < numDrawables; ++i) { 214 mDrawables.get(i).draw(canvas); 215 } 216 } 217 218 @Override 219 protected void onLayout(boolean changed, int l, int t, int r, int b) { 220 // Noop: children are positioned absolutely 221 } 222 223 /* 224 The following invalidation overrides exist for the purpose of redirecting invalidation to 225 the host view. The overlay is not parented to the host view (since a View cannot be a 226 parent), so the invalidation cannot proceed through the normal parent hierarchy. 227 There is a built-in assumption that the overlay exactly covers the host view, therefore 228 the invalidation rectangles received do not need to be adjusted when forwarded to 229 the host view. 230 */ 231 232 @Override 233 public void invalidate(Rect dirty) { 234 super.invalidate(dirty); 235 if (mHostView != null) { 236 mHostView.invalidate(dirty); 237 } 238 } 239 240 @Override 241 public void invalidate(int l, int t, int r, int b) { 242 super.invalidate(l, t, r, b); 243 if (mHostView != null) { 244 mHostView.invalidate(l, t, r, b); 245 } 246 } 247 248 @Override 249 public void invalidate() { 250 super.invalidate(); 251 if (mHostView != null) { 252 mHostView.invalidate(); 253 } 254 } 255 256 @Override 257 void invalidate(boolean invalidateCache) { 258 super.invalidate(invalidateCache); 259 if (mHostView != null) { 260 mHostView.invalidate(invalidateCache); 261 } 262 } 263 264 @Override 265 void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) { 266 super.invalidateViewProperty(invalidateParent, forceRedraw); 267 if (mHostView != null) { 268 mHostView.invalidateViewProperty(invalidateParent, forceRedraw); 269 } 270 } 271 272 @Override 273 protected void invalidateParentCaches() { 274 super.invalidateParentCaches(); 275 if (mHostView != null) { 276 mHostView.invalidateParentCaches(); 277 } 278 } 279 280 @Override 281 protected void invalidateParentIfNeeded() { 282 super.invalidateParentIfNeeded(); 283 if (mHostView != null) { 284 mHostView.invalidateParentIfNeeded(); 285 } 286 } 287 288 public void invalidateChildFast(View child, final Rect dirty) { 289 if (mHostView != null) { 290 // Note: This is not a "fast" invalidation. Would be nice to instead invalidate 291 // using DisplayList properties and a dirty rect instead of causing a real 292 // invalidation of the host view 293 int left = child.mLeft; 294 int top = child.mTop; 295 if (!child.getMatrix().isIdentity()) { 296 child.transformRect(dirty); 297 } 298 dirty.offset(left, top); 299 mHostView.invalidate(dirty); 300 } 301 } 302 303 /** 304 * @hide 305 */ 306 @Override 307 protected ViewParent invalidateChildInParentFast(int left, int top, Rect dirty) { 308 if (mHostView instanceof ViewGroup) { 309 return ((ViewGroup) mHostView).invalidateChildInParentFast(left, top, dirty); 310 } 311 return null; 312 } 313 314 @Override 315 public ViewParent invalidateChildInParent(int[] location, Rect dirty) { 316 if (mHostView != null) { 317 dirty.offset(location[0], location[1]); 318 if (mHostView instanceof ViewGroup) { 319 location[0] = 0; 320 location[1] = 0; 321 super.invalidateChildInParent(location, dirty); 322 return ((ViewGroup) mHostView).invalidateChildInParent(location, dirty); 323 } else { 324 invalidate(dirty); 325 } 326 } 327 return null; 328 } 329 } 330 331 } 332