From 807e40c55cd74004ecc2392f8655fb89b3bb5304 Mon Sep 17 00:00:00 2001 From: George Mount Date: Tue, 8 Jul 2014 17:25:25 -0700 Subject: Allow a "ghost view" that paint a different view from the overlay. Bug 15744995 The ghost view doesn't route touch events, but hides another view in its normal parent, and paints it as a child of another ViewGroup or ViewGroupOverlay. Change-Id: I352e14c366ccfb7303cee1dbff8563c673fd12ff --- core/java/android/view/GhostView.java | 125 ++++++++++++++++++++++++++++++++++ core/java/android/view/View.java | 18 ++++- 2 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 core/java/android/view/GhostView.java (limited to 'core/java') diff --git a/core/java/android/view/GhostView.java b/core/java/android/view/GhostView.java new file mode 100644 index 0000000..1aa8d99 --- /dev/null +++ b/core/java/android/view/GhostView.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.view; + +import android.graphics.Canvas; +import android.graphics.Matrix; + +/** + * This view draws another View in an Overlay without changing the parent. It will not be drawn + * by its parent because its visibility is set to INVISIBLE, but will be drawn + * here using its render node. When the GhostView is set to INVISIBLE, the View it is + * shadowing will become VISIBLE and when the GhostView becomes VISIBLE, the shadowed + * view becomes INVISIBLE. + * @hide + */ +public class GhostView extends View { + private final Matrix mMatrix = new Matrix(); + private final View mView; + + private GhostView(View view, ViewGroup host) { + super(view.getContext()); + mView = view; + setMatrix(host); + mView.mGhostView = this; + final ViewGroup parent = (ViewGroup) mView.getParent(); + setLeft(0); + setTop(0); + setRight(host.getWidth()); + setBottom(host.getHeight()); + setGhostedVisibility(View.INVISIBLE); + parent.mRecreateDisplayList = true; + parent.getDisplayList(); + } + + @Override + protected void onDraw(Canvas canvas) { + if (canvas instanceof HardwareCanvas) { + HardwareCanvas hwCanvas = (HardwareCanvas) canvas; + int saveCount = hwCanvas.save(Canvas.MATRIX_SAVE_FLAG); + canvas.concat(mMatrix); + mView.mRecreateDisplayList = true; + RenderNode renderNode = mView.getDisplayList(); + if (renderNode.isValid()) { + hwCanvas.drawRenderNode(renderNode); + } + hwCanvas.restoreToCount(saveCount); + } + } + + @Override + public void setVisibility(@Visibility int visibility) { + super.setVisibility(visibility); + if (mView.mGhostView == this) { + int inverseVisibility = (visibility == View.VISIBLE) ? View.INVISIBLE : View.VISIBLE; + setGhostedVisibility(inverseVisibility); + } + } + + private void setGhostedVisibility(int visibility) { + mView.mViewFlags = (mView.mViewFlags & ~View.VISIBILITY_MASK) | visibility; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + setGhostedVisibility(View.VISIBLE); + mView.mGhostView = null; + final ViewGroup parent = (ViewGroup) mView.getParent(); + parent.mRecreateDisplayList = true; + parent.getDisplayList(); + } + + private void setMatrix(ViewGroup host) { + host.transformMatrixToLocal(mMatrix); + ViewGroup parent = (ViewGroup) mView.getParent(); + parent.transformMatrixToGlobal(mMatrix); + mMatrix.postTranslate(-parent.getScrollX(), -parent.getScrollY()); + } + + public static GhostView addGhost(View view, ViewGroup viewGroup) { + if (!(view.getParent() instanceof ViewGroup)) { + throw new IllegalArgumentException("Ghosted views must be parented by a ViewGroup"); + } + ViewGroupOverlay overlay = viewGroup.getOverlay(); + ViewOverlay.OverlayViewGroup overlayViewGroup = overlay.mOverlayViewGroup; + GhostView ghostView = view.mGhostView; + if (ghostView != null) { + ViewGroup oldParent = (ViewGroup) ghostView.getParent(); + if (oldParent != overlayViewGroup) { + oldParent.removeView(ghostView); + ghostView = null; + } + } + if (ghostView == null) { + ghostView = new GhostView(view, (ViewGroup) overlayViewGroup.mHostView); + overlay.add(ghostView); + } + return ghostView; + } + + public static void removeGhost(View view) { + GhostView ghostView = view.mGhostView; + if (ghostView != null) { + ViewGroup parent = (ViewGroup) ghostView.getParent(); + parent.removeView(ghostView); + } + } + + public static GhostView getGhost(View view) { + return view.mGhostView; + } +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 4c1c2f9..81b5c20 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -17,9 +17,7 @@ package android.view; import android.animation.AnimatorInflater; -import android.animation.RevealAnimator; import android.animation.StateListAnimator; -import android.animation.ValueAnimator; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -3520,6 +3518,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private int[] mTempNestedScrollConsumed; /** + * An overlay is going to draw this View instead of being drawn as part of this + * View's parent. mGhostView is the View in the Overlay that must be invalidated + * when this view is invalidated. + */ + GhostView mGhostView; + + /** * Simple constructor to use when creating a view from code. * * @param context The Context the view is running in, through which it can @@ -10284,6 +10289,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @hide */ + @ViewDebug.ExportedProperty(category = "drawing") public float getTransitionAlpha() { return mTransformationInfo != null ? mTransformationInfo.mTransitionAlpha : 1; } @@ -11371,6 +11377,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { + if (mGhostView != null) { + mGhostView.invalidate(invalidateCache); + return; + } + if (skipInvalidate()) { return; } @@ -11408,7 +11419,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - // Damage the entire IsolatedZVolume recieving this view's shadow. + // Damage the entire IsolatedZVolume receiving this view's shadow. if (isHardwareAccelerated() && getZ() != 0) { damageShadowReceiver(); } @@ -19397,6 +19408,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return The name used of the View to be used to identify Views in Transitions or null * if no name has been given. */ + @ViewDebug.ExportedProperty public String getTransitionName() { return mTransitionName; } -- cgit v1.1