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