|
19 | 19 | import android.graphics.Matrix; |
20 | 20 | import android.widget.FrameLayout; |
21 | 21 |
|
| 22 | +import java.util.ArrayList; |
| 23 | + |
22 | 24 | /** |
23 | 25 | * This view draws another View in an Overlay without changing the parent. It will not be drawn |
24 | 26 | * by its parent because its visibility is set to INVISIBLE, but will be drawn |
|
30 | 32 | public class GhostView extends View { |
31 | 33 | private final View mView; |
32 | 34 | private int mReferences; |
| 35 | + private boolean mBeingMoved; |
33 | 36 |
|
34 | 37 | private GhostView(View view) { |
35 | 38 | super(view.getContext()); |
@@ -75,12 +78,14 @@ private void setGhostedVisibility(int visibility) { |
75 | 78 | @Override |
76 | 79 | protected void onDetachedFromWindow() { |
77 | 80 | super.onDetachedFromWindow(); |
78 | | - setGhostedVisibility(View.VISIBLE); |
79 | | - mView.mGhostView = null; |
80 | | - final ViewGroup parent = (ViewGroup) mView.getParent(); |
81 | | - if (parent != null) { |
82 | | - parent.mRecreateDisplayList = true; |
83 | | - parent.getDisplayList(); |
| 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 | + } |
84 | 89 | } |
85 | 90 | } |
86 | 91 |
|
@@ -121,7 +126,9 @@ public static GhostView addGhost(View view, ViewGroup viewGroup, Matrix matrix) |
121 | 126 | copySize(viewGroup, parent); |
122 | 127 | copySize(viewGroup, ghostView); |
123 | 128 | parent.addView(ghostView); |
124 | | - overlay.add(parent); |
| 129 | + ArrayList<View> tempViews = new ArrayList<View>(); |
| 130 | + int firstGhost = moveGhostViewsToTop(overlay.mOverlayViewGroup, tempViews); |
| 131 | + insertIntoOverlay(overlay.mOverlayViewGroup, parent, ghostView, tempViews, firstGhost); |
125 | 132 | ghostView.mReferences = previousRefCount; |
126 | 133 | } else if (matrix != null) { |
127 | 134 | ghostView.setMatrix(matrix); |
@@ -156,4 +163,180 @@ private static void copySize(View from, View to) { |
156 | 163 | to.setRight(from.getWidth()); |
157 | 164 | to.setBottom(from.getHeight()); |
158 | 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 | + */ |
| 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 | + */ |
| 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 | + */ |
| 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 | + */ |
| 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 | + */ |
| 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 | + */ |
| 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 | + */ |
| 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 | + for (int i = 0; i < childrenCount; i++) { |
| 328 | + int childIndex = customOrder ? parent.getChildDrawingOrder(childrenCount, i) : i; |
| 329 | + final View child = (preorderedList == null) |
| 330 | + ? parent.getChildAt(childIndex) : preorderedList.get(childIndex); |
| 331 | + if (child == view) { |
| 332 | + return false; |
| 333 | + } else if (child == comparedWith) { |
| 334 | + return true; |
| 335 | + } |
| 336 | + } |
| 337 | + |
| 338 | + // Shouldn't get here. Neither of the children is in the parent. |
| 339 | + // Just return an arbitrary one. |
| 340 | + return true; |
| 341 | + } |
159 | 342 | } |
0 commit comments