summaryrefslogtreecommitdiff
path: root/android/view/GhostView.java
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2017-09-15 17:58:39 -0400
committerJustin Klaassen <justinklaassen@google.com>2017-09-15 17:58:39 -0400
commit10d07c88d69cc64f73a069163e7ea5ba2519a099 (patch)
tree8dbd149eb350320a29c3d10e7ad3201de1c5cbee /android/view/GhostView.java
parent677516fb6b6f207d373984757d3d9450474b6b00 (diff)
downloadandroid-28-10d07c88d69cc64f73a069163e7ea5ba2519a099.tar.gz
Import Android SDK Platform PI [4335822]
/google/data/ro/projects/android/fetch_artifact \ --bid 4335822 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4335822.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: Ic8f04be005a71c2b9abeaac754d8da8d6f9a2c32
Diffstat (limited to 'android/view/GhostView.java')
-rw-r--r--android/view/GhostView.java344
1 files changed, 344 insertions, 0 deletions
diff --git a/android/view/GhostView.java b/android/view/GhostView.java
new file mode 100644
index 00000000..d1b96baa
--- /dev/null
+++ b/android/view/GhostView.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view;
+
+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
+ * here using its render node. When the GhostView is set to INVISIBLE, the View it is
+ * shadowing will become VISIBLE and when the GhostView becomes VISIBLE, the shadowed
+ * view becomes INVISIBLE.
+ * @hide
+ */
+public class GhostView extends View {
+ private final View mView;
+ private int mReferences;
+ private boolean mBeingMoved;
+
+ private GhostView(View view) {
+ super(view.getContext());
+ mView = view;
+ mView.mGhostView = this;
+ final ViewGroup parent = (ViewGroup) mView.getParent();
+ mView.setTransitionVisibility(View.INVISIBLE);
+ parent.invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (canvas instanceof DisplayListCanvas) {
+ DisplayListCanvas dlCanvas = (DisplayListCanvas) canvas;
+ mView.mRecreateDisplayList = true;
+ RenderNode renderNode = mView.updateDisplayListIfDirty();
+ if (renderNode.isValid()) {
+ dlCanvas.insertReorderBarrier(); // enable shadow for this rendernode
+ dlCanvas.drawRenderNode(renderNode);
+ dlCanvas.insertInorderBarrier(); // re-disable reordering/shadows
+ }
+ }
+ }
+
+ public void setMatrix(Matrix matrix) {
+ mRenderNode.setAnimationMatrix(matrix);
+ }
+
+ @Override
+ public void setVisibility(@Visibility int visibility) {
+ super.setVisibility(visibility);
+ if (mView.mGhostView == this) {
+ int inverseVisibility = (visibility == View.VISIBLE) ? View.INVISIBLE : View.VISIBLE;
+ mView.setTransitionVisibility(inverseVisibility);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (!mBeingMoved) {
+ mView.setTransitionVisibility(View.VISIBLE);
+ mView.mGhostView = null;
+ final ViewGroup parent = (ViewGroup) mView.getParent();
+ if (parent != null) {
+ parent.invalidate();
+ }
+ }
+ }
+
+ public static void calculateMatrix(View view, ViewGroup host, Matrix matrix) {
+ ViewGroup parent = (ViewGroup) view.getParent();
+ matrix.reset();
+ parent.transformMatrixToGlobal(matrix);
+ matrix.preTranslate(-parent.getScrollX(), -parent.getScrollY());
+ host.transformMatrixToLocal(matrix);
+ }
+
+ public static GhostView addGhost(View view, ViewGroup viewGroup, Matrix matrix) {
+ if (!(view.getParent() instanceof ViewGroup)) {
+ throw new IllegalArgumentException("Ghosted views must be parented by a ViewGroup");
+ }
+ ViewGroupOverlay overlay = viewGroup.getOverlay();
+ ViewOverlay.OverlayViewGroup overlayViewGroup = overlay.mOverlayViewGroup;
+ GhostView ghostView = view.mGhostView;
+ int previousRefCount = 0;
+ if (ghostView != null) {
+ View oldParent = (View) ghostView.getParent();
+ ViewGroup oldGrandParent = (ViewGroup) oldParent.getParent();
+ if (oldGrandParent != overlayViewGroup) {
+ previousRefCount = ghostView.mReferences;
+ oldGrandParent.removeView(oldParent);
+ ghostView = null;
+ }
+ }
+ if (ghostView == null) {
+ if (matrix == null) {
+ matrix = new Matrix();
+ calculateMatrix(view, viewGroup, matrix);
+ }
+ ghostView = new GhostView(view);
+ ghostView.setMatrix(matrix);
+ FrameLayout parent = new FrameLayout(view.getContext());
+ parent.setClipChildren(false);
+ copySize(viewGroup, parent);
+ copySize(viewGroup, ghostView);
+ parent.addView(ghostView);
+ 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);
+ }
+ ghostView.mReferences++;
+ return ghostView;
+ }
+
+ public static GhostView addGhost(View view, ViewGroup viewGroup) {
+ return addGhost(view, viewGroup, null);
+ }
+
+ public static void removeGhost(View view) {
+ GhostView ghostView = view.mGhostView;
+ if (ghostView != null) {
+ ghostView.mReferences--;
+ if (ghostView.mReferences == 0) {
+ ViewGroup parent = (ViewGroup) ghostView.getParent();
+ ViewGroup grandParent = (ViewGroup) parent.getParent();
+ grandParent.removeView(parent);
+ }
+ }
+ }
+
+ public static GhostView getGhost(View view) {
+ return view.mGhostView;
+ }
+
+ private static void copySize(View from, View to) {
+ to.setLeft(0);
+ to.setTop(0);
+ 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();
+
+ // This default value shouldn't be used because both view and comparedWith
+ // should be in the list. If there is an error, then just return an arbitrary
+ // view is on top.
+ boolean isOnTop = true;
+ 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) {
+ isOnTop = false;
+ break;
+ } else if (child == comparedWith) {
+ isOnTop = true;
+ break;
+ }
+ }
+
+ if (preorderedList != null) {
+ preorderedList.clear();
+ }
+ return isOnTop;
+ }
+}