blob: 74c801b1dc5bd164c5b84e83ac272b4e27d1d090 [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 Inwooda570dee2018-08-17 14:56:00 +010018import android.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;
Dake Gu0017ef92014-08-12 14:54:53 -070023import android.widget.FrameLayout;
George Mount807e40c2014-07-08 17:25:25 -070024
George Mount81206522014-09-26 21:53:39 -070025import java.util.ArrayList;
26
George Mount807e40c2014-07-08 17:25:25 -070027/**
28 * This view draws another View in an Overlay without changing the parent. It will not be drawn
29 * by its parent because its visibility is set to INVISIBLE, but will be drawn
30 * here using its render node. When the GhostView is set to INVISIBLE, the View it is
31 * shadowing will become VISIBLE and when the GhostView becomes VISIBLE, the shadowed
32 * view becomes INVISIBLE.
33 * @hide
34 */
35public class GhostView extends View {
George Mount807e40c2014-07-08 17:25:25 -070036 private final View mView;
Dake Gu0017ef92014-08-12 14:54:53 -070037 private int mReferences;
George Mount81206522014-09-26 21:53:39 -070038 private boolean mBeingMoved;
George Mount807e40c2014-07-08 17:25:25 -070039
Dake Gu0017ef92014-08-12 14:54:53 -070040 private GhostView(View view) {
George Mount807e40c2014-07-08 17:25:25 -070041 super(view.getContext());
42 mView = view;
George Mount807e40c2014-07-08 17:25:25 -070043 mView.mGhostView = this;
44 final ViewGroup parent = (ViewGroup) mView.getParent();
George Mounte5a93aa2015-06-05 16:47:45 -070045 mView.setTransitionVisibility(View.INVISIBLE);
George Mount8261a3f2015-05-05 16:44:15 -070046 parent.invalidate();
George Mount807e40c2014-07-08 17:25:25 -070047 }
48
49 @Override
50 protected void onDraw(Canvas canvas) {
John Reck32f140aa62018-10-04 15:08:24 -070051 if (canvas instanceof RecordingCanvas) {
52 RecordingCanvas dlCanvas = (RecordingCanvas) canvas;
George Mount807e40c2014-07-08 17:25:25 -070053 mView.mRecreateDisplayList = true;
Chris Craik31a2d062015-05-01 14:22:47 -070054 RenderNode renderNode = mView.updateDisplayListIfDirty();
John Reckc7ddcf32018-10-25 13:56:17 -070055 if (renderNode.hasDisplayList()) {
Chris Craikf6829a02015-03-10 10:28:59 -070056 dlCanvas.insertReorderBarrier(); // enable shadow for this rendernode
57 dlCanvas.drawRenderNode(renderNode);
58 dlCanvas.insertInorderBarrier(); // re-disable reordering/shadows
George Mount807e40c2014-07-08 17:25:25 -070059 }
George Mount807e40c2014-07-08 17:25:25 -070060 }
61 }
62
George Mountfe361d22014-07-08 17:25:25 -070063 public void setMatrix(Matrix matrix) {
64 mRenderNode.setAnimationMatrix(matrix);
65 }
66
George Mount807e40c2014-07-08 17:25:25 -070067 @Override
68 public void setVisibility(@Visibility int visibility) {
69 super.setVisibility(visibility);
70 if (mView.mGhostView == this) {
71 int inverseVisibility = (visibility == View.VISIBLE) ? View.INVISIBLE : View.VISIBLE;
George Mounte5a93aa2015-06-05 16:47:45 -070072 mView.setTransitionVisibility(inverseVisibility);
George Mount807e40c2014-07-08 17:25:25 -070073 }
74 }
75
George Mount807e40c2014-07-08 17:25:25 -070076 @Override
77 protected void onDetachedFromWindow() {
78 super.onDetachedFromWindow();
George Mount81206522014-09-26 21:53:39 -070079 if (!mBeingMoved) {
George Mounte5a93aa2015-06-05 16:47:45 -070080 mView.setTransitionVisibility(View.VISIBLE);
George Mount81206522014-09-26 21:53:39 -070081 mView.mGhostView = null;
82 final ViewGroup parent = (ViewGroup) mView.getParent();
83 if (parent != null) {
George Mount0006e882015-05-08 07:48:19 -070084 parent.invalidate();
George Mount81206522014-09-26 21:53:39 -070085 }
George Mountfe361d22014-07-08 17:25:25 -070086 }
George Mount807e40c2014-07-08 17:25:25 -070087 }
88
George Mountfe361d22014-07-08 17:25:25 -070089 public static void calculateMatrix(View view, ViewGroup host, Matrix matrix) {
90 ViewGroup parent = (ViewGroup) view.getParent();
91 matrix.reset();
92 parent.transformMatrixToGlobal(matrix);
93 matrix.preTranslate(-parent.getScrollX(), -parent.getScrollY());
94 host.transformMatrixToLocal(matrix);
George Mount807e40c2014-07-08 17:25:25 -070095 }
96
Mathew Inwooda570dee2018-08-17 14:56:00 +010097 @UnsupportedAppUsage
George Mountfe361d22014-07-08 17:25:25 -070098 public static GhostView addGhost(View view, ViewGroup viewGroup, Matrix matrix) {
George Mount807e40c2014-07-08 17:25:25 -070099 if (!(view.getParent() instanceof ViewGroup)) {
100 throw new IllegalArgumentException("Ghosted views must be parented by a ViewGroup");
101 }
102 ViewGroupOverlay overlay = viewGroup.getOverlay();
103 ViewOverlay.OverlayViewGroup overlayViewGroup = overlay.mOverlayViewGroup;
104 GhostView ghostView = view.mGhostView;
Dake Gu0017ef92014-08-12 14:54:53 -0700105 int previousRefCount = 0;
George Mount807e40c2014-07-08 17:25:25 -0700106 if (ghostView != null) {
Dake Gu0017ef92014-08-12 14:54:53 -0700107 View oldParent = (View) ghostView.getParent();
108 ViewGroup oldGrandParent = (ViewGroup) oldParent.getParent();
109 if (oldGrandParent != overlayViewGroup) {
110 previousRefCount = ghostView.mReferences;
111 oldGrandParent.removeView(oldParent);
George Mount807e40c2014-07-08 17:25:25 -0700112 ghostView = null;
113 }
114 }
115 if (ghostView == null) {
George Mountfe361d22014-07-08 17:25:25 -0700116 if (matrix == null) {
117 matrix = new Matrix();
118 calculateMatrix(view, viewGroup, matrix);
119 }
Dake Gu0017ef92014-08-12 14:54:53 -0700120 ghostView = new GhostView(view);
121 ghostView.setMatrix(matrix);
122 FrameLayout parent = new FrameLayout(view.getContext());
123 parent.setClipChildren(false);
124 copySize(viewGroup, parent);
125 copySize(viewGroup, ghostView);
126 parent.addView(ghostView);
George Mount81206522014-09-26 21:53:39 -0700127 ArrayList<View> tempViews = new ArrayList<View>();
128 int firstGhost = moveGhostViewsToTop(overlay.mOverlayViewGroup, tempViews);
129 insertIntoOverlay(overlay.mOverlayViewGroup, parent, ghostView, tempViews, firstGhost);
Dake Gu0017ef92014-08-12 14:54:53 -0700130 ghostView.mReferences = previousRefCount;
131 } else if (matrix != null) {
132 ghostView.setMatrix(matrix);
George Mount807e40c2014-07-08 17:25:25 -0700133 }
Dake Gu0017ef92014-08-12 14:54:53 -0700134 ghostView.mReferences++;
George Mount807e40c2014-07-08 17:25:25 -0700135 return ghostView;
136 }
137
Mathew Inwooda570dee2018-08-17 14:56:00 +0100138 @UnsupportedAppUsage
George Mountfe361d22014-07-08 17:25:25 -0700139 public static GhostView addGhost(View view, ViewGroup viewGroup) {
140 return addGhost(view, viewGroup, null);
141 }
142
Mathew Inwooda570dee2018-08-17 14:56:00 +0100143 @UnsupportedAppUsage
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}