am 8120652b: Order GhostViews so that they display in their ghosted view\'s order.

* commit '8120652bb1155d762d945c1a3bf1636b6825dbd2':
  Order GhostViews so that they display in their ghosted view's order.
This commit is contained in:
George Mount
2014-09-29 02:55:37 +00:00
committed by Android Git Automerger
2 changed files with 191 additions and 8 deletions

View File

@@ -19,6 +19,8 @@ import android.graphics.Canvas;
import android.graphics.Matrix;
import android.widget.FrameLayout;
import java.util.ArrayList;
/**
* This view draws another View in an Overlay without changing the parent. It will not be drawn
* by its parent because its visibility is set to INVISIBLE, but will be drawn
@@ -30,6 +32,7 @@ import android.widget.FrameLayout;
public class GhostView extends View {
private final View mView;
private int mReferences;
private boolean mBeingMoved;
private GhostView(View view) {
super(view.getContext());
@@ -75,12 +78,14 @@ public class GhostView extends View {
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
setGhostedVisibility(View.VISIBLE);
mView.mGhostView = null;
final ViewGroup parent = (ViewGroup) mView.getParent();
if (parent != null) {
parent.mRecreateDisplayList = true;
parent.getDisplayList();
if (!mBeingMoved) {
setGhostedVisibility(View.VISIBLE);
mView.mGhostView = null;
final ViewGroup parent = (ViewGroup) mView.getParent();
if (parent != null) {
parent.mRecreateDisplayList = true;
parent.getDisplayList();
}
}
}
@@ -121,7 +126,9 @@ public class GhostView extends View {
copySize(viewGroup, parent);
copySize(viewGroup, ghostView);
parent.addView(ghostView);
overlay.add(parent);
ArrayList<View> tempViews = new ArrayList<View>();
int firstGhost = moveGhostViewsToTop(overlay.mOverlayViewGroup, tempViews);
insertIntoOverlay(overlay.mOverlayViewGroup, parent, ghostView, tempViews, firstGhost);
ghostView.mReferences = previousRefCount;
} else if (matrix != null) {
ghostView.setMatrix(matrix);
@@ -156,4 +163,180 @@ public class GhostView extends View {
to.setRight(from.getWidth());
to.setBottom(from.getHeight());
}
/**
* Move the GhostViews to the end so that they are on top of other views and it is easier
* to do binary search for the correct location for the GhostViews in insertIntoOverlay.
*
* @return The index of the first GhostView or -1 if no GhostView is in the ViewGroup
*/
private static int moveGhostViewsToTop(ViewGroup viewGroup, ArrayList<View> tempViews) {
final int numChildren = viewGroup.getChildCount();
if (numChildren == 0) {
return -1;
} else if (isGhostWrapper(viewGroup.getChildAt(numChildren - 1))) {
// GhostViews are already at the end
int firstGhost = numChildren - 1;
for (int i = numChildren - 2; i >= 0; i--) {
if (!isGhostWrapper(viewGroup.getChildAt(i))) {
break;
}
firstGhost = i;
}
return firstGhost;
}
// Remove all GhostViews from the middle
for (int i = numChildren - 2; i >= 0; i--) {
View child = viewGroup.getChildAt(i);
if (isGhostWrapper(child)) {
tempViews.add(child);
GhostView ghostView = (GhostView)((ViewGroup)child).getChildAt(0);
ghostView.mBeingMoved = true;
viewGroup.removeViewAt(i);
ghostView.mBeingMoved = false;
}
}
final int firstGhost;
if (tempViews.isEmpty()) {
firstGhost = -1;
} else {
firstGhost = viewGroup.getChildCount();
// Add the GhostViews to the end
for (int i = tempViews.size() - 1; i >= 0; i--) {
viewGroup.addView(tempViews.get(i));
}
tempViews.clear();
}
return firstGhost;
}
/**
* Inserts a GhostView into the overlay's ViewGroup in the order in which they
* should be displayed by the UI.
*/
private static void insertIntoOverlay(ViewGroup viewGroup, ViewGroup wrapper,
GhostView ghostView, ArrayList<View> tempParents, int firstGhost) {
if (firstGhost == -1) {
viewGroup.addView(wrapper);
} else {
ArrayList<View> viewParents = new ArrayList<View>();
getParents(ghostView.mView, viewParents);
int index = getInsertIndex(viewGroup, viewParents, tempParents, firstGhost);
if (index < 0 || index >= viewGroup.getChildCount()) {
viewGroup.addView(wrapper);
} else {
viewGroup.addView(wrapper, index);
}
}
}
/**
* Find the index into the overlay to insert the GhostView based on the order that the
* views should be drawn. This keeps GhostViews layered in the same order
* that they are ordered in the UI.
*/
private static int getInsertIndex(ViewGroup overlayViewGroup, ArrayList<View> viewParents,
ArrayList<View> tempParents, int firstGhost) {
int low = firstGhost;
int high = overlayViewGroup.getChildCount() - 1;
while (low <= high) {
int mid = (low + high) / 2;
ViewGroup wrapper = (ViewGroup) overlayViewGroup.getChildAt(mid);
GhostView midView = (GhostView) wrapper.getChildAt(0);
getParents(midView.mView, tempParents);
if (isOnTop(viewParents, tempParents)) {
low = mid + 1;
} else {
high = mid - 1;
}
tempParents.clear();
}
return low;
}
/**
* Returns true if view is a GhostView's FrameLayout wrapper.
*/
private static boolean isGhostWrapper(View view) {
if (view instanceof FrameLayout) {
FrameLayout frameLayout = (FrameLayout) view;
if (frameLayout.getChildCount() == 1) {
View child = frameLayout.getChildAt(0);
return child instanceof GhostView;
}
}
return false;
}
/**
* Returns true if viewParents is from a View that is on top of the comparedWith's view.
* The ArrayLists contain the ancestors of views in order from top most grandparent, to
* the view itself, in order. The goal is to find the first matching parent and then
* compare the draw order of the siblings.
*/
private static boolean isOnTop(ArrayList<View> viewParents, ArrayList<View> comparedWith) {
if (viewParents.isEmpty() || comparedWith.isEmpty() ||
viewParents.get(0) != comparedWith.get(0)) {
// Not the same decorView -- arbitrary ordering
return true;
}
int depth = Math.min(viewParents.size(), comparedWith.size());
for (int i = 1; i < depth; i++) {
View viewParent = viewParents.get(i);
View comparedWithParent = comparedWith.get(i);
if (viewParent != comparedWithParent) {
// i - 1 is the same parent, but these are different children.
return isOnTop(viewParent, comparedWithParent);
}
}
// one of these is the parent of the other
boolean isComparedWithTheParent = (comparedWith.size() == depth);
return isComparedWithTheParent;
}
/**
* Adds all the parents, grandparents, etc. of view to parents.
*/
private static void getParents(View view, ArrayList<View> parents) {
ViewParent parent = view.getParent();
if (parent != null && parent instanceof ViewGroup) {
getParents((View) parent, parents);
}
parents.add(view);
}
/**
* Returns true if view would be drawn on top of comparedWith or false otherwise.
* view and comparedWith are siblings with the same parent. This uses the logic
* that dispatchDraw uses to determine which View should be drawn first.
*/
private static boolean isOnTop(View view, View comparedWith) {
ViewGroup parent = (ViewGroup) view.getParent();
final int childrenCount = parent.getChildCount();
final ArrayList<View> preorderedList = parent.buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& parent.isChildrenDrawingOrderEnabled();
for (int i = 0; i < childrenCount; i++) {
int childIndex = customOrder ? parent.getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? parent.getChildAt(childIndex) : preorderedList.get(childIndex);
if (child == view) {
return false;
} else if (child == comparedWith) {
return true;
}
}
// Shouldn't get here. Neither of the children is in the parent.
// Just return an arbitrary one.
return true;
}
}

View File

@@ -3298,7 +3298,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* Uses a stable, insertion sort which is commonly O(n) for ViewGroups with very few elevated
* children.
*/
private ArrayList<View> buildOrderedChildList() {
ArrayList<View> buildOrderedChildList() {
final int count = mChildrenCount;
if (count <= 1 || !hasChildWithZ()) return null;