Skip to content

Commit 8120652

Browse files
author
George Mount
committed
Order GhostViews so that they display in their ghosted view's order.
Bug 17671834 GhostViews can be added in arbitrary order since they are added depending on how they are insterted into the TransitionValues maps. This CL does two things: it ensures that GhostViews are always pulled to the top of the Overlay and inserts GhostViews into the Overlay in the order that their ghosted views are drawn. Change-Id: I9f68105126834cc8f30a6cfe5d58fc3c51465afd
1 parent 922fcc2 commit 8120652

2 files changed

Lines changed: 191 additions & 8 deletions

File tree

core/java/android/view/GhostView.java

Lines changed: 190 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import android.graphics.Matrix;
2020
import android.widget.FrameLayout;
2121

22+
import java.util.ArrayList;
23+
2224
/**
2325
* This view draws another View in an Overlay without changing the parent. It will not be drawn
2426
* by its parent because its visibility is set to INVISIBLE, but will be drawn
@@ -30,6 +32,7 @@
3032
public class GhostView extends View {
3133
private final View mView;
3234
private int mReferences;
35+
private boolean mBeingMoved;
3336

3437
private GhostView(View view) {
3538
super(view.getContext());
@@ -75,12 +78,14 @@ private void setGhostedVisibility(int visibility) {
7578
@Override
7679
protected void onDetachedFromWindow() {
7780
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+
}
8489
}
8590
}
8691

@@ -121,7 +126,9 @@ public static GhostView addGhost(View view, ViewGroup viewGroup, Matrix matrix)
121126
copySize(viewGroup, parent);
122127
copySize(viewGroup, ghostView);
123128
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);
125132
ghostView.mReferences = previousRefCount;
126133
} else if (matrix != null) {
127134
ghostView.setMatrix(matrix);
@@ -156,4 +163,180 @@ private static void copySize(View from, View to) {
156163
to.setRight(from.getWidth());
157164
to.setBottom(from.getHeight());
158165
}
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+
}
159342
}

core/java/android/view/ViewGroup.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3298,7 +3298,7 @@ private boolean hasChildWithZ() {
32983298
* Uses a stable, insertion sort which is commonly O(n) for ViewGroups with very few elevated
32993299
* children.
33003300
*/
3301-
private ArrayList<View> buildOrderedChildList() {
3301+
ArrayList<View> buildOrderedChildList() {
33023302
final int count = mChildrenCount;
33033303
if (count <= 1 || !hasChildWithZ()) return null;
33043304

0 commit comments

Comments
 (0)