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