blob: 9c91c99537b72e4c447a4af02fc821b520e18444 [file] [log] [blame]
/*
* Copyright (C) 2017 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.support.transition;
import android.annotation.SuppressLint;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.v4.view.ViewCompat;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
/**
* Backport of android.view.GhostView introduced in API level 21.
* <p>
* While the platform version uses ViewOverlay, this ghost view finds the closest FrameLayout in
* the hierarchy and adds itself there.
* <p>
* Since we cannot use RenderNode to delegate drawing, we instead use {@link View#draw(Canvas)} to
* draw the target view. We apply the same transformation matrix applied to the target view. For
* that, this view is sized as large as the parent FrameLayout (except padding) while the platform
* version becomes as large as the target view.
*/
@RequiresApi(14)
@SuppressLint("ViewConstructor")
class GhostViewApi14 extends View implements GhostViewImpl {
static class Creator implements GhostViewImpl.Creator {
@Override
public GhostViewImpl addGhost(View view, ViewGroup viewGroup, Matrix matrix) {
GhostViewApi14 ghostView = getGhostView(view);
if (ghostView == null) {
FrameLayout frameLayout = findFrameLayout(viewGroup);
if (frameLayout == null) {
return null;
}
ghostView = new GhostViewApi14(view);
frameLayout.addView(ghostView);
}
ghostView.mReferences++;
return ghostView;
}
@Override
public void removeGhost(View view) {
GhostViewApi14 ghostView = getGhostView(view);
if (ghostView != null) {
ghostView.mReferences--;
if (ghostView.mReferences <= 0) {
ViewParent parent = ghostView.getParent();
if (parent instanceof ViewGroup) {
ViewGroup group = (ViewGroup) parent;
group.endViewTransition(ghostView);
group.removeView(ghostView);
}
}
}
}
/**
* Find the closest FrameLayout in the ascendant hierarchy from the specified {@code
* viewGroup}.
*/
private static FrameLayout findFrameLayout(ViewGroup viewGroup) {
while (!(viewGroup instanceof FrameLayout)) {
ViewParent parent = viewGroup.getParent();
if (!(parent instanceof ViewGroup)) {
return null;
}
viewGroup = (ViewGroup) parent;
}
return (FrameLayout) viewGroup;
}
}
/** The target view */
final View mView;
/** The parent of the view that is disappearing at the beginning of the animation */
ViewGroup mStartParent;
/** The view that is disappearing at the beginning of the animation */
View mStartView;
/** The number of references to this ghost view */
int mReferences;
/** The horizontal distance from the ghost view to the target view */
private int mDeltaX;
/** The horizontal distance from the ghost view to the target view */
private int mDeltaY;
/** The current transformation matrix of the target view */
Matrix mCurrentMatrix;
/** The matrix applied to the ghost view canvas */
private final Matrix mMatrix = new Matrix();
private final ViewTreeObserver.OnPreDrawListener mOnPreDrawListener =
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
// The target view was invalidated; get the transformation.
mCurrentMatrix = mView.getMatrix();
// We draw the view.
ViewCompat.postInvalidateOnAnimation(GhostViewApi14.this);
if (mStartParent != null && mStartView != null) {
mStartParent.endViewTransition(mStartView);
ViewCompat.postInvalidateOnAnimation(mStartParent);
mStartParent = null;
mStartView = null;
}
return true;
}
};
GhostViewApi14(View view) {
super(view.getContext());
mView = view;
setLayerType(LAYER_TYPE_HARDWARE, null);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
setGhostView(mView, this);
// Calculate the deltas
final int[] location = new int[2];
final int[] viewLocation = new int[2];
getLocationOnScreen(location);
mView.getLocationOnScreen(viewLocation);
mDeltaX = viewLocation[0] - location[0];
mDeltaY = viewLocation[1] - location[1];
// Monitor invalidation of the target view.
mView.getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener);
// Make the target view invisible because we draw it instead.
mView.setVisibility(INVISIBLE);
}
@Override
protected void onDetachedFromWindow() {
mView.getViewTreeObserver().removeOnPreDrawListener(mOnPreDrawListener);
mView.setVisibility(VISIBLE);
setGhostView(mView, null);
super.onDetachedFromWindow();
}
@Override
protected void onDraw(Canvas canvas) {
// Apply the matrix while adjusting the coordinates
mMatrix.set(mCurrentMatrix);
mMatrix.postTranslate(mDeltaX, mDeltaY);
canvas.setMatrix(mMatrix);
// Draw the target
mView.draw(canvas);
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
mView.setVisibility(visibility == VISIBLE ? INVISIBLE : VISIBLE);
}
@Override
public void reserveEndViewTransition(ViewGroup viewGroup, View view) {
mStartParent = viewGroup;
mStartView = view;
}
private static void setGhostView(@NonNull View view, GhostViewApi14 ghostView) {
view.setTag(R.id.ghost_view, ghostView);
}
static GhostViewApi14 getGhostView(@NonNull View view) {
return (GhostViewApi14) view.getTag(R.id.ghost_view);
}
}