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