blob: a72832760f961c27173972e2880a630473485e24 [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
Artur Satayevad9254c2019-12-10 17:47:54 +000018import android.compat.annotation.UnsupportedAppUsage;
George Mount807e40c2014-07-08 17:25:25 -070019import android.graphics.Canvas;
20import android.graphics.Matrix;
John Reck32f140aa62018-10-04 15:08:24 -070021import android.graphics.RecordingCanvas;
22import android.graphics.RenderNode;
Andrey Kulikov7c4e6932019-02-05 19:13:19 +000023import android.os.Build;
Dake Gu0017ef92014-08-12 14:54:53 -070024import android.widget.FrameLayout;
George Mount807e40c2014-07-08 17:25:25 -070025
George Mount81206522014-09-26 21:53:39 -070026import java.util.ArrayList;
27
George Mount807e40c2014-07-08 17:25:25 -070028/**
29 * This view draws another View in an Overlay without changing the parent. It will not be drawn
30 * by its parent because its visibility is set to INVISIBLE, but will be drawn
31 * here using its render node. When the GhostView is set to INVISIBLE, the View it is
32 * shadowing will become VISIBLE and when the GhostView becomes VISIBLE, the shadowed
33 * view becomes INVISIBLE.
34 * @hide
35 */
36public class GhostView extends View {
George Mount807e40c2014-07-08 17:25:25 -070037 private final View mView;
Dake Gu0017ef92014-08-12 14:54:53 -070038 private int mReferences;
George Mount81206522014-09-26 21:53:39 -070039 private boolean mBeingMoved;
George Mount807e40c2014-07-08 17:25:25 -070040
Dake Gu0017ef92014-08-12 14:54:53 -070041 private GhostView(View view) {
George Mount807e40c2014-07-08 17:25:25 -070042 super(view.getContext());
43 mView = view;
George Mount807e40c2014-07-08 17:25:25 -070044 mView.mGhostView = this;
45 final ViewGroup parent = (ViewGroup) mView.getParent();
George Mounte5a93aa2015-06-05 16:47:45 -070046 mView.setTransitionVisibility(View.INVISIBLE);
George Mount8261a3f2015-05-05 16:44:15 -070047 parent.invalidate();
George Mount807e40c2014-07-08 17:25:25 -070048 }
49
50 @Override
51 protected void onDraw(Canvas canvas) {
John Reck32f140aa62018-10-04 15:08:24 -070052 if (canvas instanceof RecordingCanvas) {
53 RecordingCanvas dlCanvas = (RecordingCanvas) canvas;
George Mount807e40c2014-07-08 17:25:25 -070054 mView.mRecreateDisplayList = true;
Chris Craik31a2d062015-05-01 14:22:47 -070055 RenderNode renderNode = mView.updateDisplayListIfDirty();
John Reckc7ddcf32018-10-25 13:56:17 -070056 if (renderNode.hasDisplayList()) {
Chris Craikf6829a02015-03-10 10:28:59 -070057 dlCanvas.insertReorderBarrier(); // enable shadow for this rendernode
58 dlCanvas.drawRenderNode(renderNode);
59 dlCanvas.insertInorderBarrier(); // re-disable reordering/shadows
George Mount807e40c2014-07-08 17:25:25 -070060 }
George Mount807e40c2014-07-08 17:25:25 -070061 }
62 }
63
George Mountfe361d22014-07-08 17:25:25 -070064 public void setMatrix(Matrix matrix) {
65 mRenderNode.setAnimationMatrix(matrix);
66 }
67
George Mount807e40c2014-07-08 17:25:25 -070068 @Override
69 public void setVisibility(@Visibility int visibility) {
70 super.setVisibility(visibility);
71 if (mView.mGhostView == this) {
72 int inverseVisibility = (visibility == View.VISIBLE) ? View.INVISIBLE : View.VISIBLE;
George Mounte5a93aa2015-06-05 16:47:45 -070073 mView.setTransitionVisibility(inverseVisibility);
George Mount807e40c2014-07-08 17:25:25 -070074 }
75 }
76
George Mount807e40c2014-07-08 17:25:25 -070077 @Override
78 protected void onDetachedFromWindow() {
79 super.onDetachedFromWindow();
George Mount81206522014-09-26 21:53:39 -070080 if (!mBeingMoved) {
George Mounte5a93aa2015-06-05 16:47:45 -070081 mView.setTransitionVisibility(View.VISIBLE);
George Mount81206522014-09-26 21:53:39 -070082 mView.mGhostView = null;
83 final ViewGroup parent = (ViewGroup) mView.getParent();
84 if (parent != null) {
George Mount0006e882015-05-08 07:48:19 -070085 parent.invalidate();
George Mount81206522014-09-26 21:53:39 -070086 }
George Mountfe361d22014-07-08 17:25:25 -070087 }
George Mount807e40c2014-07-08 17:25:25 -070088 }
89
George Mountfe361d22014-07-08 17:25:25 -070090 public static void calculateMatrix(View view, ViewGroup host, Matrix matrix) {
91 ViewGroup parent = (ViewGroup) view.getParent();
92 matrix.reset();
93 parent.transformMatrixToGlobal(matrix);
94 matrix.preTranslate(-parent.getScrollX(), -parent.getScrollY());
95 host.transformMatrixToLocal(matrix);
George Mount807e40c2014-07-08 17:25:25 -070096 }
97
Mathew Inwooda570dee2018-08-17 14:56:00 +010098 @UnsupportedAppUsage
George Mountfe361d22014-07-08 17:25:25 -070099 public static GhostView addGhost(View view, ViewGroup viewGroup, Matrix matrix) {
George Mount807e40c2014-07-08 17:25:25 -0700100 if (!(view.getParent() instanceof ViewGroup)) {
101 throw new IllegalArgumentException("Ghosted views must be parented by a ViewGroup");
102 }
103 ViewGroupOverlay overlay = viewGroup.getOverlay();
104 ViewOverlay.OverlayViewGroup overlayViewGroup = overlay.mOverlayViewGroup;
105 GhostView ghostView = view.mGhostView;
Dake Gu0017ef92014-08-12 14:54:53 -0700106 int previousRefCount = 0;
George Mount807e40c2014-07-08 17:25:25 -0700107 if (ghostView != null) {
Dake Gu0017ef92014-08-12 14:54:53 -0700108 View oldParent = (View) ghostView.getParent();
109 ViewGroup oldGrandParent = (ViewGroup) oldParent.getParent();
110 if (oldGrandParent != overlayViewGroup) {
111 previousRefCount = ghostView.mReferences;
112 oldGrandParent.removeView(oldParent);
George Mount807e40c2014-07-08 17:25:25 -0700113 ghostView = null;
114 }
115 }
116 if (ghostView == null) {
George Mountfe361d22014-07-08 17:25:25 -0700117 if (matrix == null) {
118 matrix = new Matrix();
119 calculateMatrix(view, viewGroup, matrix);
120 }
Dake Gu0017ef92014-08-12 14:54:53 -0700121 ghostView = new GhostView(view);
122 ghostView.setMatrix(matrix);
123 FrameLayout parent = new FrameLayout(view.getContext());
124 parent.setClipChildren(false);
125 copySize(viewGroup, parent);
126 copySize(viewGroup, ghostView);
127 parent.addView(ghostView);
George Mount81206522014-09-26 21:53:39 -0700128 ArrayList<View> tempViews = new ArrayList<View>();
129 int firstGhost = moveGhostViewsToTop(overlay.mOverlayViewGroup, tempViews);
130 insertIntoOverlay(overlay.mOverlayViewGroup, parent, ghostView, tempViews, firstGhost);
Dake Gu0017ef92014-08-12 14:54:53 -0700131 ghostView.mReferences = previousRefCount;
132 } else if (matrix != null) {
133 ghostView.setMatrix(matrix);
George Mount807e40c2014-07-08 17:25:25 -0700134 }
Dake Gu0017ef92014-08-12 14:54:53 -0700135 ghostView.mReferences++;
George Mount807e40c2014-07-08 17:25:25 -0700136 return ghostView;
137 }
138
Andrey Kulikov7c4e6932019-02-05 19:13:19 +0000139 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
George Mountfe361d22014-07-08 17:25:25 -0700140 public static GhostView addGhost(View view, ViewGroup viewGroup) {
141 return addGhost(view, viewGroup, null);
142 }
143
Andrey Kulikov7c4e6932019-02-05 19:13:19 +0000144 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
George Mount807e40c2014-07-08 17:25:25 -0700145 public static void removeGhost(View view) {
146 GhostView ghostView = view.mGhostView;
147 if (ghostView != null) {
Dake Gu0017ef92014-08-12 14:54:53 -0700148 ghostView.mReferences--;
149 if (ghostView.mReferences == 0) {
150 ViewGroup parent = (ViewGroup) ghostView.getParent();
151 ViewGroup grandParent = (ViewGroup) parent.getParent();
152 grandParent.removeView(parent);
153 }
George Mount807e40c2014-07-08 17:25:25 -0700154 }
155 }
156
157 public static GhostView getGhost(View view) {
158 return view.mGhostView;
159 }
Dake Gu0017ef92014-08-12 14:54:53 -0700160
161 private static void copySize(View from, View to) {
162 to.setLeft(0);
163 to.setTop(0);
164 to.setRight(from.getWidth());
165 to.setBottom(from.getHeight());
166 }
George Mount81206522014-09-26 21:53:39 -0700167
168 /**
169 * Move the GhostViews to the end so that they are on top of other views and it is easier
170 * to do binary search for the correct location for the GhostViews in insertIntoOverlay.
171 *
172 * @return The index of the first GhostView or -1 if no GhostView is in the ViewGroup
173 */
174 private static int moveGhostViewsToTop(ViewGroup viewGroup, ArrayList<View> tempViews) {
175 final int numChildren = viewGroup.getChildCount();
176 if (numChildren == 0) {
177 return -1;
178 } else if (isGhostWrapper(viewGroup.getChildAt(numChildren - 1))) {
179 // GhostViews are already at the end
180 int firstGhost = numChildren - 1;
181 for (int i = numChildren - 2; i >= 0; i--) {
182 if (!isGhostWrapper(viewGroup.getChildAt(i))) {
183 break;
184 }
185 firstGhost = i;
186 }
187 return firstGhost;
188 }
189
190 // Remove all GhostViews from the middle
191 for (int i = numChildren - 2; i >= 0; i--) {
192 View child = viewGroup.getChildAt(i);
193 if (isGhostWrapper(child)) {
194 tempViews.add(child);
195 GhostView ghostView = (GhostView)((ViewGroup)child).getChildAt(0);
196 ghostView.mBeingMoved = true;
197 viewGroup.removeViewAt(i);
198 ghostView.mBeingMoved = false;
199 }
200 }
201
202 final int firstGhost;
203 if (tempViews.isEmpty()) {
204 firstGhost = -1;
205 } else {
206 firstGhost = viewGroup.getChildCount();
207 // Add the GhostViews to the end
208 for (int i = tempViews.size() - 1; i >= 0; i--) {
209 viewGroup.addView(tempViews.get(i));
210 }
211 tempViews.clear();
212 }
213 return firstGhost;
214 }
215
216 /**
217 * Inserts a GhostView into the overlay's ViewGroup in the order in which they
218 * should be displayed by the UI.
219 */
220 private static void insertIntoOverlay(ViewGroup viewGroup, ViewGroup wrapper,
221 GhostView ghostView, ArrayList<View> tempParents, int firstGhost) {
222 if (firstGhost == -1) {
223 viewGroup.addView(wrapper);
224 } else {
225 ArrayList<View> viewParents = new ArrayList<View>();
226 getParents(ghostView.mView, viewParents);
227
228 int index = getInsertIndex(viewGroup, viewParents, tempParents, firstGhost);
229 if (index < 0 || index >= viewGroup.getChildCount()) {
230 viewGroup.addView(wrapper);
231 } else {
232 viewGroup.addView(wrapper, index);
233 }
234 }
235 }
236
237 /**
238 * Find the index into the overlay to insert the GhostView based on the order that the
239 * views should be drawn. This keeps GhostViews layered in the same order
240 * that they are ordered in the UI.
241 */
242 private static int getInsertIndex(ViewGroup overlayViewGroup, ArrayList<View> viewParents,
243 ArrayList<View> tempParents, int firstGhost) {
244 int low = firstGhost;
245 int high = overlayViewGroup.getChildCount() - 1;
246
247 while (low <= high) {
248 int mid = (low + high) / 2;
249 ViewGroup wrapper = (ViewGroup) overlayViewGroup.getChildAt(mid);
250 GhostView midView = (GhostView) wrapper.getChildAt(0);
251 getParents(midView.mView, tempParents);
252 if (isOnTop(viewParents, tempParents)) {
253 low = mid + 1;
254 } else {
255 high = mid - 1;
256 }
257 tempParents.clear();
258 }
259
260 return low;
261 }
262
263 /**
264 * Returns true if view is a GhostView's FrameLayout wrapper.
265 */
266 private static boolean isGhostWrapper(View view) {
267 if (view instanceof FrameLayout) {
268 FrameLayout frameLayout = (FrameLayout) view;
269 if (frameLayout.getChildCount() == 1) {
270 View child = frameLayout.getChildAt(0);
271 return child instanceof GhostView;
272 }
273 }
274 return false;
275 }
276
277 /**
278 * Returns true if viewParents is from a View that is on top of the comparedWith's view.
279 * The ArrayLists contain the ancestors of views in order from top most grandparent, to
280 * the view itself, in order. The goal is to find the first matching parent and then
281 * compare the draw order of the siblings.
282 */
283 private static boolean isOnTop(ArrayList<View> viewParents, ArrayList<View> comparedWith) {
284 if (viewParents.isEmpty() || comparedWith.isEmpty() ||
285 viewParents.get(0) != comparedWith.get(0)) {
286 // Not the same decorView -- arbitrary ordering
287 return true;
288 }
289 int depth = Math.min(viewParents.size(), comparedWith.size());
290 for (int i = 1; i < depth; i++) {
291 View viewParent = viewParents.get(i);
292 View comparedWithParent = comparedWith.get(i);
293
294 if (viewParent != comparedWithParent) {
295 // i - 1 is the same parent, but these are different children.
296 return isOnTop(viewParent, comparedWithParent);
297 }
298 }
299
300 // one of these is the parent of the other
301 boolean isComparedWithTheParent = (comparedWith.size() == depth);
302 return isComparedWithTheParent;
303 }
304
305 /**
306 * Adds all the parents, grandparents, etc. of view to parents.
307 */
308 private static void getParents(View view, ArrayList<View> parents) {
309 ViewParent parent = view.getParent();
310 if (parent != null && parent instanceof ViewGroup) {
311 getParents((View) parent, parents);
312 }
313 parents.add(view);
314 }
315
316 /**
317 * Returns true if view would be drawn on top of comparedWith or false otherwise.
318 * view and comparedWith are siblings with the same parent. This uses the logic
319 * that dispatchDraw uses to determine which View should be drawn first.
320 */
321 private static boolean isOnTop(View view, View comparedWith) {
322 ViewGroup parent = (ViewGroup) view.getParent();
323
324 final int childrenCount = parent.getChildCount();
325 final ArrayList<View> preorderedList = parent.buildOrderedChildList();
326 final boolean customOrder = preorderedList == null
327 && parent.isChildrenDrawingOrderEnabled();
George Mount50ac6cb2014-09-29 16:56:22 -0700328
329 // This default value shouldn't be used because both view and comparedWith
330 // should be in the list. If there is an error, then just return an arbitrary
331 // view is on top.
332 boolean isOnTop = true;
George Mount81206522014-09-26 21:53:39 -0700333 for (int i = 0; i < childrenCount; i++) {
334 int childIndex = customOrder ? parent.getChildDrawingOrder(childrenCount, i) : i;
335 final View child = (preorderedList == null)
336 ? parent.getChildAt(childIndex) : preorderedList.get(childIndex);
337 if (child == view) {
George Mount50ac6cb2014-09-29 16:56:22 -0700338 isOnTop = false;
339 break;
George Mount81206522014-09-26 21:53:39 -0700340 } else if (child == comparedWith) {
George Mount50ac6cb2014-09-29 16:56:22 -0700341 isOnTop = true;
342 break;
George Mount81206522014-09-26 21:53:39 -0700343 }
344 }
345
George Mount50ac6cb2014-09-29 16:56:22 -0700346 if (preorderedList != null) {
347 preorderedList.clear();
348 }
349 return isOnTop;
George Mount81206522014-09-26 21:53:39 -0700350 }
George Mount807e40c2014-07-08 17:25:25 -0700351}