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 17 package androidx.transition; 18 19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21 import android.animation.Animator; 22 import android.animation.TimeInterpolator; 23 import android.content.Context; 24 import android.content.res.TypedArray; 25 import android.util.AttributeSet; 26 import android.view.Gravity; 27 import android.view.View; 28 import android.view.ViewGroup; 29 import android.view.animation.AccelerateInterpolator; 30 import android.view.animation.DecelerateInterpolator; 31 32 import androidx.annotation.IntDef; 33 import androidx.annotation.NonNull; 34 import androidx.annotation.RestrictTo; 35 import androidx.core.content.res.TypedArrayUtils; 36 import androidx.core.view.ViewCompat; 37 38 import org.xmlpull.v1.XmlPullParser; 39 40 import java.lang.annotation.Retention; 41 import java.lang.annotation.RetentionPolicy; 42 43 /** 44 * This transition tracks changes to the visibility of target views in the 45 * start and end scenes and moves views in or out from one of the edges of the 46 * scene. Visibility is determined by both the 47 * {@link View#setVisibility(int)} state of the view as well as whether it 48 * is parented in the current view hierarchy. Disappearing Views are 49 * limited as described in {@link Visibility#onDisappear(android.view.ViewGroup, 50 * TransitionValues, int, TransitionValues, int)}. 51 */ 52 public class Slide extends Visibility { 53 54 private static final TimeInterpolator sDecelerate = new DecelerateInterpolator(); 55 private static final TimeInterpolator sAccelerate = new AccelerateInterpolator(); 56 private static final String PROPNAME_SCREEN_POSITION = "android:slide:screenPosition"; 57 private CalculateSlide mSlideCalculator = sCalculateBottom; 58 private int mSlideEdge = Gravity.BOTTOM; 59 60 /** @hide */ 61 @RestrictTo(LIBRARY_GROUP) 62 @Retention(RetentionPolicy.SOURCE) 63 @IntDef({Gravity.LEFT, Gravity.TOP, Gravity.RIGHT, Gravity.BOTTOM, Gravity.START, Gravity.END}) 64 public @interface GravityFlag { 65 } 66 67 private interface CalculateSlide { 68 69 /** Returns the translation value for view when it goes out of the scene */ 70 float getGoneX(ViewGroup sceneRoot, View view); 71 72 /** Returns the translation value for view when it goes out of the scene */ 73 float getGoneY(ViewGroup sceneRoot, View view); 74 } 75 76 private abstract static class CalculateSlideHorizontal implements CalculateSlide { 77 78 @Override 79 public float getGoneY(ViewGroup sceneRoot, View view) { 80 return view.getTranslationY(); 81 } 82 } 83 84 private abstract static class CalculateSlideVertical implements CalculateSlide { 85 86 @Override 87 public float getGoneX(ViewGroup sceneRoot, View view) { 88 return view.getTranslationX(); 89 } 90 } 91 92 private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() { 93 @Override 94 public float getGoneX(ViewGroup sceneRoot, View view) { 95 return view.getTranslationX() - sceneRoot.getWidth(); 96 } 97 }; 98 99 private static final CalculateSlide sCalculateStart = new CalculateSlideHorizontal() { 100 @Override 101 public float getGoneX(ViewGroup sceneRoot, View view) { 102 final boolean isRtl = ViewCompat.getLayoutDirection(sceneRoot) 103 == ViewCompat.LAYOUT_DIRECTION_RTL; 104 final float x; 105 if (isRtl) { 106 x = view.getTranslationX() + sceneRoot.getWidth(); 107 } else { 108 x = view.getTranslationX() - sceneRoot.getWidth(); 109 } 110 return x; 111 } 112 }; 113 114 private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() { 115 @Override 116 public float getGoneY(ViewGroup sceneRoot, View view) { 117 return view.getTranslationY() - sceneRoot.getHeight(); 118 } 119 }; 120 121 private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() { 122 @Override 123 public float getGoneX(ViewGroup sceneRoot, View view) { 124 return view.getTranslationX() + sceneRoot.getWidth(); 125 } 126 }; 127 128 private static final CalculateSlide sCalculateEnd = new CalculateSlideHorizontal() { 129 @Override 130 public float getGoneX(ViewGroup sceneRoot, View view) { 131 final boolean isRtl = ViewCompat.getLayoutDirection(sceneRoot) 132 == ViewCompat.LAYOUT_DIRECTION_RTL; 133 final float x; 134 if (isRtl) { 135 x = view.getTranslationX() - sceneRoot.getWidth(); 136 } else { 137 x = view.getTranslationX() + sceneRoot.getWidth(); 138 } 139 return x; 140 } 141 }; 142 143 private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() { 144 @Override 145 public float getGoneY(ViewGroup sceneRoot, View view) { 146 return view.getTranslationY() + sceneRoot.getHeight(); 147 } 148 }; 149 150 /** 151 * Constructor using the default {@link Gravity#BOTTOM} 152 * slide edge direction. 153 */ 154 public Slide() { 155 setSlideEdge(Gravity.BOTTOM); 156 } 157 158 /** 159 * Constructor using the provided slide edge direction. 160 */ 161 public Slide(int slideEdge) { 162 setSlideEdge(slideEdge); 163 } 164 165 public Slide(Context context, AttributeSet attrs) { 166 super(context, attrs); 167 TypedArray a = context.obtainStyledAttributes(attrs, Styleable.SLIDE); 168 int edge = TypedArrayUtils.getNamedInt(a, (XmlPullParser) attrs, "slideEdge", 169 Styleable.Slide.SLIDE_EDGE, Gravity.BOTTOM); 170 a.recycle(); 171 //noinspection WrongConstant 172 setSlideEdge(edge); 173 } 174 175 private void captureValues(TransitionValues transitionValues) { 176 View view = transitionValues.view; 177 int[] position = new int[2]; 178 view.getLocationOnScreen(position); 179 transitionValues.values.put(PROPNAME_SCREEN_POSITION, position); 180 } 181 182 @Override 183 public void captureStartValues(@NonNull TransitionValues transitionValues) { 184 super.captureStartValues(transitionValues); 185 captureValues(transitionValues); 186 } 187 188 @Override 189 public void captureEndValues(@NonNull TransitionValues transitionValues) { 190 super.captureEndValues(transitionValues); 191 captureValues(transitionValues); 192 } 193 194 /** 195 * Change the edge that Views appear and disappear from. 196 * 197 * @param slideEdge The edge of the scene to use for Views appearing and disappearing. One of 198 * {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP}, 199 * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM}, 200 * {@link android.view.Gravity#START}, {@link android.view.Gravity#END}. 201 */ 202 public void setSlideEdge(@GravityFlag int slideEdge) { 203 switch (slideEdge) { 204 case Gravity.LEFT: 205 mSlideCalculator = sCalculateLeft; 206 break; 207 case Gravity.TOP: 208 mSlideCalculator = sCalculateTop; 209 break; 210 case Gravity.RIGHT: 211 mSlideCalculator = sCalculateRight; 212 break; 213 case Gravity.BOTTOM: 214 mSlideCalculator = sCalculateBottom; 215 break; 216 case Gravity.START: 217 mSlideCalculator = sCalculateStart; 218 break; 219 case Gravity.END: 220 mSlideCalculator = sCalculateEnd; 221 break; 222 default: 223 throw new IllegalArgumentException("Invalid slide direction"); 224 } 225 mSlideEdge = slideEdge; 226 SidePropagation propagation = new SidePropagation(); 227 propagation.setSide(slideEdge); 228 setPropagation(propagation); 229 } 230 231 /** 232 * Returns the edge that Views appear and disappear from. 233 * 234 * @return the edge of the scene to use for Views appearing and disappearing. One of 235 * {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP}, 236 * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM}, 237 * {@link android.view.Gravity#START}, {@link android.view.Gravity#END}. 238 */ 239 @GravityFlag 240 public int getSlideEdge() { 241 return mSlideEdge; 242 } 243 244 @Override 245 public Animator onAppear(ViewGroup sceneRoot, View view, 246 TransitionValues startValues, TransitionValues endValues) { 247 if (endValues == null) { 248 return null; 249 } 250 int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_POSITION); 251 float endX = view.getTranslationX(); 252 float endY = view.getTranslationY(); 253 float startX = mSlideCalculator.getGoneX(sceneRoot, view); 254 float startY = mSlideCalculator.getGoneY(sceneRoot, view); 255 return TranslationAnimationCreator 256 .createAnimation(view, endValues, position[0], position[1], 257 startX, startY, endX, endY, sDecelerate); 258 } 259 260 @Override 261 public Animator onDisappear(ViewGroup sceneRoot, View view, 262 TransitionValues startValues, TransitionValues endValues) { 263 if (startValues == null) { 264 return null; 265 } 266 int[] position = (int[]) startValues.values.get(PROPNAME_SCREEN_POSITION); 267 float startX = view.getTranslationX(); 268 float startY = view.getTranslationY(); 269 float endX = mSlideCalculator.getGoneX(sceneRoot, view); 270 float endY = mSlideCalculator.getGoneY(sceneRoot, view); 271 return TranslationAnimationCreator 272 .createAnimation(view, startValues, position[0], position[1], 273 startX, startY, endX, endY, sAccelerate); 274 } 275 276 } 277