diff options
author | Justin Klaassen <justinklaassen@google.com> | 2017-09-15 17:58:39 -0400 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2017-09-15 17:58:39 -0400 |
commit | 10d07c88d69cc64f73a069163e7ea5ba2519a099 (patch) | |
tree | 8dbd149eb350320a29c3d10e7ad3201de1c5cbee /android/view/GhostView.java | |
parent | 677516fb6b6f207d373984757d3d9450474b6b00 (diff) | |
download | android-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.java | 344 |
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; + } +} |