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 */ 16package android.view; 17 18import android.animation.LayoutTransition; 19import android.content.Context; 20import android.graphics.Canvas; 21import android.graphics.Rect; 22import android.graphics.drawable.Drawable; 23 24import 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 */ 39public 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 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 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 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 162 protected boolean verifyDrawable(Drawable who) { 163 return super.verifyDrawable(who) || (mDrawables != null && mDrawables.contains(who)); 164 } 165 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 193 public void remove(View view) { 194 super.removeView(view); 195 } 196 197 public void clear() { 198 removeAllViews(); 199 if (mDrawables != null) { 200 mDrawables.clear(); 201 } 202 } 203 204 boolean isEmpty() { 205 if (getChildCount() == 0 && 206 (mDrawables == null || mDrawables.size() == 0)) { 207 return true; 208 } 209 return false; 210 } 211 212 @Override 213 public void invalidateDrawable(Drawable drawable) { 214 invalidate(drawable.getBounds()); 215 } 216 217 @Override 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 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 241 public void invalidate(Rect dirty) { 242 super.invalidate(dirty); 243 if (mHostView != null) { 244 mHostView.invalidate(dirty); 245 } 246 } 247 248 @Override 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 257 public void invalidate() { 258 super.invalidate(); 259 if (mHostView != null) { 260 mHostView.invalidate(); 261 } 262 } 263 264 @Override 265 void invalidate(boolean invalidateCache) { 266 super.invalidate(invalidateCache); 267 if (mHostView != null) { 268 mHostView.invalidate(invalidateCache); 269 } 270 } 271 272 @Override 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 281 protected void invalidateParentCaches() { 282 super.invalidateParentCaches(); 283 if (mHostView != null) { 284 mHostView.invalidateParentCaches(); 285 } 286 } 287 288 @Override 289 protected void invalidateParentIfNeeded() { 290 super.invalidateParentIfNeeded(); 291 if (mHostView != null) { 292 mHostView.invalidateParentIfNeeded(); 293 } 294 } 295 296 /** 297 * @hide 298 */ 299 @Override 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 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 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