1 /* 2 * Copyright (C) 2014 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.graphics.Canvas; 19 import android.graphics.Matrix; 20 import android.widget.FrameLayout; 21 22 import java.util.ArrayList; 23 24 /** 25 * This view draws another View in an Overlay without changing the parent. It will not be drawn 26 * by its parent because its visibility is set to INVISIBLE, but will be drawn 27 * here using its render node. When the GhostView is set to INVISIBLE, the View it is 28 * shadowing will become VISIBLE and when the GhostView becomes VISIBLE, the shadowed 29 * view becomes INVISIBLE. 30 * @hide 31 */ 32 public class GhostView extends View { 33 private final View mView; 34 private int mReferences; 35 private boolean mBeingMoved; 36 GhostView(View view)37 private GhostView(View view) { 38 super(view.getContext()); 39 mView = view; 40 mView.mGhostView = this; 41 final ViewGroup parent = (ViewGroup) mView.getParent(); 42 setGhostedVisibility(View.INVISIBLE); 43 parent.mRecreateDisplayList = true; 44 parent.getDisplayList(); 45 } 46 47 @Override onDraw(Canvas canvas)48 protected void onDraw(Canvas canvas) { 49 if (canvas instanceof HardwareCanvas) { 50 HardwareCanvas hwCanvas = (HardwareCanvas) canvas; 51 mView.mRecreateDisplayList = true; 52 RenderNode renderNode = mView.getDisplayList(); 53 if (renderNode.isValid()) { 54 hwCanvas.insertReorderBarrier(); // enable shadow for this rendernode 55 hwCanvas.drawRenderNode(renderNode); 56 hwCanvas.insertInorderBarrier(); // re-disable reordering/shadows 57 } 58 } 59 } 60 setMatrix(Matrix matrix)61 public void setMatrix(Matrix matrix) { 62 mRenderNode.setAnimationMatrix(matrix); 63 } 64 65 @Override setVisibility(@isibility int visibility)66 public void setVisibility(@Visibility int visibility) { 67 super.setVisibility(visibility); 68 if (mView.mGhostView == this) { 69 int inverseVisibility = (visibility == View.VISIBLE) ? View.INVISIBLE : View.VISIBLE; 70 setGhostedVisibility(inverseVisibility); 71 } 72 } 73 setGhostedVisibility(int visibility)74 private void setGhostedVisibility(int visibility) { 75 mView.mViewFlags = (mView.mViewFlags & ~View.VISIBILITY_MASK) | visibility; 76 } 77 78 @Override onDetachedFromWindow()79 protected void onDetachedFromWindow() { 80 super.onDetachedFromWindow(); 81 if (!mBeingMoved) { 82 setGhostedVisibility(View.VISIBLE); 83 mView.mGhostView = null; 84 final ViewGroup parent = (ViewGroup) mView.getParent(); 85 if (parent != null) { 86 parent.mRecreateDisplayList = true; 87 parent.getDisplayList(); 88 } 89 } 90 } 91 calculateMatrix(View view, ViewGroup host, Matrix matrix)92 public static void calculateMatrix(View view, ViewGroup host, Matrix matrix) { 93 ViewGroup parent = (ViewGroup) view.getParent(); 94 matrix.reset(); 95 parent.transformMatrixToGlobal(matrix); 96 matrix.preTranslate(-parent.getScrollX(), -parent.getScrollY()); 97 host.transformMatrixToLocal(matrix); 98 } 99 addGhost(View view, ViewGroup viewGroup, Matrix matrix)100 public static GhostView addGhost(View view, ViewGroup viewGroup, Matrix matrix) { 101 if (!(view.getParent() instanceof ViewGroup)) { 102 throw new IllegalArgumentException("Ghosted views must be parented by a ViewGroup"); 103 } 104 ViewGroupOverlay overlay = viewGroup.getOverlay(); 105 ViewOverlay.OverlayViewGroup overlayViewGroup = overlay.mOverlayViewGroup; 106 GhostView ghostView = view.mGhostView; 107 int previousRefCount = 0; 108 if (ghostView != null) { 109 View oldParent = (View) ghostView.getParent(); 110 ViewGroup oldGrandParent = (ViewGroup) oldParent.getParent(); 111 if (oldGrandParent != overlayViewGroup) { 112 previousRefCount = ghostView.mReferences; 113 oldGrandParent.removeView(oldParent); 114 ghostView = null; 115 } 116 } 117 if (ghostView == null) { 118 if (matrix == null) { 119 matrix = new Matrix(); 120 calculateMatrix(view, viewGroup, matrix); 121 } 122 ghostView = new GhostView(view); 123 ghostView.setMatrix(matrix); 124 FrameLayout parent = new FrameLayout(view.getContext()); 125 parent.setClipChildren(false); 126 copySize(viewGroup, parent); 127 copySize(viewGroup, ghostView); 128 parent.addView(ghostView); 129 ArrayList<View> tempViews = new ArrayList<View>(); 130 int firstGhost = moveGhostViewsToTop(overlay.mOverlayViewGroup, tempViews); 131 insertIntoOverlay(overlay.mOverlayViewGroup, parent, ghostView, tempViews, firstGhost); 132 ghostView.mReferences = previousRefCount; 133 } else if (matrix != null) { 134 ghostView.setMatrix(matrix); 135 } 136 ghostView.mReferences++; 137 return ghostView; 138 } 139 addGhost(View view, ViewGroup viewGroup)140 public static GhostView addGhost(View view, ViewGroup viewGroup) { 141 return addGhost(view, viewGroup, null); 142 } 143 removeGhost(View view)144 public static void removeGhost(View view) { 145 GhostView ghostView = view.mGhostView; 146 if (ghostView != null) { 147 ghostView.mReferences--; 148 if (ghostView.mReferences == 0) { 149 ViewGroup parent = (ViewGroup) ghostView.getParent(); 150 ViewGroup grandParent = (ViewGroup) parent.getParent(); 151 grandParent.removeView(parent); 152 } 153 } 154 } 155 getGhost(View view)156 public static GhostView getGhost(View view) { 157 return view.mGhostView; 158 } 159 copySize(View from, View to)160 private static void copySize(View from, View to) { 161 to.setLeft(0); 162 to.setTop(0); 163 to.setRight(from.getWidth()); 164 to.setBottom(from.getHeight()); 165 } 166 167 /** 168 * Move the GhostViews to the end so that they are on top of other views and it is easier 169 * to do binary search for the correct location for the GhostViews in insertIntoOverlay. 170 * 171 * @return The index of the first GhostView or -1 if no GhostView is in the ViewGroup 172 */ moveGhostViewsToTop(ViewGroup viewGroup, ArrayList<View> tempViews)173 private static int moveGhostViewsToTop(ViewGroup viewGroup, ArrayList<View> tempViews) { 174 final int numChildren = viewGroup.getChildCount(); 175 if (numChildren == 0) { 176 return -1; 177 } else if (isGhostWrapper(viewGroup.getChildAt(numChildren - 1))) { 178 // GhostViews are already at the end 179 int firstGhost = numChildren - 1; 180 for (int i = numChildren - 2; i >= 0; i--) { 181 if (!isGhostWrapper(viewGroup.getChildAt(i))) { 182 break; 183 } 184 firstGhost = i; 185 } 186 return firstGhost; 187 } 188 189 // Remove all GhostViews from the middle 190 for (int i = numChildren - 2; i >= 0; i--) { 191 View child = viewGroup.getChildAt(i); 192 if (isGhostWrapper(child)) { 193 tempViews.add(child); 194 GhostView ghostView = (GhostView)((ViewGroup)child).getChildAt(0); 195 ghostView.mBeingMoved = true; 196 viewGroup.removeViewAt(i); 197 ghostView.mBeingMoved = false; 198 } 199 } 200 201 final int firstGhost; 202 if (tempViews.isEmpty()) { 203 firstGhost = -1; 204 } else { 205 firstGhost = viewGroup.getChildCount(); 206 // Add the GhostViews to the end 207 for (int i = tempViews.size() - 1; i >= 0; i--) { 208 viewGroup.addView(tempViews.get(i)); 209 } 210 tempViews.clear(); 211 } 212 return firstGhost; 213 } 214 215 /** 216 * Inserts a GhostView into the overlay's ViewGroup in the order in which they 217 * should be displayed by the UI. 218 */ insertIntoOverlay(ViewGroup viewGroup, ViewGroup wrapper, GhostView ghostView, ArrayList<View> tempParents, int firstGhost)219 private static void insertIntoOverlay(ViewGroup viewGroup, ViewGroup wrapper, 220 GhostView ghostView, ArrayList<View> tempParents, int firstGhost) { 221 if (firstGhost == -1) { 222 viewGroup.addView(wrapper); 223 } else { 224 ArrayList<View> viewParents = new ArrayList<View>(); 225 getParents(ghostView.mView, viewParents); 226 227 int index = getInsertIndex(viewGroup, viewParents, tempParents, firstGhost); 228 if (index < 0 || index >= viewGroup.getChildCount()) { 229 viewGroup.addView(wrapper); 230 } else { 231 viewGroup.addView(wrapper, index); 232 } 233 } 234 } 235 236 /** 237 * Find the index into the overlay to insert the GhostView based on the order that the 238 * views should be drawn. This keeps GhostViews layered in the same order 239 * that they are ordered in the UI. 240 */ getInsertIndex(ViewGroup overlayViewGroup, ArrayList<View> viewParents, ArrayList<View> tempParents, int firstGhost)241 private static int getInsertIndex(ViewGroup overlayViewGroup, ArrayList<View> viewParents, 242 ArrayList<View> tempParents, int firstGhost) { 243 int low = firstGhost; 244 int high = overlayViewGroup.getChildCount() - 1; 245 246 while (low <= high) { 247 int mid = (low + high) / 2; 248 ViewGroup wrapper = (ViewGroup) overlayViewGroup.getChildAt(mid); 249 GhostView midView = (GhostView) wrapper.getChildAt(0); 250 getParents(midView.mView, tempParents); 251 if (isOnTop(viewParents, tempParents)) { 252 low = mid + 1; 253 } else { 254 high = mid - 1; 255 } 256 tempParents.clear(); 257 } 258 259 return low; 260 } 261 262 /** 263 * Returns true if view is a GhostView's FrameLayout wrapper. 264 */ isGhostWrapper(View view)265 private static boolean isGhostWrapper(View view) { 266 if (view instanceof FrameLayout) { 267 FrameLayout frameLayout = (FrameLayout) view; 268 if (frameLayout.getChildCount() == 1) { 269 View child = frameLayout.getChildAt(0); 270 return child instanceof GhostView; 271 } 272 } 273 return false; 274 } 275 276 /** 277 * Returns true if viewParents is from a View that is on top of the comparedWith's view. 278 * The ArrayLists contain the ancestors of views in order from top most grandparent, to 279 * the view itself, in order. The goal is to find the first matching parent and then 280 * compare the draw order of the siblings. 281 */ isOnTop(ArrayList<View> viewParents, ArrayList<View> comparedWith)282 private static boolean isOnTop(ArrayList<View> viewParents, ArrayList<View> comparedWith) { 283 if (viewParents.isEmpty() || comparedWith.isEmpty() || 284 viewParents.get(0) != comparedWith.get(0)) { 285 // Not the same decorView -- arbitrary ordering 286 return true; 287 } 288 int depth = Math.min(viewParents.size(), comparedWith.size()); 289 for (int i = 1; i < depth; i++) { 290 View viewParent = viewParents.get(i); 291 View comparedWithParent = comparedWith.get(i); 292 293 if (viewParent != comparedWithParent) { 294 // i - 1 is the same parent, but these are different children. 295 return isOnTop(viewParent, comparedWithParent); 296 } 297 } 298 299 // one of these is the parent of the other 300 boolean isComparedWithTheParent = (comparedWith.size() == depth); 301 return isComparedWithTheParent; 302 } 303 304 /** 305 * Adds all the parents, grandparents, etc. of view to parents. 306 */ getParents(View view, ArrayList<View> parents)307 private static void getParents(View view, ArrayList<View> parents) { 308 ViewParent parent = view.getParent(); 309 if (parent != null && parent instanceof ViewGroup) { 310 getParents((View) parent, parents); 311 } 312 parents.add(view); 313 } 314 315 /** 316 * Returns true if view would be drawn on top of comparedWith or false otherwise. 317 * view and comparedWith are siblings with the same parent. This uses the logic 318 * that dispatchDraw uses to determine which View should be drawn first. 319 */ isOnTop(View view, View comparedWith)320 private static boolean isOnTop(View view, View comparedWith) { 321 ViewGroup parent = (ViewGroup) view.getParent(); 322 323 final int childrenCount = parent.getChildCount(); 324 final ArrayList<View> preorderedList = parent.buildOrderedChildList(); 325 final boolean customOrder = preorderedList == null 326 && parent.isChildrenDrawingOrderEnabled(); 327 328 // This default value shouldn't be used because both view and comparedWith 329 // should be in the list. If there is an error, then just return an arbitrary 330 // view is on top. 331 boolean isOnTop = true; 332 for (int i = 0; i < childrenCount; i++) { 333 int childIndex = customOrder ? parent.getChildDrawingOrder(childrenCount, i) : i; 334 final View child = (preorderedList == null) 335 ? parent.getChildAt(childIndex) : preorderedList.get(childIndex); 336 if (child == view) { 337 isOnTop = false; 338 break; 339 } else if (child == comparedWith) { 340 isOnTop = true; 341 break; 342 } 343 } 344 345 if (preorderedList != null) { 346 preorderedList.clear(); 347 } 348 return isOnTop; 349 } 350 } 351