blob: fa7b067deb20e392702d42723368a8d0d1881d07 [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
Mathew Inwoode5ad5982018-08-17 15:07:52 +010018import android.annotation.UnsupportedAppUsage;
George Mount807e40c2014-07-08 17:25:25 -070019import android.graphics.Canvas;
20import android.graphics.Matrix;
Dake Gu0017ef92014-08-12 14:54:53 -070021import android.widget.FrameLayout;
George Mount807e40c2014-07-08 17:25:25 -070022
George Mount81206522014-09-26 21:53:39 -070023import java.util.ArrayList;
24
George Mount807e40c2014-07-08 17:25:25 -070025/**
26 * This view draws another View in an Overlay without changing the parent. It will not be drawn
27 * by its parent because its visibility is set to INVISIBLE, but will be drawn
28 * here using its render node. When the GhostView is set to INVISIBLE, the View it is
29 * shadowing will become VISIBLE and when the GhostView becomes VISIBLE, the shadowed
30 * view becomes INVISIBLE.
31 * @hide
32 */
33public class GhostView extends View {
George Mount807e40c2014-07-08 17:25:25 -070034 private final View mView;
Dake Gu0017ef92014-08-12 14:54:53 -070035 private int mReferences;
George Mount81206522014-09-26 21:53:39 -070036 private boolean mBeingMoved;
George Mount807e40c2014-07-08 17:25:25 -070037
Dake Gu0017ef92014-08-12 14:54:53 -070038 private GhostView(View view) {
George Mount807e40c2014-07-08 17:25:25 -070039 super(view.getContext());
40 mView = view;
George Mount807e40c2014-07-08 17:25:25 -070041 mView.mGhostView = this;
42 final ViewGroup parent = (ViewGroup) mView.getParent();
George Mounte5a93aa2015-06-05 16:47:45 -070043 mView.setTransitionVisibility(View.INVISIBLE);
George Mount8261a3f2015-05-05 16:44:15 -070044 parent.invalidate();
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;
George Mounte5a93aa2015-06-05 16:47:45 -070070 mView.setTransitionVisibility(inverseVisibility);
George Mount807e40c2014-07-08 17:25:25 -070071 }
72 }
73
George Mount807e40c2014-07-08 17:25:25 -070074 @Override
75 protected void onDetachedFromWindow() {
76 super.onDetachedFromWindow();
George Mount81206522014-09-26 21:53:39 -070077 if (!mBeingMoved) {
George Mounte5a93aa2015-06-05 16:47:45 -070078 mView.setTransitionVisibility(View.VISIBLE);
George Mount81206522014-09-26 21:53:39 -070079 mView.mGhostView = null;
80 final ViewGroup parent = (ViewGroup) mView.getParent();
81 if (parent != null) {
George Mount0006e882015-05-08 07:48:19 -070082 parent.invalidate();
George Mount81206522014-09-26 21:53:39 -070083 }
George Mountfe361d22014-07-08 17:25:25 -070084 }
George Mount807e40c2014-07-08 17:25:25 -070085 }
86
George Mountfe361d22014-07-08 17:25:25 -070087 public static void calculateMatrix(View view, ViewGroup host, Matrix matrix) {
88 ViewGroup parent = (ViewGroup) view.getParent();
89 matrix.reset();
90 parent.transformMatrixToGlobal(matrix);
91 matrix.preTranslate(-parent.getScrollX(), -parent.getScrollY());
92 host.transformMatrixToLocal(matrix);
George Mount807e40c2014-07-08 17:25:25 -070093 }
94
Mathew Inwoode5ad5982018-08-17 15:07:52 +010095 @UnsupportedAppUsage
George Mountfe361d22014-07-08 17:25:25 -070096 public static GhostView addGhost(View view, ViewGroup viewGroup, Matrix matrix) {
George Mount807e40c2014-07-08 17:25:25 -070097 if (!(view.getParent() instanceof ViewGroup)) {
98 throw new IllegalArgumentException("Ghosted views must be parented by a ViewGroup");
99 }
100 ViewGroupOverlay overlay = viewGroup.getOverlay();
101 ViewOverlay.OverlayViewGroup overlayViewGroup = overlay.mOverlayViewGroup;
102 GhostView ghostView = view.mGhostView;
Dake Gu0017ef92014-08-12 14:54:53 -0700103 int previousRefCount = 0;
George Mount807e40c2014-07-08 17:25:25 -0700104 if (ghostView != null) {
Dake Gu0017ef92014-08-12 14:54:53 -0700105 View oldParent = (View) ghostView.getParent();
106 ViewGroup oldGrandParent = (ViewGroup) oldParent.getParent();
107 if (oldGrandParent != overlayViewGroup) {
108 previousRefCount = ghostView.mReferences;
109 oldGrandParent.removeView(oldParent);
George Mount807e40c2014-07-08 17:25:25 -0700110 ghostView = null;
111 }
112 }
113 if (ghostView == null) {
George Mountfe361d22014-07-08 17:25:25 -0700114 if (matrix == null) {
115 matrix = new Matrix();
116 calculateMatrix(view, viewGroup, matrix);
117 }
Dake Gu0017ef92014-08-12 14:54:53 -0700118 ghostView = new GhostView(view);
119 ghostView.setMatrix(matrix);
120 FrameLayout parent = new FrameLayout(view.getContext());
121 parent.setClipChildren(false);
122 copySize(viewGroup, parent);
123 copySize(viewGroup, ghostView);
124 parent.addView(ghostView);
George Mount81206522014-09-26 21:53:39 -0700125 ArrayList<View> tempViews = new ArrayList<View>();
126 int firstGhost = moveGhostViewsToTop(overlay.mOverlayViewGroup, tempViews);
127 insertIntoOverlay(overlay.mOverlayViewGroup, parent, ghostView, tempViews, firstGhost);
Dake Gu0017ef92014-08-12 14:54:53 -0700128 ghostView.mReferences = previousRefCount;
129 } else if (matrix != null) {
130 ghostView.setMatrix(matrix);
George Mount807e40c2014-07-08 17:25:25 -0700131 }
Dake Gu0017ef92014-08-12 14:54:53 -0700132 ghostView.mReferences++;
George Mount807e40c2014-07-08 17:25:25 -0700133 return ghostView;
134 }
135
Mathew Inwoode5ad5982018-08-17 15:07:52 +0100136 @UnsupportedAppUsage
George Mountfe361d22014-07-08 17:25:25 -0700137 public static GhostView addGhost(View view, ViewGroup viewGroup) {
138 return addGhost(view, viewGroup, null);
139 }
140
Mathew Inwoode5ad5982018-08-17 15:07:52 +0100141 @UnsupportedAppUsage
George Mount807e40c2014-07-08 17:25:25 -0700142 public static void removeGhost(View view) {
143 GhostView ghostView = view.mGhostView;
144 if (ghostView != null) {
Dake Gu0017ef92014-08-12 14:54:53 -0700145 ghostView.mReferences--;
146 if (ghostView.mReferences == 0) {
147 ViewGroup parent = (ViewGroup) ghostView.getParent();
148 ViewGroup grandParent = (ViewGroup) parent.getParent();
149 grandParent.removeView(parent);
150 }
George Mount807e40c2014-07-08 17:25:25 -0700151 }
152 }
153
154 public static GhostView getGhost(View view) {
155 return view.mGhostView;
156 }
Dake Gu0017ef92014-08-12 14:54:53 -0700157
158 private static void copySize(View from, View to) {
159 to.setLeft(0);
160 to.setTop(0);
161 to.setRight(from.getWidth());
162 to.setBottom(from.getHeight());
163 }
George Mount81206522014-09-26 21:53:39 -0700164
165 /**
166 * Move the GhostViews to the end so that they are on top of other views and it is easier
167 * to do binary search for the correct location for the GhostViews in insertIntoOverlay.
168 *
169 * @return The index of the first GhostView or -1 if no GhostView is in the ViewGroup
170 */
171 private static int moveGhostViewsToTop(ViewGroup viewGroup, ArrayList<View> tempViews) {
172 final int numChildren = viewGroup.getChildCount();
173 if (numChildren == 0) {
174 return -1;
175 } else if (isGhostWrapper(viewGroup.getChildAt(numChildren - 1))) {
176 // GhostViews are already at the end
177 int firstGhost = numChildren - 1;
178 for (int i = numChildren - 2; i >= 0; i--) {
179 if (!isGhostWrapper(viewGroup.getChildAt(i))) {
180 break;
181 }
182 firstGhost = i;
183 }
184 return firstGhost;
185 }
186
187 // Remove all GhostViews from the middle
188 for (int i = numChildren - 2; i >= 0; i--) {
189 View child = viewGroup.getChildAt(i);
190 if (isGhostWrapper(child)) {
191 tempViews.add(child);
192 GhostView ghostView = (GhostView)((ViewGroup)child).getChildAt(0);
193 ghostView.mBeingMoved = true;
194 viewGroup.removeViewAt(i);
195 ghostView.mBeingMoved = false;
196 }
197 }
198
199 final int firstGhost;
200 if (tempViews.isEmpty()) {
201 firstGhost = -1;
202 } else {
203 firstGhost = viewGroup.getChildCount();
204 // Add the GhostViews to the end
205 for (int i = tempViews.size() - 1; i >= 0; i--) {
206 viewGroup.addView(tempViews.get(i));
207 }
208 tempViews.clear();
209 }
210 return firstGhost;
211 }
212
213 /**
214 * Inserts a GhostView into the overlay's ViewGroup in the order in which they
215 * should be displayed by the UI.
216 */
217 private static void insertIntoOverlay(ViewGroup viewGroup, ViewGroup wrapper,
218 GhostView ghostView, ArrayList<View> tempParents, int firstGhost) {
219 if (firstGhost == -1) {
220 viewGroup.addView(wrapper);
221 } else {
222 ArrayList<View> viewParents = new ArrayList<View>();
223 getParents(ghostView.mView, viewParents);
224
225 int index = getInsertIndex(viewGroup, viewParents, tempParents, firstGhost);
226 if (index < 0 || index >= viewGroup.getChildCount()) {
227 viewGroup.addView(wrapper);
228 } else {
229 viewGroup.addView(wrapper, index);
230 }
231 }
232 }
233
234 /**
235 * Find the index into the overlay to insert the GhostView based on the order that the
236 * views should be drawn. This keeps GhostViews layered in the same order
237 * that they are ordered in the UI.
238 */
239 private static int getInsertIndex(ViewGroup overlayViewGroup, ArrayList<View> viewParents,
240 ArrayList<View> tempParents, int firstGhost) {
241 int low = firstGhost;
242 int high = overlayViewGroup.getChildCount() - 1;
243
244 while (low <= high) {
245 int mid = (low + high) / 2;
246 ViewGroup wrapper = (ViewGroup) overlayViewGroup.getChildAt(mid);
247 GhostView midView = (GhostView) wrapper.getChildAt(0);
248 getParents(midView.mView, tempParents);
249 if (isOnTop(viewParents, tempParents)) {
250 low = mid + 1;
251 } else {
252 high = mid - 1;
253 }
254 tempParents.clear();
255 }
256
257 return low;
258 }
259
260 /**
261 * Returns true if view is a GhostView's FrameLayout wrapper.
262 */
263 private static boolean isGhostWrapper(View view) {
264 if (view instanceof FrameLayout) {
265 FrameLayout frameLayout = (FrameLayout) view;
266 if (frameLayout.getChildCount() == 1) {
267 View child = frameLayout.getChildAt(0);
268 return child instanceof GhostView;
269 }
270 }
271 return false;
272 }
273
274 /**
275 * Returns true if viewParents is from a View that is on top of the comparedWith's view.
276 * The ArrayLists contain the ancestors of views in order from top most grandparent, to
277 * the view itself, in order. The goal is to find the first matching parent and then
278 * compare the draw order of the siblings.
279 */
280 private static boolean isOnTop(ArrayList<View> viewParents, ArrayList<View> comparedWith) {
281 if (viewParents.isEmpty() || comparedWith.isEmpty() ||
282 viewParents.get(0) != comparedWith.get(0)) {
283 // Not the same decorView -- arbitrary ordering
284 return true;
285 }
286 int depth = Math.min(viewParents.size(), comparedWith.size());
287 for (int i = 1; i < depth; i++) {
288 View viewParent = viewParents.get(i);
289 View comparedWithParent = comparedWith.get(i);
290
291 if (viewParent != comparedWithParent) {
292 // i - 1 is the same parent, but these are different children.
293 return isOnTop(viewParent, comparedWithParent);
294 }
295 }
296
297 // one of these is the parent of the other
298 boolean isComparedWithTheParent = (comparedWith.size() == depth);
299 return isComparedWithTheParent;
300 }
301
302 /**
303 * Adds all the parents, grandparents, etc. of view to parents.
304 */
305 private static void getParents(View view, ArrayList<View> parents) {
306 ViewParent parent = view.getParent();
307 if (parent != null && parent instanceof ViewGroup) {
308 getParents((View) parent, parents);
309 }
310 parents.add(view);
311 }
312
313 /**
314 * Returns true if view would be drawn on top of comparedWith or false otherwise.
315 * view and comparedWith are siblings with the same parent. This uses the logic
316 * that dispatchDraw uses to determine which View should be drawn first.
317 */
318 private static boolean isOnTop(View view, View comparedWith) {
319 ViewGroup parent = (ViewGroup) view.getParent();
320
321 final int childrenCount = parent.getChildCount();
322 final ArrayList<View> preorderedList = parent.buildOrderedChildList();
323 final boolean customOrder = preorderedList == null
324 && parent.isChildrenDrawingOrderEnabled();
George Mount50ac6cb2014-09-29 16:56:22 -0700325
326 // This default value shouldn't be used because both view and comparedWith
327 // should be in the list. If there is an error, then just return an arbitrary
328 // view is on top.
329 boolean isOnTop = true;
George Mount81206522014-09-26 21:53:39 -0700330 for (int i = 0; i < childrenCount; i++) {
331 int childIndex = customOrder ? parent.getChildDrawingOrder(childrenCount, i) : i;
332 final View child = (preorderedList == null)
333 ? parent.getChildAt(childIndex) : preorderedList.get(childIndex);
334 if (child == view) {
George Mount50ac6cb2014-09-29 16:56:22 -0700335 isOnTop = false;
336 break;
George Mount81206522014-09-26 21:53:39 -0700337 } else if (child == comparedWith) {
George Mount50ac6cb2014-09-29 16:56:22 -0700338 isOnTop = true;
339 break;
George Mount81206522014-09-26 21:53:39 -0700340 }
341 }
342
George Mount50ac6cb2014-09-29 16:56:22 -0700343 if (preorderedList != null) {
344 preorderedList.clear();
345 }
346 return isOnTop;
George Mount81206522014-09-26 21:53:39 -0700347 }
George Mount807e40c2014-07-08 17:25:25 -0700348}