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 package com.android.tv.common.ui.setup.animation; 17 18 import android.animation.Animator; 19 import android.animation.AnimatorListenerAdapter; 20 import android.animation.AnimatorSet; 21 import android.animation.TimeInterpolator; 22 import android.transition.Fade; 23 import android.transition.Transition; 24 import android.transition.TransitionValues; 25 import android.transition.Visibility; 26 import android.view.Gravity; 27 import android.view.View; 28 import android.view.ViewGroup; 29 import android.view.ViewParent; 30 import android.view.animation.AccelerateInterpolator; 31 import android.view.animation.DecelerateInterpolator; 32 import java.util.ArrayList; 33 import java.util.Collections; 34 import java.util.Comparator; 35 import java.util.List; 36 37 /** Execute horizontal slide of 1/4 width and fade (to workaround bug 23718734) */ 38 public class FadeAndShortSlide extends Visibility { 39 private static final TimeInterpolator APPEAR_INTERPOLATOR = new DecelerateInterpolator(); 40 private static final TimeInterpolator DISAPPEAR_INTERPOLATOR = new AccelerateInterpolator(); 41 42 private static final String PROPNAME_SCREEN_POSITION = 43 "android_fadeAndShortSlideTransition_screenPosition"; 44 private static final String PROPNAME_DELAY = "propname_delay"; 45 46 private static final int DEFAULT_DISTANCE = 200; 47 48 private abstract static class CalculateSlide { 49 /** Returns the translation value for view when it goes out of the scene */ getGoneX( ViewGroup sceneRoot, View view, int[] position, int distance)50 public abstract float getGoneX( 51 ViewGroup sceneRoot, View view, int[] position, int distance); 52 } 53 54 private static final CalculateSlide sCalculateStart = 55 new CalculateSlide() { 56 @Override 57 public float getGoneX( 58 ViewGroup sceneRoot, View view, int[] position, int distance) { 59 final boolean isRtl = 60 sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 61 final float x; 62 if (isRtl) { 63 x = view.getTranslationX() + distance; 64 } else { 65 x = view.getTranslationX() - distance; 66 } 67 return x; 68 } 69 }; 70 71 private static final CalculateSlide sCalculateEnd = 72 new CalculateSlide() { 73 @Override 74 public float getGoneX( 75 ViewGroup sceneRoot, View view, int[] position, int distance) { 76 final boolean isRtl = 77 sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 78 final float x; 79 if (isRtl) { 80 x = view.getTranslationX() - distance; 81 } else { 82 x = view.getTranslationX() + distance; 83 } 84 return x; 85 } 86 }; 87 88 private static final ViewPositionComparator sViewPositionComparator = 89 new ViewPositionComparator(); 90 91 private int mSlideEdge; 92 private CalculateSlide mSlideCalculator = sCalculateEnd; 93 private Visibility mFade = new Fade(); 94 95 // TODO: Consider using TransitionPropagation. 96 private final int[] mParentIdsForDelay; 97 private int mDistance = DEFAULT_DISTANCE; 98 FadeAndShortSlide()99 public FadeAndShortSlide() { 100 this(Gravity.START); 101 } 102 FadeAndShortSlide(int slideEdge)103 public FadeAndShortSlide(int slideEdge) { 104 this(slideEdge, null); 105 } 106 FadeAndShortSlide(int slideEdge, int[] parentIdsForDelay)107 public FadeAndShortSlide(int slideEdge, int[] parentIdsForDelay) { 108 setSlideEdge(slideEdge); 109 mParentIdsForDelay = parentIdsForDelay; 110 } 111 112 @Override setEpicenterCallback(EpicenterCallback epicenterCallback)113 public void setEpicenterCallback(EpicenterCallback epicenterCallback) { 114 super.setEpicenterCallback(epicenterCallback); 115 mFade.setEpicenterCallback(epicenterCallback); 116 } 117 captureValues(TransitionValues transitionValues)118 private void captureValues(TransitionValues transitionValues) { 119 View view = transitionValues.view; 120 int[] position = new int[2]; 121 view.getLocationOnScreen(position); 122 transitionValues.values.put(PROPNAME_SCREEN_POSITION, position); 123 } 124 getDelayOrder(View view, boolean appear)125 private int getDelayOrder(View view, boolean appear) { 126 if (mParentIdsForDelay == null) { 127 return -1; 128 } 129 final View parentForDelay = findParentForDelay(view); 130 if (parentForDelay == null || !(parentForDelay instanceof ViewGroup)) { 131 return -1; 132 } 133 List<View> transitionTargets = new ArrayList<>(); 134 getTransitionTargets((ViewGroup) parentForDelay, transitionTargets); 135 sViewPositionComparator.mParentForDelay = parentForDelay; 136 sViewPositionComparator.mIsLtr = view.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR; 137 sViewPositionComparator.mToLeft = 138 sViewPositionComparator.mIsLtr 139 ? mSlideEdge == (appear ? Gravity.END : Gravity.START) 140 : mSlideEdge == (appear ? Gravity.START : Gravity.END); 141 Collections.sort(transitionTargets, sViewPositionComparator); 142 return transitionTargets.indexOf(view); 143 } 144 findParentForDelay(View view)145 private View findParentForDelay(View view) { 146 if (isParentForDelay(view.getId())) { 147 return view; 148 } 149 View parent = view; 150 while (parent.getParent() instanceof View) { 151 parent = (View) parent.getParent(); 152 if (isParentForDelay(parent.getId())) { 153 return parent; 154 } 155 } 156 return null; 157 } 158 isParentForDelay(int viewId)159 private boolean isParentForDelay(int viewId) { 160 for (int id : mParentIdsForDelay) { 161 if (id == viewId) { 162 return true; 163 } 164 } 165 return false; 166 } 167 getTransitionTargets(ViewGroup parent, List<View> transitionTargets)168 private void getTransitionTargets(ViewGroup parent, List<View> transitionTargets) { 169 int count = parent.getChildCount(); 170 for (int i = 0; i < count; ++i) { 171 View child = parent.getChildAt(i); 172 if (child instanceof ViewGroup && !((ViewGroup) child).isTransitionGroup()) { 173 getTransitionTargets((ViewGroup) child, transitionTargets); 174 } else { 175 transitionTargets.add(child); 176 } 177 } 178 } 179 180 @Override captureStartValues(TransitionValues transitionValues)181 public void captureStartValues(TransitionValues transitionValues) { 182 super.captureStartValues(transitionValues); 183 mFade.captureStartValues(transitionValues); 184 captureValues(transitionValues); 185 int delayIndex = getDelayOrder(transitionValues.view, false); 186 if (delayIndex > 0) { 187 transitionValues.values.put( 188 PROPNAME_DELAY, delayIndex * SetupAnimationHelper.DELAY_BETWEEN_SIBLINGS_MS); 189 } 190 } 191 192 @Override captureEndValues(TransitionValues transitionValues)193 public void captureEndValues(TransitionValues transitionValues) { 194 super.captureEndValues(transitionValues); 195 mFade.captureEndValues(transitionValues); 196 captureValues(transitionValues); 197 int delayIndex = getDelayOrder(transitionValues.view, true); 198 if (delayIndex > 0) { 199 transitionValues.values.put( 200 PROPNAME_DELAY, delayIndex * SetupAnimationHelper.DELAY_BETWEEN_SIBLINGS_MS); 201 } 202 } 203 setSlideEdge(int slideEdge)204 public void setSlideEdge(int slideEdge) { 205 mSlideEdge = slideEdge; 206 switch (slideEdge) { 207 case Gravity.START: 208 mSlideCalculator = sCalculateStart; 209 break; 210 case Gravity.END: 211 mSlideCalculator = sCalculateEnd; 212 break; 213 default: 214 throw new IllegalArgumentException("Invalid slide direction"); 215 } 216 } 217 218 @Override onAppear( ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)219 public Animator onAppear( 220 ViewGroup sceneRoot, 221 View view, 222 TransitionValues startValues, 223 TransitionValues endValues) { 224 if (endValues == null) { 225 return null; 226 } 227 int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_POSITION); 228 int left = position[0]; 229 float endX = view.getTranslationX(); 230 float startX = mSlideCalculator.getGoneX(sceneRoot, view, position, mDistance); 231 final Animator slideAnimator = 232 TranslationAnimationCreator.createAnimation( 233 view, endValues, left, startX, endX, APPEAR_INTERPOLATOR, this); 234 if (slideAnimator == null) { 235 return null; 236 } 237 mFade.setInterpolator(APPEAR_INTERPOLATOR); 238 final AnimatorSet set = new AnimatorSet(); 239 set.play(slideAnimator).with(mFade.onAppear(sceneRoot, view, startValues, endValues)); 240 Long delay = (Long) endValues.values.get(PROPNAME_DELAY); 241 if (delay != null) { 242 set.setStartDelay(delay); 243 } 244 return set; 245 } 246 247 @Override onDisappear( ViewGroup sceneRoot, final View view, TransitionValues startValues, TransitionValues endValues)248 public Animator onDisappear( 249 ViewGroup sceneRoot, 250 final View view, 251 TransitionValues startValues, 252 TransitionValues endValues) { 253 if (startValues == null) { 254 return null; 255 } 256 int[] position = (int[]) startValues.values.get(PROPNAME_SCREEN_POSITION); 257 int left = position[0]; 258 float startX = view.getTranslationX(); 259 float endX = mSlideCalculator.getGoneX(sceneRoot, view, position, mDistance); 260 final Animator slideAnimator = 261 TranslationAnimationCreator.createAnimation( 262 view, startValues, left, startX, endX, DISAPPEAR_INTERPOLATOR, this); 263 if (slideAnimator == null) { // slideAnimator is null if startX == endX 264 return null; 265 } 266 267 mFade.setInterpolator(DISAPPEAR_INTERPOLATOR); 268 final Animator fadeAnimator = mFade.onDisappear(sceneRoot, view, startValues, endValues); 269 if (fadeAnimator == null) { 270 return null; 271 } 272 fadeAnimator.addListener( 273 new AnimatorListenerAdapter() { 274 @Override 275 public void onAnimationEnd(Animator animator) { 276 fadeAnimator.removeListener(this); 277 view.setAlpha(0.0f); 278 } 279 }); 280 281 final AnimatorSet set = new AnimatorSet(); 282 set.play(slideAnimator).with(fadeAnimator); 283 Long delay = (Long) startValues.values.get(PROPNAME_DELAY); 284 if (delay != null) { 285 set.setStartDelay(delay); 286 } 287 return set; 288 } 289 290 @Override addListener(TransitionListener listener)291 public Transition addListener(TransitionListener listener) { 292 mFade.addListener(listener); 293 return super.addListener(listener); 294 } 295 296 @Override removeListener(TransitionListener listener)297 public Transition removeListener(TransitionListener listener) { 298 mFade.removeListener(listener); 299 return super.removeListener(listener); 300 } 301 302 @Override clone()303 public Transition clone() { 304 FadeAndShortSlide clone = (FadeAndShortSlide) super.clone(); 305 clone.mFade = (Visibility) mFade.clone(); 306 return clone; 307 } 308 309 @Override setDuration(long duration)310 public Transition setDuration(long duration) { 311 long scaledDuration = SetupAnimationHelper.applyAnimationTimeScale(duration); 312 mFade.setDuration(scaledDuration); 313 return super.setDuration(scaledDuration); 314 } 315 316 /** Sets the moving distance in pixel. */ setDistance(int distance)317 public void setDistance(int distance) { 318 mDistance = distance; 319 } 320 321 private static class ViewPositionComparator implements Comparator<View> { 322 View mParentForDelay; 323 boolean mIsLtr; 324 boolean mToLeft; 325 326 @Override compare(View lhs, View rhs)327 public int compare(View lhs, View rhs) { 328 int start1; 329 int start2; 330 if (mIsLtr) { 331 start1 = getRelativeLeft(lhs, mParentForDelay); 332 start2 = getRelativeLeft(rhs, mParentForDelay); 333 } else { 334 start1 = getRelativeRight(lhs, mParentForDelay); 335 start2 = getRelativeRight(rhs, mParentForDelay); 336 } 337 if (mToLeft) { 338 if (start1 > start2) { 339 return 1; 340 } else if (start1 < start2) { 341 return -1; 342 } 343 } else { 344 if (start1 > start2) { 345 return -1; 346 } else if (start1 < start2) { 347 return 1; 348 } 349 } 350 int top1 = getRelativeTop(lhs, mParentForDelay); 351 int top2 = getRelativeTop(rhs, mParentForDelay); 352 return Integer.compare(top1, top2); 353 } 354 getRelativeLeft(View child, View ancestor)355 private int getRelativeLeft(View child, View ancestor) { 356 ViewParent parent = child.getParent(); 357 int left = child.getLeft(); 358 while (parent instanceof View) { 359 if (parent == ancestor) { 360 break; 361 } 362 left += ((View) parent).getLeft(); 363 parent = parent.getParent(); 364 } 365 return left; 366 } 367 getRelativeRight(View child, View ancestor)368 private int getRelativeRight(View child, View ancestor) { 369 ViewParent parent = child.getParent(); 370 int right = child.getRight(); 371 while (parent instanceof View) { 372 if (parent == ancestor) { 373 break; 374 } 375 right += ((View) parent).getLeft(); 376 parent = parent.getParent(); 377 } 378 return right; 379 } 380 getRelativeTop(View child, View ancestor)381 private int getRelativeTop(View child, View ancestor) { 382 ViewParent parent = child.getParent(); 383 int top = child.getTop(); 384 while (parent instanceof View) { 385 if (parent == ancestor) { 386 break; 387 } 388 top += ((View) parent).getTop(); 389 parent = parent.getParent(); 390 } 391 return top; 392 } 393 } 394 } 395