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.content.Context; 19 import android.graphics.Canvas; 20 import android.graphics.Rect; 21 import android.graphics.drawable.Drawable; 22 23 import java.util.ArrayList; 24 25 /** 26 * An overlay is an extra layer that sits on top of a View (the "host view") 27 * which is drawn after all other content in that view (including children, 28 * if the view is a ViewGroup). Interaction with the overlay layer is done 29 * by adding and removing drawables. 30 * 31 * <p>An overlay requested from a ViewGroup is of type {@link ViewGroupOverlay}, 32 * which also supports adding and removing views.</p> 33 * 34 * @see View#getOverlay() View.getOverlay() 35 * @see ViewGroup#getOverlay() ViewGroup.getOverlay() 36 * @see ViewGroupOverlay 37 */ 38 public class ViewOverlay { 39 40 /** 41 * The actual container for the drawables (and views, if it's a ViewGroupOverlay). 42 * All of the management and rendering details for the overlay are handled in 43 * OverlayViewGroup. 44 */ 45 OverlayViewGroup mOverlayViewGroup; 46 47 ViewOverlay(Context context, View hostView) { 48 mOverlayViewGroup = new OverlayViewGroup(context, hostView); 49 } 50 51 /** 52 * Used internally by View and ViewGroup to handle drawing and invalidation 53 * of the overlay 54 * @return 55 */ 56 ViewGroup getOverlayView() { 57 return mOverlayViewGroup; 58 } 59 60 /** 61 * Adds a Drawable to the overlay. The bounds of the drawable should be relative to 62 * the host view. Any drawable added to the overlay should be removed when it is no longer 63 * needed or no longer visible. 64 * 65 * @param drawable The Drawable to be added to the overlay. This drawable will be 66 * drawn when the view redraws its overlay. 67 * @see #remove(Drawable) 68 */ 69 public void add(Drawable drawable) { 70 mOverlayViewGroup.add(drawable); 71 } 72 73 /** 74 * Removes the specified Drawable from the overlay. 75 * 76 * @param drawable The Drawable to be removed from the overlay. 77 * @see #add(Drawable) 78 */ 79 public void remove(Drawable drawable) { 80 mOverlayViewGroup.remove(drawable); 81 } 82 83 /** 84 * Removes all content from the overlay. 85 */ 86 public void clear() { 87 mOverlayViewGroup.clear(); 88 } 89 90 boolean isEmpty() { 91 return mOverlayViewGroup.isEmpty(); 92 } 93 94 /** 95 * OverlayViewGroup is a container that View and ViewGroup use to host 96 * drawables and views added to their overlays ({@link ViewOverlay} and 97 * {@link ViewGroupOverlay}, respectively). Drawables are added to the overlay 98 * via the add/remove methods in ViewOverlay, Views are added/removed via 99 * ViewGroupOverlay. These drawable and view objects are 100 * drawn whenever the view itself is drawn; first the view draws its own 101 * content (and children, if it is a ViewGroup), then it draws its overlay 102 * (if it has one). 103 * 104 * <p>Besides managing and drawing the list of drawables, this class serves 105 * two purposes: 106 * (1) it noops layout calls because children are absolutely positioned and 107 * (2) it forwards all invalidation calls to its host view. The invalidation 108 * redirect is necessary because the overlay is not a child of the host view 109 * and invalidation cannot therefore follow the normal path up through the 110 * parent hierarchy.</p> 111 * 112 * @see View#getOverlay() 113 * @see ViewGroup#getOverlay() 114 */ 115 static class OverlayViewGroup extends ViewGroup { 116 117 /** 118 * The View for which this is an overlay. Invalidations of the overlay are redirected to 119 * this host view. 120 */ 121 View mHostView; 122 123 /** 124 * The set of drawables to draw when the overlay is rendered. 125 */ 126 ArrayList<Drawable> mDrawables = null; 127 128 OverlayViewGroup(Context context, View hostView) { 129 super(context); 130 mHostView = hostView; 131 mAttachInfo = mHostView.mAttachInfo; 132 mRight = hostView.getWidth(); 133 mBottom = hostView.getHeight(); 134 } 135 136 public void add(Drawable drawable) { 137 if (mDrawables == null) { 138 139 mDrawables = new ArrayList<Drawable>(); 140 } 141 if (!mDrawables.contains(drawable)) { 142 // Make each drawable unique in the overlay; can't add it more than once 143 mDrawables.add(drawable); 144 invalidate(drawable.getBounds()); 145 drawable.setCallback(this); 146 } 147 } 148 149 public void remove(Drawable drawable) { 150 if (mDrawables != null) { 151 mDrawables.remove(drawable); 152 invalidate(drawable.getBounds()); 153 drawable.setCallback(null); 154 } 155 } 156 157 public void add(View child) { 158 if (child.getParent() instanceof ViewGroup) { 159 ViewGroup parent = (ViewGroup) child.getParent(); 160 if (parent != mHostView && parent.getParent() != null && 161 parent.mAttachInfo != null) { 162 // Moving to different container; figure out how to position child such that 163 // it is in the same location on the screen 164 int[] parentLocation = new int[2]; 165 int[] hostViewLocation = new int[2]; 166 parent.getLocationOnScreen(parentLocation); 167 mHostView.getLocationOnScreen(hostViewLocation); 168 child.offsetLeftAndRight(parentLocation[0] - hostViewLocation[0]); 169 child.offsetTopAndBottom(parentLocation[1] - hostViewLocation[1]); 170 } 171 parent.removeView(child); 172 } 173 super.addView(child); 174 } 175 176 public void remove(View view) { 177 super.removeView(view); 178 } 179 180 public void clear() { 181 removeAllViews(); 182 if (mDrawables != null) { 183 mDrawables.clear(); 184 } 185 } 186 187 boolean isEmpty() { 188 if (getChildCount() == 0 && 189 (mDrawables == null || mDrawables.size() == 0)) { 190 return true; 191 } 192 return false; 193 } 194 195 @Override 196 public void invalidateDrawable(Drawable drawable) { 197 invalidate(drawable.getBounds()); 198 } 199 200 @Override 201 protected void dispatchDraw(Canvas canvas) { 202 super.dispatchDraw(canvas); 203 final int numDrawables = (mDrawables == null) ? 0 : mDrawables.size(); 204 for (int i = 0; i < numDrawables; ++i) { 205 mDrawables.get(i).draw(canvas); 206 } 207 } 208 209 @Override 210 protected void onLayout(boolean changed, int l, int t, int r, int b) { 211 // Noop: children are positioned absolutely 212 } 213 214 /* 215 The following invalidation overrides exist for the purpose of redirecting invalidation to 216 the host view. The overlay is not parented to the host view (since a View cannot be a 217 parent), so the invalidation cannot proceed through the normal parent hierarchy. 218 There is a built-in assumption that the overlay exactly covers the host view, therefore 219 the invalidation rectangles received do not need to be adjusted when forwarded to 220 the host view. 221 */ 222 223 @Override 224 public void invalidate(Rect dirty) { 225 super.invalidate(dirty); 226 if (mHostView != null) { 227 mHostView.invalidate(dirty); 228 } 229 } 230 231 @Override 232 public void invalidate(int l, int t, int r, int b) { 233 super.invalidate(l, t, r, b); 234 if (mHostView != null) { 235 mHostView.invalidate(l, t, r, b); 236 } 237 } 238 239 @Override 240 public void invalidate() { 241 super.invalidate(); 242 if (mHostView != null) { 243 mHostView.invalidate(); 244 } 245 } 246 247 @Override 248 void invalidate(boolean invalidateCache) { 249 super.invalidate(invalidateCache); 250 if (mHostView != null) { 251 mHostView.invalidate(invalidateCache); 252 } 253 } 254 255 @Override 256 void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) { 257 super.invalidateViewProperty(invalidateParent, forceRedraw); 258 if (mHostView != null) { 259 mHostView.invalidateViewProperty(invalidateParent, forceRedraw); 260 } 261 } 262 263 @Override 264 protected void invalidateParentCaches() { 265 super.invalidateParentCaches(); 266 if (mHostView != null) { 267 mHostView.invalidateParentCaches(); 268 } 269 } 270 271 @Override 272 protected void invalidateParentIfNeeded() { 273 super.invalidateParentIfNeeded(); 274 if (mHostView != null) { 275 mHostView.invalidateParentIfNeeded(); 276 } 277 } 278 279 public void invalidateChildFast(View child, final Rect dirty) { 280 if (mHostView != null) { 281 // Note: This is not a "fast" invalidation. Would be nice to instead invalidate 282 // using DisplayList properties and a dirty rect instead of causing a real 283 // invalidation of the host view 284 int left = child.mLeft; 285 int top = child.mTop; 286 if (!child.getMatrix().isIdentity()) { 287 child.transformRect(dirty); 288 } 289 dirty.offset(left, top); 290 mHostView.invalidate(dirty); 291 } 292 } 293 294 @Override 295 public ViewParent invalidateChildInParent(int[] location, Rect dirty) { 296 if (mHostView != null) { 297 dirty.offset(location[0], location[1]); 298 if (mHostView instanceof ViewGroup) { 299 location[0] = 0; 300 location[1] = 0; 301 super.invalidateChildInParent(location, dirty); 302 return ((ViewGroup) mHostView).invalidateChildInParent(location, dirty); 303 } else { 304 invalidate(dirty); 305 } 306 } 307 return null; 308 } 309 } 310 311 } 312