FadeAndShortSlide.java revision 99ec8b0cb375f7e5577ea3ec9f09e6ff7a95de0d
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 */ 16package android.support.v17.leanback.transition; 17 18import android.animation.Animator; 19import android.animation.AnimatorSet; 20import android.animation.TimeInterpolator; 21import android.content.Context; 22import android.content.res.TypedArray; 23import android.graphics.Rect; 24import android.support.v17.leanback.R; 25import android.transition.Fade; 26import android.transition.Transition; 27import android.transition.TransitionValues; 28import android.transition.Visibility; 29import android.util.AttributeSet; 30import android.view.Gravity; 31import android.view.View; 32import android.view.ViewGroup; 33import android.view.animation.DecelerateInterpolator; 34 35/** 36 * Execute horizontal slide of 1/4 width and fade (to workaround bug 23718734) 37 * @hide 38 */ 39public class FadeAndShortSlide extends Visibility { 40 41 private static final TimeInterpolator sDecelerate = new DecelerateInterpolator(); 42 // private static final TimeInterpolator sAccelerate = new AccelerateInterpolator(); 43 private static final String PROPNAME_SCREEN_POSITION = 44 "android:fadeAndShortSlideTransition:screenPosition"; 45 46 private CalculateSlide mSlideCalculator; 47 private Visibility mFade = new Fade(); 48 private float mDistance = -1; 49 50 private static abstract class CalculateSlide { 51 52 CalculateSlide() { 53 } 54 55 /** Returns the translation X value for view when it goes out of the scene */ 56 float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) { 57 return view.getTranslationX(); 58 } 59 60 /** Returns the translation Y value for view when it goes out of the scene */ 61 float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) { 62 return view.getTranslationY(); 63 } 64 } 65 66 float getHorizontalDistance(ViewGroup sceneRoot) { 67 return mDistance >= 0 ? mDistance : (sceneRoot.getWidth() / 4); 68 } 69 70 float getVerticalDistance(ViewGroup sceneRoot) { 71 return mDistance >= 0 ? mDistance : (sceneRoot.getHeight() / 4); 72 } 73 74 final static CalculateSlide sCalculateStart = new CalculateSlide() { 75 @Override 76 public float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) { 77 final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 78 final float x; 79 if (isRtl) { 80 x = view.getTranslationX() + t.getHorizontalDistance(sceneRoot); 81 } else { 82 x = view.getTranslationX() - t.getHorizontalDistance(sceneRoot); 83 } 84 return x; 85 } 86 }; 87 88 final static CalculateSlide sCalculateEnd = new CalculateSlide() { 89 @Override 90 public float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) { 91 final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 92 final float x; 93 if (isRtl) { 94 x = view.getTranslationX() - t.getHorizontalDistance(sceneRoot); 95 } else { 96 x = view.getTranslationX() + t.getHorizontalDistance(sceneRoot); 97 } 98 return x; 99 } 100 }; 101 102 final static CalculateSlide sCalculateStartEnd = new CalculateSlide() { 103 @Override 104 public float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) { 105 final int viewCenter = position[0] + view.getWidth() / 2; 106 sceneRoot.getLocationOnScreen(position); 107 Rect center = t.getEpicenter(); 108 final int sceneRootCenter = center == null ? (position[0] + sceneRoot.getWidth() / 2) 109 : center.centerX(); 110 if (viewCenter < sceneRootCenter) { 111 return view.getTranslationX() - t.getHorizontalDistance(sceneRoot); 112 } else { 113 return view.getTranslationX() + t.getHorizontalDistance(sceneRoot); 114 } 115 } 116 }; 117 118 final static CalculateSlide sCalculateBottom = new CalculateSlide() { 119 @Override 120 public float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) { 121 return view.getTranslationY() + t.getVerticalDistance(sceneRoot); 122 } 123 }; 124 125 final static CalculateSlide sCalculateTop = new CalculateSlide() { 126 @Override 127 public float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) { 128 return view.getTranslationY() - t.getVerticalDistance(sceneRoot); 129 } 130 }; 131 132 final CalculateSlide sCalculateTopBottom = new CalculateSlide() { 133 @Override 134 public float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) { 135 final int viewCenter = position[1] + view.getHeight() / 2; 136 sceneRoot.getLocationOnScreen(position); 137 Rect center = getEpicenter(); 138 final int sceneRootCenter = center == null ? (position[1] + sceneRoot.getHeight() / 2) 139 : center.centerY(); 140 if (viewCenter < sceneRootCenter) { 141 return view.getTranslationY() - t.getVerticalDistance(sceneRoot); 142 } else { 143 return view.getTranslationY() + t.getVerticalDistance(sceneRoot); 144 } 145 } 146 }; 147 148 public FadeAndShortSlide() { 149 this(Gravity.START); 150 } 151 152 public FadeAndShortSlide(int slideEdge) { 153 setSlideEdge(slideEdge); 154 } 155 156 public FadeAndShortSlide(Context context, AttributeSet attrs) { 157 super(context, attrs); 158 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbSlide); 159 int edge = a.getInt(R.styleable.lbSlide_lb_slideEdge, Gravity.START); 160 setSlideEdge(edge); 161 a.recycle(); 162 } 163 164 @Override 165 public void setEpicenterCallback(EpicenterCallback epicenterCallback) { 166 mFade.setEpicenterCallback(epicenterCallback); 167 super.setEpicenterCallback(epicenterCallback); 168 } 169 170 private void captureValues(TransitionValues transitionValues) { 171 View view = transitionValues.view; 172 int[] position = new int[2]; 173 view.getLocationOnScreen(position); 174 transitionValues.values.put(PROPNAME_SCREEN_POSITION, position); 175 } 176 177 @Override 178 public void captureStartValues(TransitionValues transitionValues) { 179 mFade.captureStartValues(transitionValues); 180 super.captureStartValues(transitionValues); 181 captureValues(transitionValues); 182 } 183 184 @Override 185 public void captureEndValues(TransitionValues transitionValues) { 186 mFade.captureEndValues(transitionValues); 187 super.captureEndValues(transitionValues); 188 captureValues(transitionValues); 189 } 190 191 public void setSlideEdge(int slideEdge) { 192 switch (slideEdge) { 193 case Gravity.START: 194 mSlideCalculator = sCalculateStart; 195 break; 196 case Gravity.END: 197 mSlideCalculator = sCalculateEnd; 198 break; 199 case Gravity.START | Gravity.END: 200 mSlideCalculator = sCalculateStartEnd; 201 break; 202 case Gravity.TOP: 203 mSlideCalculator = sCalculateTop; 204 break; 205 case Gravity.BOTTOM: 206 mSlideCalculator = sCalculateBottom; 207 break; 208 case Gravity.TOP | Gravity.BOTTOM: 209 mSlideCalculator = sCalculateTopBottom; 210 break; 211 default: 212 throw new IllegalArgumentException("Invalid slide direction"); 213 } 214 } 215 216 @Override 217 public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, 218 TransitionValues endValues) { 219 if (endValues == null) { 220 return null; 221 } 222 if (sceneRoot == view) { 223 // workaround b/25375640, avoid run animation on sceneRoot 224 return null; 225 } 226 int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_POSITION); 227 int left = position[0]; 228 int top = position[1]; 229 float endX = view.getTranslationX(); 230 float startX = mSlideCalculator.getGoneX(this, sceneRoot, view, position); 231 float endY = view.getTranslationY(); 232 float startY = mSlideCalculator.getGoneY(this, sceneRoot, view, position); 233 final Animator slideAnimator = TranslationAnimationCreator.createAnimation(view, endValues, 234 left, top, startX, startY, endX, endY, sDecelerate, this); 235 final Animator fadeAnimator = mFade.onAppear(sceneRoot, view, startValues, endValues); 236 if (slideAnimator == null) { 237 return fadeAnimator; 238 } else if (fadeAnimator == null) { 239 return slideAnimator; 240 } 241 final AnimatorSet set = new AnimatorSet(); 242 set.play(slideAnimator).with(fadeAnimator); 243 244 return set; 245 } 246 247 @Override 248 public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, 249 TransitionValues endValues) { 250 if (startValues == null) { 251 return null; 252 } 253 if (sceneRoot == view) { 254 // workaround b/25375640, avoid run animation on sceneRoot 255 return null; 256 } 257 int[] position = (int[]) startValues.values.get(PROPNAME_SCREEN_POSITION); 258 int left = position[0]; 259 int top = position[1]; 260 float startX = view.getTranslationX(); 261 float endX = mSlideCalculator.getGoneX(this, sceneRoot, view, position); 262 float startY = view.getTranslationY(); 263 float endY = mSlideCalculator.getGoneY(this, sceneRoot, view, position); 264 final Animator slideAnimator = TranslationAnimationCreator.createAnimation(view, 265 startValues, left, top, startX, startY, endX, endY, sDecelerate /* sAccelerate */, 266 this); 267 final Animator fadeAnimator = mFade.onDisappear(sceneRoot, view, startValues, endValues); 268 if (slideAnimator == null) { 269 return fadeAnimator; 270 } else if (fadeAnimator == null) { 271 return slideAnimator; 272 } 273 final AnimatorSet set = new AnimatorSet(); 274 set.play(slideAnimator).with(fadeAnimator); 275 276 return set; 277 } 278 279 @Override 280 public Transition addListener(TransitionListener listener) { 281 mFade.addListener(listener); 282 return super.addListener(listener); 283 } 284 285 @Override 286 public Transition removeListener(TransitionListener listener) { 287 mFade.removeListener(listener); 288 return super.removeListener(listener); 289 } 290 291 /** 292 * Returns distance to slide. When negative value is returned, it will use 1/4 of 293 * sceneRoot dimension. 294 */ 295 public float getDistance() { 296 return mDistance; 297 } 298 299 /** 300 * Set distance to slide, default value is -1. when negative value is set, it will use 1/4 of 301 * sceneRoot dimension. 302 * @param distance Pixels to slide. 303 */ 304 public void setDistance(float distance) { 305 mDistance = distance; 306 } 307 308 @Override 309 public Transition clone() { 310 FadeAndShortSlide clone = null; 311 clone = (FadeAndShortSlide) super.clone(); 312 clone.mFade = (Visibility) mFade.clone(); 313 return clone; 314 } 315} 316