1/* 2 * Copyright (C) 2017 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 */ 16 17package androidx.transition; 18 19import android.annotation.SuppressLint; 20import android.graphics.Canvas; 21import android.graphics.Matrix; 22import android.view.View; 23import android.view.ViewGroup; 24import android.view.ViewParent; 25import android.view.ViewTreeObserver; 26import android.widget.FrameLayout; 27 28import androidx.annotation.NonNull; 29import androidx.core.view.ViewCompat; 30 31/** 32 * Backport of android.view.GhostView introduced in API level 21. 33 * <p> 34 * While the platform version uses ViewOverlay, this ghost view finds the closest FrameLayout in 35 * the hierarchy and adds itself there. 36 * <p> 37 * Since we cannot use RenderNode to delegate drawing, we instead use {@link View#draw(Canvas)} to 38 * draw the target view. We apply the same transformation matrix applied to the target view. For 39 * that, this view is sized as large as the parent FrameLayout (except padding) while the platform 40 * version becomes as large as the target view. 41 */ 42@SuppressLint("ViewConstructor") 43class GhostViewApi14 extends View implements GhostViewImpl { 44 45 static GhostViewImpl addGhost(View view, ViewGroup viewGroup) { 46 GhostViewApi14 ghostView = getGhostView(view); 47 if (ghostView == null) { 48 FrameLayout frameLayout = findFrameLayout(viewGroup); 49 if (frameLayout == null) { 50 return null; 51 } 52 ghostView = new GhostViewApi14(view); 53 frameLayout.addView(ghostView); 54 } 55 ghostView.mReferences++; 56 return ghostView; 57 } 58 59 static void removeGhost(View view) { 60 GhostViewApi14 ghostView = getGhostView(view); 61 if (ghostView != null) { 62 ghostView.mReferences--; 63 if (ghostView.mReferences <= 0) { 64 ViewParent parent = ghostView.getParent(); 65 if (parent instanceof ViewGroup) { 66 ViewGroup group = (ViewGroup) parent; 67 group.endViewTransition(ghostView); 68 group.removeView(ghostView); 69 } 70 } 71 } 72 } 73 74 /** 75 * Find the closest FrameLayout in the ascendant hierarchy from the specified {@code 76 * viewGroup}. 77 */ 78 private static FrameLayout findFrameLayout(ViewGroup viewGroup) { 79 while (!(viewGroup instanceof FrameLayout)) { 80 ViewParent parent = viewGroup.getParent(); 81 if (!(parent instanceof ViewGroup)) { 82 return null; 83 } 84 viewGroup = (ViewGroup) parent; 85 } 86 return (FrameLayout) viewGroup; 87 } 88 89 /** The target view */ 90 final View mView; 91 92 /** The parent of the view that is disappearing at the beginning of the animation */ 93 ViewGroup mStartParent; 94 95 /** The view that is disappearing at the beginning of the animation */ 96 View mStartView; 97 98 /** The number of references to this ghost view */ 99 int mReferences; 100 101 /** The horizontal distance from the ghost view to the target view */ 102 private int mDeltaX; 103 104 /** The horizontal distance from the ghost view to the target view */ 105 private int mDeltaY; 106 107 /** The current transformation matrix of the target view */ 108 Matrix mCurrentMatrix; 109 110 /** The matrix applied to the ghost view canvas */ 111 private final Matrix mMatrix = new Matrix(); 112 113 private final ViewTreeObserver.OnPreDrawListener mOnPreDrawListener = 114 new ViewTreeObserver.OnPreDrawListener() { 115 @Override 116 public boolean onPreDraw() { 117 // The target view was invalidated; get the transformation. 118 mCurrentMatrix = mView.getMatrix(); 119 // We draw the view. 120 ViewCompat.postInvalidateOnAnimation(GhostViewApi14.this); 121 if (mStartParent != null && mStartView != null) { 122 mStartParent.endViewTransition(mStartView); 123 ViewCompat.postInvalidateOnAnimation(mStartParent); 124 mStartParent = null; 125 mStartView = null; 126 } 127 return true; 128 } 129 }; 130 131 GhostViewApi14(View view) { 132 super(view.getContext()); 133 mView = view; 134 setLayerType(LAYER_TYPE_HARDWARE, null); 135 } 136 137 @Override 138 protected void onAttachedToWindow() { 139 super.onAttachedToWindow(); 140 setGhostView(mView, this); 141 // Calculate the deltas 142 final int[] location = new int[2]; 143 final int[] viewLocation = new int[2]; 144 getLocationOnScreen(location); 145 mView.getLocationOnScreen(viewLocation); 146 viewLocation[0] = (int) (viewLocation[0] - mView.getTranslationX()); 147 viewLocation[1] = (int) (viewLocation[1] - mView.getTranslationY()); 148 mDeltaX = viewLocation[0] - location[0]; 149 mDeltaY = viewLocation[1] - location[1]; 150 // Monitor invalidation of the target view. 151 mView.getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener); 152 // Make the target view invisible because we draw it instead. 153 mView.setVisibility(INVISIBLE); 154 } 155 156 @Override 157 protected void onDetachedFromWindow() { 158 mView.getViewTreeObserver().removeOnPreDrawListener(mOnPreDrawListener); 159 mView.setVisibility(VISIBLE); 160 setGhostView(mView, null); 161 super.onDetachedFromWindow(); 162 } 163 164 @Override 165 protected void onDraw(Canvas canvas) { 166 // Apply the matrix while adjusting the coordinates 167 mMatrix.set(mCurrentMatrix); 168 mMatrix.postTranslate(mDeltaX, mDeltaY); 169 canvas.setMatrix(mMatrix); 170 // Draw the target 171 mView.draw(canvas); 172 } 173 174 @Override 175 public void setVisibility(int visibility) { 176 super.setVisibility(visibility); 177 mView.setVisibility(visibility == VISIBLE ? INVISIBLE : VISIBLE); 178 } 179 180 @Override 181 public void reserveEndViewTransition(ViewGroup viewGroup, View view) { 182 mStartParent = viewGroup; 183 mStartView = view; 184 } 185 186 private static void setGhostView(@NonNull View view, GhostViewApi14 ghostView) { 187 view.setTag(R.id.ghost_view, ghostView); 188 } 189 190 static GhostViewApi14 getGhostView(@NonNull View view) { 191 return (GhostViewApi14) view.getTag(R.id.ghost_view); 192 } 193 194} 195