/*
 * 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
/** * 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.
Returns:The index of the first GhostView or -1 if no GhostView is in the ViewGroup
/** * 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.
/** * 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.
/** * 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.
/** * 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.
/** * 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.
/** * 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.
/** * 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; } }