1 /* 2 * Copyright (C) 2015 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 17 package android.support.v17.preference; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.AnimatorSet; 22 import android.animation.ObjectAnimator; 23 import android.animation.TimeInterpolator; 24 import android.app.Fragment; 25 import android.graphics.Path; 26 import android.transition.Fade; 27 import android.transition.Transition; 28 import android.transition.TransitionValues; 29 import android.transition.Visibility; 30 import android.view.Gravity; 31 import android.view.View; 32 import android.view.ViewGroup; 33 import android.view.animation.DecelerateInterpolator; 34 35 /** 36 * @hide 37 */ 38 public class LeanbackPreferenceFragmentTransitionHelperApi21 { 39 addTransitions(Fragment f)40 public static void addTransitions(Fragment f) { 41 final Transition transitionStartEdge = new FadeAndShortSlideTransition(Gravity.START); 42 final Transition transitionEndEdge = new FadeAndShortSlideTransition(Gravity.END); 43 44 f.setEnterTransition(transitionEndEdge); 45 f.setExitTransition(transitionStartEdge); 46 f.setReenterTransition(transitionStartEdge); 47 f.setReturnTransition(transitionEndEdge); 48 } 49 50 private static class FadeAndShortSlideTransition extends Visibility { 51 52 private static final TimeInterpolator sDecelerate = new DecelerateInterpolator(); 53 // private static final TimeInterpolator sAccelerate = new AccelerateInterpolator(); 54 private static final String PROPNAME_SCREEN_POSITION = 55 "android:fadeAndShortSlideTransition:screenPosition"; 56 57 private CalculateSlide mSlideCalculator = sCalculateEnd; 58 private Visibility mFade = new Fade(); 59 60 private interface CalculateSlide { 61 62 /** Returns the translation value for view when it goes out of the scene */ getGoneX(ViewGroup sceneRoot, View view)63 float getGoneX(ViewGroup sceneRoot, View view); 64 } 65 66 private static final CalculateSlide sCalculateStart = new CalculateSlide() { 67 @Override 68 public float getGoneX(ViewGroup sceneRoot, View view) { 69 final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 70 final float x; 71 if (isRtl) { 72 x = view.getTranslationX() + sceneRoot.getWidth() / 4; 73 } else { 74 x = view.getTranslationX() - sceneRoot.getWidth() / 4; 75 } 76 return x; 77 } 78 }; 79 80 private static final CalculateSlide sCalculateEnd = new CalculateSlide() { 81 @Override 82 public float getGoneX(ViewGroup sceneRoot, View view) { 83 final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 84 final float x; 85 if (isRtl) { 86 x = view.getTranslationX() - sceneRoot.getWidth() / 4; 87 } else { 88 x = view.getTranslationX() + sceneRoot.getWidth() / 4; 89 } 90 return x; 91 } 92 }; 93 FadeAndShortSlideTransition(int slideEdge)94 public FadeAndShortSlideTransition(int slideEdge) { 95 setSlideEdge(slideEdge); 96 } 97 98 @Override setEpicenterCallback(EpicenterCallback epicenterCallback)99 public void setEpicenterCallback(EpicenterCallback epicenterCallback) { 100 super.setEpicenterCallback(epicenterCallback); 101 mFade.setEpicenterCallback(epicenterCallback); 102 } 103 captureValues(TransitionValues transitionValues)104 private void captureValues(TransitionValues transitionValues) { 105 View view = transitionValues.view; 106 int[] position = new int[2]; 107 view.getLocationOnScreen(position); 108 transitionValues.values.put(PROPNAME_SCREEN_POSITION, position[0]); 109 } 110 111 @Override captureStartValues(TransitionValues transitionValues)112 public void captureStartValues(TransitionValues transitionValues) { 113 super.captureStartValues(transitionValues); 114 mFade.captureStartValues(transitionValues); 115 captureValues(transitionValues); 116 } 117 118 @Override captureEndValues(TransitionValues transitionValues)119 public void captureEndValues(TransitionValues transitionValues) { 120 super.captureEndValues(transitionValues); 121 mFade.captureEndValues(transitionValues); 122 captureValues(transitionValues); 123 } 124 setSlideEdge(int slideEdge)125 public void setSlideEdge(int slideEdge) { 126 switch (slideEdge) { 127 case Gravity.START: 128 mSlideCalculator = sCalculateStart; 129 break; 130 case Gravity.END: 131 mSlideCalculator = sCalculateEnd; 132 break; 133 default: 134 throw new IllegalArgumentException("Invalid slide direction"); 135 } 136 // SidePropagation propagation = new SidePropagation(); 137 // propagation.setSide(slideEdge); 138 // setPropagation(propagation); 139 } 140 141 @Override onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)142 public Animator onAppear(ViewGroup sceneRoot, View view, 143 TransitionValues startValues, TransitionValues endValues) { 144 if (endValues == null) { 145 return null; 146 } 147 Integer position = (Integer) endValues.values.get(PROPNAME_SCREEN_POSITION); 148 float endX = view.getTranslationX(); 149 float startX = mSlideCalculator.getGoneX(sceneRoot, view); 150 final Animator slideAnimator = TranslationAnimationCreator 151 .createAnimation(view, endValues, position, 152 startX, endX, sDecelerate, this); 153 final AnimatorSet set = new AnimatorSet(); 154 set.play(slideAnimator) 155 .with(mFade.onAppear(sceneRoot, view, startValues, endValues)); 156 157 return set; 158 } 159 160 @Override onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)161 public Animator onDisappear(ViewGroup sceneRoot, View view, 162 TransitionValues startValues, TransitionValues endValues) { 163 if (startValues == null) { 164 return null; 165 } 166 Integer position = (Integer) startValues.values.get(PROPNAME_SCREEN_POSITION); 167 float startX = view.getTranslationX(); 168 float endX = mSlideCalculator.getGoneX(sceneRoot, view); 169 final Animator slideAnimator = TranslationAnimationCreator 170 .createAnimation(view, startValues, position, 171 startX, endX, sDecelerate /*sAccelerate*/, this); 172 final AnimatorSet set = new AnimatorSet(); 173 set.play(slideAnimator) 174 .with(mFade.onDisappear(sceneRoot, view, startValues, endValues)); 175 176 return set; 177 } 178 179 @Override addListener(TransitionListener listener)180 public Transition addListener(TransitionListener listener) { 181 mFade.addListener(listener); 182 return super.addListener(listener); 183 } 184 185 @Override removeListener(TransitionListener listener)186 public Transition removeListener(TransitionListener listener) { 187 mFade.removeListener(listener); 188 return super.removeListener(listener); 189 } 190 191 @Override clone()192 public Transition clone() { 193 FadeAndShortSlideTransition clone = null; 194 clone = (FadeAndShortSlideTransition) super.clone(); 195 clone.mFade = (Visibility) mFade.clone(); 196 return clone; 197 } 198 } 199 200 /** 201 * This class is used by Slide and Explode to create an animator that goes from the start 202 * position to the end position. It takes into account the canceled position so that it 203 * will not blink out or shift suddenly when the transition is interrupted. 204 */ 205 private static class TranslationAnimationCreator { 206 207 /** 208 * Creates an animator that can be used for x and/or y translations. When interrupted, 209 * it sets a tag to keep track of the position so that it may be continued from position. 210 * 211 * @param view The view being moved. This may be in the overlay for onDisappear. 212 * @param values The values containing the view in the view hierarchy. 213 * @param viewPosX The x screen coordinate of view 214 * @param startX The start translation x of view 215 * @param endX The end translation x of view 216 * @param interpolator The interpolator to use with this animator. 217 * @return An animator that moves from (startX, startY) to (endX, endY) unless there was 218 * a previous interruption, in which case it moves from the current position to 219 * (endX, endY). 220 */ createAnimation(View view, TransitionValues values, int viewPosX, float startX, float endX, TimeInterpolator interpolator, Transition transition)221 static Animator createAnimation(View view, TransitionValues values, int viewPosX, 222 float startX, float endX, TimeInterpolator interpolator, 223 Transition transition) { 224 float terminalX = view.getTranslationX(); 225 Integer startPosition = (Integer) values.view.getTag(R.id.transitionPosition); 226 if (startPosition != null) { 227 startX = startPosition - viewPosX + terminalX; 228 } 229 // Initial position is at translation startX, startY, so position is offset by that 230 // amount 231 int startPosX = viewPosX + Math.round(startX - terminalX); 232 233 view.setTranslationX(startX); 234 if (startX == endX) { 235 return null; 236 } 237 Path path = new Path(); 238 path.moveTo(startX, 0); 239 path.lineTo(endX, 0); 240 ObjectAnimator anim = 241 ObjectAnimator.ofFloat(view, View.TRANSLATION_X, View.TRANSLATION_Y, path); 242 243 TransitionPositionListener listener = new TransitionPositionListener(view, values.view, 244 startPosX, terminalX); 245 transition.addListener(listener); 246 anim.addListener(listener); 247 anim.addPauseListener(listener); 248 anim.setInterpolator(interpolator); 249 return anim; 250 } 251 252 private static class TransitionPositionListener extends AnimatorListenerAdapter implements 253 Transition.TransitionListener { 254 255 private final View mViewInHierarchy; 256 private final View mMovingView; 257 private final int mStartX; 258 private Integer mTransitionPosition; 259 private float mPausedX; 260 private final float mTerminalX; 261 TransitionPositionListener(View movingView, View viewInHierarchy, int startX, float terminalX)262 private TransitionPositionListener(View movingView, View viewInHierarchy, 263 int startX, float terminalX) { 264 mMovingView = movingView; 265 mViewInHierarchy = viewInHierarchy; 266 mStartX = startX - Math.round(mMovingView.getTranslationX()); 267 mTerminalX = terminalX; 268 mTransitionPosition = (Integer) mViewInHierarchy.getTag(R.id.transitionPosition); 269 if (mTransitionPosition != null) { 270 mViewInHierarchy.setTag(R.id.transitionPosition, null); 271 } 272 } 273 274 @Override onAnimationCancel(Animator animation)275 public void onAnimationCancel(Animator animation) { 276 mTransitionPosition = Math.round(mStartX + mMovingView.getTranslationX()); 277 mViewInHierarchy.setTag(R.id.transitionPosition, mTransitionPosition); 278 } 279 280 @Override onAnimationEnd(Animator animator)281 public void onAnimationEnd(Animator animator) { 282 } 283 284 @Override onAnimationPause(Animator animator)285 public void onAnimationPause(Animator animator) { 286 mPausedX = mMovingView.getTranslationX(); 287 mMovingView.setTranslationX(mTerminalX); 288 } 289 290 @Override onAnimationResume(Animator animator)291 public void onAnimationResume(Animator animator) { 292 mMovingView.setTranslationX(mPausedX); 293 } 294 295 @Override onTransitionStart(Transition transition)296 public void onTransitionStart(Transition transition) { 297 } 298 299 @Override onTransitionEnd(Transition transition)300 public void onTransitionEnd(Transition transition) { 301 mMovingView.setTranslationX(mTerminalX); 302 } 303 304 @Override onTransitionCancel(Transition transition)305 public void onTransitionCancel(Transition transition) { 306 } 307 308 @Override onTransitionPause(Transition transition)309 public void onTransitionPause(Transition transition) { 310 } 311 312 @Override onTransitionResume(Transition transition)313 public void onTransitionResume(Transition transition) { 314 } 315 } 316 317 } 318 319 } 320