android.support.transition.GhostViewApi14.java Source code

Java tutorial

Introduction

Here is the source code for android.support.transition.GhostViewApi14.java

Source

/*
 * 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);
        viewLocation[0] = (int) (viewLocation[0] - mView.getTranslationX());
        viewLocation[1] = (int) (viewLocation[1] - mView.getTranslationY());
        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);
    }

}