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