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 17package android.support.v17.preference; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.AnimatorSet; 22import android.animation.ObjectAnimator; 23import android.animation.TimeInterpolator; 24import android.app.Fragment; 25import android.graphics.Path; 26import android.transition.Fade; 27import android.transition.Transition; 28import android.transition.TransitionValues; 29import android.transition.Visibility; 30import android.view.Gravity; 31import android.view.View; 32import android.view.ViewGroup; 33import android.view.animation.DecelerateInterpolator; 34 35/** 36 * @hide 37 */ 38public class LeanbackPreferenceFragmentTransitionHelperApi21 { 39 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 */ 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 94 public FadeAndShortSlideTransition(int slideEdge) { 95 setSlideEdge(slideEdge); 96 } 97 98 @Override 99 public void setEpicenterCallback(EpicenterCallback epicenterCallback) { 100 super.setEpicenterCallback(epicenterCallback); 101 mFade.setEpicenterCallback(epicenterCallback); 102 } 103 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 112 public void captureStartValues(TransitionValues transitionValues) { 113 super.captureStartValues(transitionValues); 114 mFade.captureStartValues(transitionValues); 115 captureValues(transitionValues); 116 } 117 118 @Override 119 public void captureEndValues(TransitionValues transitionValues) { 120 super.captureEndValues(transitionValues); 121 mFade.captureEndValues(transitionValues); 122 captureValues(transitionValues); 123 } 124 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 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 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 180 public Transition addListener(TransitionListener listener) { 181 mFade.addListener(listener); 182 return super.addListener(listener); 183 } 184 185 @Override 186 public Transition removeListener(TransitionListener listener) { 187 mFade.removeListener(listener); 188 return super.removeListener(listener); 189 } 190 191 @Override 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 */ 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 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 275 public void onAnimationCancel(Animator animation) { 276 mTransitionPosition = Math.round(mStartX + mMovingView.getTranslationX()); 277 mViewInHierarchy.setTag(R.id.transitionPosition, mTransitionPosition); 278 } 279 280 @Override 281 public void onAnimationEnd(Animator animator) { 282 } 283 284 @Override 285 public void onAnimationPause(Animator animator) { 286 mPausedX = mMovingView.getTranslationX(); 287 mMovingView.setTranslationX(mTerminalX); 288 } 289 290 @Override 291 public void onAnimationResume(Animator animator) { 292 mMovingView.setTranslationX(mPausedX); 293 } 294 295 @Override 296 public void onTransitionStart(Transition transition) { 297 } 298 299 @Override 300 public void onTransitionEnd(Transition transition) { 301 mMovingView.setTranslationX(mTerminalX); 302 } 303 304 @Override 305 public void onTransitionCancel(Transition transition) { 306 } 307 308 @Override 309 public void onTransitionPause(Transition transition) { 310 } 311 312 @Override 313 public void onTransitionResume(Transition transition) { 314 } 315 } 316 317 } 318 319} 320