blob: bc38e1a271f1751b4474b7202535afae9e160a6a [file] [log] [blame]
George Mount807e40c2014-07-08 17:25:25 -07001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package android.view;
17
18import android.graphics.Canvas;
19import android.graphics.Matrix;
Dake Gu0017ef92014-08-12 14:54:53 -070020import android.widget.FrameLayout;
George Mount807e40c2014-07-08 17:25:25 -070021
George Mount81206522014-09-26 21:53:39 -070022import java.util.ArrayList;
23
George Mount807e40c2014-07-08 17:25:25 -070024/**
25 * This view draws another View in an Overlay without changing the parent. It will not be drawn
26 * by its parent because its visibility is set to INVISIBLE, but will be drawn
27 * here using its render node. When the GhostView is set to INVISIBLE, the View it is
28 * shadowing will become VISIBLE and when the GhostView becomes VISIBLE, the shadowed
29 * view becomes INVISIBLE.
30 * @hide
31 */
32public class GhostView extends View {
George Mount807e40c2014-07-08 17:25:25 -070033 private final View mView;
Dake Gu0017ef92014-08-12 14:54:53 -070034 private int mReferences;
George Mount81206522014-09-26 21:53:39 -070035 private boolean mBeingMoved;
George Mount807e40c2014-07-08 17:25:25 -070036
Dake Gu0017ef92014-08-12 14:54:53 -070037 private GhostView(View view) {
George Mount807e40c2014-07-08 17:25:25 -070038 super(view.getContext());
39 mView = view;
George Mount807e40c2014-07-08 17:25:25 -070040 mView.mGhostView = this;
41 final ViewGroup parent = (ViewGroup) mView.getParent();
George Mount807e40c2014-07-08 17:25:25 -070042 setGhostedVisibility(View.INVISIBLE);
43 parent.mRecreateDisplayList = true;
Chris Craik31a2d062015-05-01 14:22:47 -070044 parent.updateDisplayListIfDirty();
George Mount807e40c2014-07-08 17:25:25 -070045 }
46
47 @Override
48 protected void onDraw(Canvas canvas) {
Chris Craikf6829a02015-03-10 10:28:59 -070049 if (canvas instanceof DisplayListCanvas) {
50 DisplayListCanvas dlCanvas = (DisplayListCanvas) canvas;
George Mount807e40c2014-07-08 17:25:25 -070051 mView.mRecreateDisplayList = true;
Chris Craik31a2d062015-05-01 14:22:47 -070052 RenderNode renderNode = mView.updateDisplayListIfDirty();
George Mount807e40c2014-07-08 17:25:25 -070053 if (renderNode.isValid()) {
Chris Craikf6829a02015-03-10 10:28:59 -070054 dlCanvas.insertReorderBarrier(); // enable shadow for this rendernode
55 dlCanvas.drawRenderNode(renderNode);
56 dlCanvas.insertInorderBarrier(); // re-disable reordering/shadows
George Mount807e40c2014-07-08 17:25:25 -070057 }
George Mount807e40c2014-07-08 17:25:25 -070058 }
59 }
60
George Mountfe361d22014-07-08 17:25:25 -070061 public void setMatrix(Matrix matrix) {
62 mRenderNode.setAnimationMatrix(matrix);
63 }
64
George Mount807e40c2014-07-08 17:25:25 -070065 @Override
66 public void setVisibility(@Visibility int visibility) {
67 super.setVisibility(visibility);
68 if (mView.mGhostView == this) {
69 int inverseVisibility = (visibility == View.VISIBLE) ? View.INVISIBLE : View.VISIBLE;
70 setGhostedVisibility(inverseVisibility);
71 }
72 }
73
74 private void setGhostedVisibility(int visibility) {
75 mView.mViewFlags = (mView.mViewFlags & ~View.VISIBILITY_MASK) | visibility;
76 }
77
78 @Override
79 protected void onDetachedFromWindow() {
80 super.onDetachedFromWindow();
George Mount81206522014-09-26 21:53:39 -070081 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;
Chris Craik31a2d062015-05-01 14:22:47 -070087 parent.updateDisplayListIfDirty();
George Mount81206522014-09-26 21:53:39 -070088 }
George Mountfe361d22014-07-08 17:25:25 -070089 }
George Mount807e40c2014-07-08 17:25:25 -070090 }
91
George Mountfe361d22014-07-08 17:25:25 -070092 public static void calculateMatrix(View view, ViewGroup host, Matrix matrix) {
93 ViewGroup parent = (ViewGroup) view.getParent();
94 matrix.reset();
95 parent.transformMatrixToGlobal(matrix);
96 matrix.preTranslate(-parent.getScrollX(), -parent.getScrollY());
97 host.transformMatrixToLocal(matrix);
George Mount807e40c2014-07-08 17:25:25 -070098 }
99
George Mountfe361d22014-07-08 17:25:25 -0700100 public static GhostView addGhost(View view, ViewGroup viewGroup, Matrix matrix) {
George Mount807e40c2014-07-08 17:25:25 -0700101 if (!(view.getParent() instanceof ViewGroup)) {
102 throw new IllegalArgumentException("Ghosted views must be parented by a ViewGroup");
103 }
104 ViewGroupOverlay overlay = viewGroup.getOverlay();
105 ViewOverlay.OverlayViewGroup overlayViewGroup = overlay.mOverlayViewGroup;
106 GhostView ghostView = view.mGhostView;
Dake Gu0017ef92014-08-12 14:54:53 -0700107 int previousRefCount = 0;
George Mount807e40c2014-07-08 17:25:25 -0700108 if (ghostView != null) {
Dake Gu0017ef92014-08-12 14:54:53 -0700109 View oldParent = (View) ghostView.getParent();
110 ViewGroup oldGrandParent = (ViewGroup) oldParent.getParent();
111 if (oldGrandParent != overlayViewGroup) {
112 previousRefCount = ghostView.mReferences;
113 oldGrandParent.removeView(oldParent);
George Mount807e40c2014-07-08 17:25:25 -0700114 ghostView = null;
115 }
116 }
117 if (ghostView == null) {
George Mountfe361d22014-07-08 17:25:25 -0700118 if (matrix == null) {
119 matrix = new Matrix();
120 calculateMatrix(view, viewGroup, matrix);
121 }
Dake Gu0017ef92014-08-12 14:54:53 -0700122 ghostView = new GhostView(view);
123 ghostView.setMatrix(matrix);
124 FrameLayout parent = new FrameLayout(view.getContext());
125 parent.setClipChildren(false);
126 copySize(viewGroup, parent);
127 copySize(viewGroup, ghostView);
128 parent.addView(ghostView);
George Mount81206522014-09-26 21:53:39 -0700129 ArrayList<View> tempViews = new ArrayList<View>();
130 int firstGhost = moveGhostViewsToTop(overlay.mOverlayViewGroup, tempViews);
131 insertIntoOverlay(overlay.mOverlayViewGroup, parent, ghostView, tempViews, firstGhost);
Dake Gu0017ef92014-08-12 14:54:53 -0700132 ghostView.mReferences = previousRefCount;
133 } else if (matrix != null) {
134 ghostView.setMatrix(matrix);
George Mount807e40c2014-07-08 17:25:25 -0700135 }
Dake Gu0017ef92014-08-12 14:54:53 -0700136 ghostView.mReferences++;
George Mount807e40c2014-07-08 17:25:25 -0700137 return ghostView;
138 }
139
George Mountfe361d22014-07-08 17:25:25 -0700140 public static GhostView addGhost(View view, ViewGroup viewGroup) {
141 return addGhost(view, viewGroup, null);
142 }
143
George Mount807e40c2014-07-08 17:25:25 -0700144 public static void removeGhost(View view) {
145 GhostView ghostView = view.mGhostView;
146 if (ghostView != null) {
Dake Gu0017ef92014-08-12 14:54:53 -0700147 ghostView.mReferences--;
148 if (ghostView.mReferences == 0) {
149 ViewGroup parent = (ViewGroup) ghostView.getParent();
150 ViewGroup grandParent = (ViewGroup) parent.getParent();
151 grandParent.removeView(parent);
152 }
George Mount807e40c2014-07-08 17:25:25 -0700153 }
154 }
155
156 public static GhostView getGhost(View view) {
157 return view.mGhostView;
158 }
Dake Gu0017ef92014-08-12 14:54:53 -0700159
160 private static void copySize(View from, View to) {
161 to.setLeft(0);
162 to.setTop(0);
163 to.setRight(from.getWidth());
164 to.setBottom(from.getHeight());
165 }
George Mount81206522014-09-26 21:53:39 -0700166
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();
George Mount50ac6cb2014-09-29 16:56:22 -0700327
328 // This default value shouldn't be used because both view and comparedWith
329 // should be in the list. If there is an error, then just return an arbitrary
330 // view is on top.
331 boolean isOnTop = true;
George Mount81206522014-09-26 21:53:39 -0700332 for (int i = 0; i < childrenCount; i++) {
333 int childIndex = customOrder ? parent.getChildDrawingOrder(childrenCount, i) : i;
334 final View child = (preorderedList == null)
335 ? parent.getChildAt(childIndex) : preorderedList.get(childIndex);
336 if (child == view) {
George Mount50ac6cb2014-09-29 16:56:22 -0700337 isOnTop = false;
338 break;
George Mount81206522014-09-26 21:53:39 -0700339 } else if (child == comparedWith) {
George Mount50ac6cb2014-09-29 16:56:22 -0700340 isOnTop = true;
341 break;
George Mount81206522014-09-26 21:53:39 -0700342 }
343 }
344
George Mount50ac6cb2014-09-29 16:56:22 -0700345 if (preorderedList != null) {
346 preorderedList.clear();
347 }
348 return isOnTop;
George Mount81206522014-09-26 21:53:39 -0700349 }
George Mount807e40c2014-07-08 17:25:25 -0700350}