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.support.v17.leanback.transition; 17 18import android.animation.Animator; 19import android.animation.AnimatorListenerAdapter; 20import android.animation.AnimatorSet; 21import android.animation.ObjectAnimator; 22import android.animation.TimeInterpolator; 23import android.animation.ValueAnimator; 24import android.content.Context; 25import android.content.res.TypedArray; 26import android.graphics.Rect; 27import android.util.AttributeSet; 28import android.util.Log; 29import android.util.Property; 30import android.view.Gravity; 31import android.view.View; 32import android.view.ViewGroup; 33import android.view.animation.AccelerateInterpolator; 34import android.view.animation.AnimationUtils; 35import android.view.animation.DecelerateInterpolator; 36import android.transition.Visibility; 37import android.transition.Transition; 38import android.transition.TransitionValues; 39import android.support.v17.leanback.R; 40 41/** 42 * Slide distance toward/from a edge. 43 * This is a limited Slide implementation for KitKat without propagation support. 44 * @hide 45 */ 46class SlideKitkat extends Visibility { 47 private static final String TAG = "SlideKitkat"; 48 49 private static final TimeInterpolator sDecelerate = new DecelerateInterpolator(); 50 private static final TimeInterpolator sAccelerate = new AccelerateInterpolator(); 51 52 private int mSlideEdge; 53 private CalculateSlide mSlideCalculator; 54 55 private interface CalculateSlide { 56 /** Returns the translation value for view when it out of the scene */ 57 float getGone(View view); 58 59 /** Returns the translation value for view when it is in the scene */ 60 float getHere(View view); 61 62 /** Returns the property to animate translation */ 63 Property<View, Float> getProperty(); 64 } 65 66 private static abstract class CalculateSlideHorizontal implements CalculateSlide { 67 @Override 68 public float getHere(View view) { 69 return view.getTranslationX(); 70 } 71 72 @Override 73 public Property<View, Float> getProperty() { 74 return View.TRANSLATION_X; 75 } 76 } 77 78 private static abstract class CalculateSlideVertical implements CalculateSlide { 79 @Override 80 public float getHere(View view) { 81 return view.getTranslationY(); 82 } 83 84 @Override 85 public Property<View, Float> getProperty() { 86 return View.TRANSLATION_Y; 87 } 88 } 89 90 private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() { 91 @Override 92 public float getGone(View view) { 93 return view.getTranslationX() - view.getWidth(); 94 } 95 }; 96 97 private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() { 98 @Override 99 public float getGone(View view) { 100 return view.getTranslationY() - view.getHeight(); 101 } 102 }; 103 104 private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() { 105 @Override 106 public float getGone(View view) { 107 return view.getTranslationX() + view.getWidth(); 108 } 109 }; 110 111 private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() { 112 @Override 113 public float getGone(View view) { 114 return view.getTranslationY() + view.getHeight(); 115 } 116 }; 117 118 private static final CalculateSlide sCalculateStart = new CalculateSlideHorizontal() { 119 @Override 120 public float getGone(View view) { 121 if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { 122 return view.getTranslationX() + view.getWidth(); 123 } else { 124 return view.getTranslationX() - view.getWidth(); 125 } 126 } 127 }; 128 129 private static final CalculateSlide sCalculateEnd = new CalculateSlideHorizontal() { 130 @Override 131 public float getGone(View view) { 132 if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { 133 return view.getTranslationX() - view.getWidth(); 134 } else { 135 return view.getTranslationX() + view.getWidth(); 136 } 137 } 138 }; 139 140 public SlideKitkat() { 141 setSlideEdge(Gravity.BOTTOM); 142 } 143 144 public SlideKitkat(Context context, AttributeSet attrs) { 145 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbSlide); 146 int edge = a.getInt(R.styleable.lbSlide_lb_slideEdge, Gravity.BOTTOM); 147 setSlideEdge(edge); 148 long duration = a.getInt(R.styleable.lbSlide_android_duration, -1); 149 if (duration >= 0) { 150 setDuration(duration); 151 } 152 long startDelay = a.getInt(R.styleable.lbSlide_android_startDelay, -1); 153 if (startDelay > 0) { 154 setStartDelay(startDelay); 155 } 156 final int resID = a.getResourceId(R.styleable.lbSlide_android_interpolator, 0); 157 if (resID > 0) { 158 setInterpolator(AnimationUtils.loadInterpolator(context, resID)); 159 } 160 a.recycle(); 161 } 162 163 /** 164 * Change the edge that Views appear and disappear from. 165 * 166 * @param slideEdge The edge of the scene to use for Views appearing and disappearing. One of 167 * {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP}, 168 * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM}, 169 * {@link android.view.Gravity#START}, {@link android.view.Gravity#END}. 170 */ 171 public void setSlideEdge(int slideEdge) { 172 switch (slideEdge) { 173 case Gravity.LEFT: 174 mSlideCalculator = sCalculateLeft; 175 break; 176 case Gravity.TOP: 177 mSlideCalculator = sCalculateTop; 178 break; 179 case Gravity.RIGHT: 180 mSlideCalculator = sCalculateRight; 181 break; 182 case Gravity.BOTTOM: 183 mSlideCalculator = sCalculateBottom; 184 break; 185 case Gravity.START: 186 mSlideCalculator = sCalculateStart; 187 break; 188 case Gravity.END: 189 mSlideCalculator = sCalculateEnd; 190 break; 191 default: 192 throw new IllegalArgumentException("Invalid slide direction"); 193 } 194 mSlideEdge = slideEdge; 195 } 196 197 /** 198 * Returns the edge that Views appear and disappear from. 199 * @return the edge of the scene to use for Views appearing and disappearing. One of 200 * {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP}, 201 * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM}, 202 * {@link android.view.Gravity#START}, {@link android.view.Gravity#END}. 203 */ 204 public int getSlideEdge() { 205 return mSlideEdge; 206 } 207 208 private Animator createAnimation(final View view, Property<View, Float> property, 209 float start, float end, float terminalValue, TimeInterpolator interpolator, 210 int finalVisibility) { 211 float[] startPosition = (float[]) view.getTag(R.id.lb_slide_transition_value); 212 if (startPosition != null) { 213 start = View.TRANSLATION_Y == property ? startPosition[1] : startPosition[0]; 214 view.setTag(R.id.lb_slide_transition_value, null); 215 } 216 final ObjectAnimator anim = ObjectAnimator.ofFloat(view, property, start, end); 217 218 SlideAnimatorListener listener = new SlideAnimatorListener(view, property, terminalValue, end, 219 finalVisibility); 220 anim.addListener(listener); 221 anim.addPauseListener(listener); 222 anim.setInterpolator(interpolator); 223 return anim; 224 } 225 226 @Override 227 public Animator onAppear(ViewGroup sceneRoot, 228 TransitionValues startValues, int startVisibility, 229 TransitionValues endValues, int endVisibility) { 230 View view = (endValues != null) ? endValues.view : null; 231 if (view == null) { 232 return null; 233 } 234 float end = mSlideCalculator.getHere(view); 235 float start = mSlideCalculator.getGone(view); 236 return createAnimation(view, mSlideCalculator.getProperty(), start, end, end, sDecelerate, 237 View.VISIBLE); 238 } 239 240 @Override 241 public Animator onDisappear(ViewGroup sceneRoot, 242 TransitionValues startValues, int startVisibility, 243 TransitionValues endValues, int endVisibility) { 244 View view = (startValues != null) ? startValues.view : null; 245 if (view == null) { 246 return null; 247 } 248 float start = mSlideCalculator.getHere(view); 249 float end = mSlideCalculator.getGone(view); 250 251 return createAnimation(view, mSlideCalculator.getProperty(), start, end, start, 252 sAccelerate, View.INVISIBLE); 253 } 254 255 private static class SlideAnimatorListener extends AnimatorListenerAdapter { 256 private boolean mCanceled = false; 257 private float mPausedValue; 258 private final View mView; 259 private final float mEndValue; 260 private final float mTerminalValue; 261 private final int mFinalVisibility; 262 private final Property<View, Float> mProp; 263 264 public SlideAnimatorListener(View view, Property<View, Float> prop, 265 float terminalValue, float endValue, int finalVisibility) { 266 mProp = prop; 267 mView = view; 268 mTerminalValue = terminalValue; 269 mEndValue = endValue; 270 mFinalVisibility = finalVisibility; 271 view.setVisibility(View.VISIBLE); 272 } 273 274 @Override 275 public void onAnimationCancel(Animator animator) { 276 float[] transitionPosition = new float[2]; 277 transitionPosition[0] = mView.getTranslationX(); 278 transitionPosition[1] = mView.getTranslationY(); 279 mView.setTag(R.id.lb_slide_transition_value, transitionPosition); 280 mProp.set(mView, mTerminalValue); 281 mCanceled = true; 282 } 283 284 @Override 285 public void onAnimationEnd(Animator animator) { 286 if (!mCanceled) { 287 mProp.set(mView, mTerminalValue); 288 } 289 mView.setVisibility(mFinalVisibility); 290 } 291 292 @Override 293 public void onAnimationPause(Animator animator) { 294 mPausedValue = mProp.get(mView); 295 mProp.set(mView, mEndValue); 296 mView.setVisibility(mFinalVisibility); 297 } 298 299 @Override 300 public void onAnimationResume(Animator animator) { 301 mProp.set(mView, mPausedValue); 302 mView.setVisibility(View.VISIBLE); 303 } 304 } 305}