blob: d1b96baa6062818521420842c6c26c2298fe4e48 [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 Mounte5a93aa2015-06-05 16:47:45 -070042 mView.setTransitionVisibility(View.INVISIBLE);
George Mount8261a3f2015-05-05 16:44:15 -070043 parent.invalidate();
George Mount807e40c2014-07-08 17:25:25 -070044 }
45
46 @Override
47 protected void onDraw(Canvas canvas) {
Chris Craikf6829a02015-03-10 10:28:59 -070048 if (canvas instanceof DisplayListCanvas) {
49 DisplayListCanvas dlCanvas = (DisplayListCanvas) canvas;
George Mount807e40c2014-07-08 17:25:25 -070050 mView.mRecreateDisplayList = true;
Chris Craik31a2d062015-05-01 14:22:47 -070051 RenderNode renderNode = mView.updateDisplayListIfDirty();
George Mount807e40c2014-07-08 17:25:25 -070052 if (renderNode.isValid()) {
Chris Craikf6829a02015-03-10 10:28:59 -070053 dlCanvas.insertReorderBarrier(); // enable shadow for this rendernode
54 dlCanvas.drawRenderNode(renderNode);
55 dlCanvas.insertInorderBarrier(); // re-disable reordering/shadows
George Mount807e40c2014-07-08 17:25:25 -070056 }
George Mount807e40c2014-07-08 17:25:25 -070057 }
58 }
59
George Mountfe361d22014-07-08 17:25:25 -070060 public void setMatrix(Matrix matrix) {
61 mRenderNode.setAnimationMatrix(matrix);
62 }
63
George Mount807e40c2014-07-08 17:25:25 -070064 @Override
65 public void setVisibility(@Visibility int visibility) {
66 super.setVisibility(visibility);
67 if (mView.mGhostView == this) {
68 int inverseVisibility = (visibility == View.VISIBLE) ? View.INVISIBLE : View.VISIBLE;
George Mounte5a93aa2015-06-05 16:47:45 -070069 mView.setTransitionVisibility(inverseVisibility);
George Mount807e40c2014-07-08 17:25:25 -070070 }
71 }
72
George Mount807e40c2014-07-08 17:25:25 -070073 @Override
74 protected void onDetachedFromWindow() {
75 super.onDetachedFromWindow();
George Mount81206522014-09-26 21:53:39 -070076 if (!mBeingMoved) {
George Mounte5a93aa2015-06-05 16:47:45 -070077 mView.setTransitionVisibility(View.VISIBLE);
George Mount81206522014-09-26 21:53:39 -070078 mView.mGhostView = null;
79 final ViewGroup parent = (ViewGroup) mView.getParent();
80 if (parent != null) {
George Mount0006e882015-05-08 07:48:19 -070081 parent.invalidate();
George Mount81206522014-09-26 21:53:39 -070082 }
George Mountfe361d22014-07-08 17:25:25 -070083 }
George Mount807e40c2014-07-08 17:25:25 -070084 }
85
George Mountfe361d22014-07-08 17:25:25 -070086 public static void calculateMatrix(View view, ViewGroup host, Matrix matrix) {
87 ViewGroup parent = (ViewGroup) view.getParent();
88 matrix.reset();
89 parent.transformMatrixToGlobal(matrix);
90 matrix.preTranslate(-parent.getScrollX(), -parent.getScrollY());
91 host.transformMatrixToLocal(matrix);
George Mount807e40c2014-07-08 17:25:25 -070092 }
93
George Mountfe361d22014-07-08 17:25:25 -070094 public static GhostView addGhost(View view, ViewGroup viewGroup, Matrix matrix) {
George Mount807e40c2014-07-08 17:25:25 -070095 if (!(view.getParent() instanceof ViewGroup)) {
96 throw new IllegalArgumentException("Ghosted views must be parented by a ViewGroup");
97 }
98 ViewGroupOverlay overlay = viewGroup.getOverlay();
99 ViewOverlay.OverlayViewGroup overlayViewGroup = overlay.mOverlayViewGroup;
100 GhostView ghostView = view.mGhostView;
Dake Gu0017ef92014-08-12 14:54:53 -0700101 int previousRefCount = 0;
George Mount807e40c2014-07-08 17:25:25 -0700102 if (ghostView != null) {
Dake Gu0017ef92014-08-12 14:54:53 -0700103 View oldParent = (View) ghostView.getParent();
104 ViewGroup oldGrandParent = (ViewGroup) oldParent.getParent();
105 if (oldGrandParent != overlayViewGroup) {
106 previousRefCount = ghostView.mReferences;
107 oldGrandParent.removeView(oldParent);
George Mount807e40c2014-07-08 17:25:25 -0700108 ghostView = null;
109 }
110 }
111 if (ghostView == null) {
George Mountfe361d22014-07-08 17:25:25 -0700112 if (matrix == null) {
113 matrix = new Matrix();
114 calculateMatrix(view, viewGroup, matrix);
115 }
Dake Gu0017ef92014-08-12 14:54:53 -0700116 ghostView = new GhostView(view);
117 ghostView.setMatrix(matrix);
118 FrameLayout parent = new FrameLayout(view.getContext());
119 parent.setClipChildren(false);
120 copySize(viewGroup, parent);
121 copySize(viewGroup, ghostView);
122 parent.addView(ghostView);
George Mount81206522014-09-26 21:53:39 -0700123 ArrayList<View> tempViews = new ArrayList<View>();
124 int firstGhost = moveGhostViewsToTop(overlay.mOverlayViewGroup, tempViews);
125 insertIntoOverlay(overlay.mOverlayViewGroup, parent, ghostView, tempViews, firstGhost);
Dake Gu0017ef92014-08-12 14:54:53 -0700126 ghostView.mReferences = previousRefCount;
127 } else if (matrix != null) {
128 ghostView.setMatrix(matrix);
George Mount807e40c2014-07-08 17:25:25 -0700129 }
Dake Gu0017ef92014-08-12 14:54:53 -0700130 ghostView.mReferences++;
George Mount807e40c2014-07-08 17:25:25 -0700131 return ghostView;
132 }
133
George Mountfe361d22014-07-08 17:25:25 -0700134 public static GhostView addGhost(View view, ViewGroup viewGroup) {
135 return addGhost(view, viewGroup, null);
136 }
137
George Mount807e40c2014-07-08 17:25:25 -0700138 public static void removeGhost(View view) {
139 GhostView ghostView = view.mGhostView;
140 if (ghostView != null) {
Dake Gu0017ef92014-08-12 14:54:53 -0700141 ghostView.mReferences--;
142 if (ghostView.mReferences == 0) {
143 ViewGroup parent = (ViewGroup) ghostView.getParent();
144 ViewGroup grandParent = (ViewGroup) parent.getParent();
145 grandParent.removeView(parent);
146 }
George Mount807e40c2014-07-08 17:25:25 -0700147 }
148 }
149
150 public static GhostView getGhost(View view) {
151 return view.mGhostView;
152 }
Dake Gu0017ef92014-08-12 14:54:53 -0700153
154 private static void copySize(View from, View to) {
155 to.setLeft(0);
156 to.setTop(0);
157 to.setRight(from.getWidth());
158 to.setBottom(from.getHeight());
159 }
George Mount81206522014-09-26 21:53:39 -0700160
161 /**
162 * Move the GhostViews to the end so that they are on top of other views and it is easier
163 * to do binary search for the correct location for the GhostViews in insertIntoOverlay.
164 *
165 * @return The index of the first GhostView or -1 if no GhostView is in the ViewGroup
166 */
167 private static int moveGhostViewsToTop(ViewGroup viewGroup, ArrayList<View> tempViews) {
168 final int numChildren = viewGroup.getChildCount();
169 if (numChildren == 0) {
170 return -1;
171 } else if (isGhostWrapper(viewGroup.getChildAt(numChildren - 1))) {
172 // GhostViews are already at the end
173 int firstGhost = numChildren - 1;
174 for (int i = numChildren - 2; i >= 0; i--) {
175 if (!isGhostWrapper(viewGroup.getChildAt(i))) {
176 break;
177 }
178 firstGhost = i;
179 }
180 return firstGhost;
181 }
182
183 // Remove all GhostViews from the middle
184 for (int i = numChildren - 2; i >= 0; i--) {
185 View child = viewGroup.getChildAt(i);
186 if (isGhostWrapper(child)) {
187 tempViews.add(child);
188 GhostView ghostView = (GhostView)((ViewGroup)child).getChildAt(0);
189 ghostView.mBeingMoved = true;
190 viewGroup.removeViewAt(i);
191 ghostView.mBeingMoved = false;
192 }
193 }
194
195 final int firstGhost;
196 if (tempViews.isEmpty()) {
197 firstGhost = -1;
198 } else {
199 firstGhost = viewGroup.getChildCount();
200 // Add the GhostViews to the end
201 for (int i = tempViews.size() - 1; i >= 0; i--) {
202 viewGroup.addView(tempViews.get(i));
203 }
204 tempViews.clear();
205 }
206 return firstGhost;
207 }
208
209 /**
210 * Inserts a GhostView into the overlay's ViewGroup in the order in which they
211 * should be displayed by the UI.
212 */
213 private static void insertIntoOverlay(ViewGroup viewGroup, ViewGroup wrapper,
214 GhostView ghostView, ArrayList<View> tempParents, int firstGhost) {
215 if (firstGhost == -1) {
216 viewGroup.addView(wrapper);
217 } else {
218 ArrayList<View> viewParents = new ArrayList<View>();
219 getParents(ghostView.mView, viewParents);
220
221 int index = getInsertIndex(viewGroup, viewParents, tempParents, firstGhost);
222 if (index < 0 || index >= viewGroup.getChildCount()) {
223 viewGroup.addView(wrapper);
224 } else {
225 viewGroup.addView(wrapper, index);
226 }
227 }
228 }
229
230 /**
231 * Find the index into the overlay to insert the GhostView based on the order that the
232 * views should be drawn. This keeps GhostViews layered in the same order
233 * that they are ordered in the UI.
234 */
235 private static int getInsertIndex(ViewGroup overlayViewGroup, ArrayList<View> viewParents,
236 ArrayList<View> tempParents, int firstGhost) {
237 int low = firstGhost;
238 int high = overlayViewGroup.getChildCount() - 1;
239
240 while (low <= high) {
241 int mid = (low + high) / 2;
242 ViewGroup wrapper = (ViewGroup) overlayViewGroup.getChildAt(mid);
243 GhostView midView = (GhostView) wrapper.getChildAt(0);
244 getParents(midView.mView, tempParents);
245 if (isOnTop(viewParents, tempParents)) {
246 low = mid + 1;
247 } else {
248 high = mid - 1;
249 }
250 tempParents.clear();
251 }
252
253 return low;
254 }
255
256 /**
257 * Returns true if view is a GhostView's FrameLayout wrapper.
258 */
259 private static boolean isGhostWrapper(View view) {
260 if (view instanceof FrameLayout) {
261 FrameLayout frameLayout = (FrameLayout) view;
262 if (frameLayout.getChildCount() == 1) {
263 View child = frameLayout.getChildAt(0);
264 return child instanceof GhostView;
265 }
266 }
267 return false;
268 }
269
270 /**
271 * Returns true if viewParents is from a View that is on top of the comparedWith's view.
272 * The ArrayLists contain the ancestors of views in order from top most grandparent, to
273 * the view itself, in order. The goal is to find the first matching parent and then
274 * compare the draw order of the siblings.
275 */
276 private static boolean isOnTop(ArrayList<View> viewParents, ArrayList<View> comparedWith) {
277 if (viewParents.isEmpty() || comparedWith.isEmpty() ||
278 viewParents.get(0) != comparedWith.get(0)) {
279 // Not the same decorView -- arbitrary ordering
280 return true;
281 }
282 int depth = Math.min(viewParents.size(), comparedWith.size());
283 for (int i = 1; i < depth; i++) {
284 View viewParent = viewParents.get(i);
285 View comparedWithParent = comparedWith.get(i);
286
287 if (viewParent != comparedWithParent) {
288 // i - 1 is the same parent, but these are different children.
289 return isOnTop(viewParent, comparedWithParent);
290 }
291 }
292
293 // one of these is the parent of the other
294 boolean isComparedWithTheParent = (comparedWith.size() == depth);
295 return isComparedWithTheParent;
296 }
297
298 /**
299 * Adds all the parents, grandparents, etc. of view to parents.
300 */
301 private static void getParents(View view, ArrayList<View> parents) {
302 ViewParent parent = view.getParent();
303 if (parent != null && parent instanceof ViewGroup) {
304 getParents((View) parent, parents);
305 }
306 parents.add(view);
307 }
308
309 /**
310 * Returns true if view would be drawn on top of comparedWith or false otherwise.
311 * view and comparedWith are siblings with the same parent. This uses the logic
312 * that dispatchDraw uses to determine which View should be drawn first.
313 */
314 private static boolean isOnTop(View view, View comparedWith) {
315 ViewGroup parent = (ViewGroup) view.getParent();
316
317 final int childrenCount = parent.getChildCount();
318 final ArrayList<View> preorderedList = parent.buildOrderedChildList();
319 final boolean customOrder = preorderedList == null
320 && parent.isChildrenDrawingOrderEnabled();
George Mount50ac6cb2014-09-29 16:56:22 -0700321
322 // This default value shouldn't be used because both view and comparedWith
323 // should be in the list. If there is an error, then just return an arbitrary
324 // view is on top.
325 boolean isOnTop = true;
George Mount81206522014-09-26 21:53:39 -0700326 for (int i = 0; i < childrenCount; i++) {
327 int childIndex = customOrder ? parent.getChildDrawingOrder(childrenCount, i) : i;
328 final View child = (preorderedList == null)
329 ? parent.getChildAt(childIndex) : preorderedList.get(childIndex);
330 if (child == view) {
George Mount50ac6cb2014-09-29 16:56:22 -0700331 isOnTop = false;
332 break;
George Mount81206522014-09-26 21:53:39 -0700333 } else if (child == comparedWith) {
George Mount50ac6cb2014-09-29 16:56:22 -0700334 isOnTop = true;
335 break;
George Mount81206522014-09-26 21:53:39 -0700336 }
337 }
338
George Mount50ac6cb2014-09-29 16:56:22 -0700339 if (preorderedList != null) {
340 preorderedList.clear();
341 }
342 return isOnTop;
George Mount81206522014-09-26 21:53:39 -0700343 }
George Mount807e40c2014-07-08 17:25:25 -0700344}