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