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