Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Android animations go reactive

Android animations go reactive

RxMagic + Android Animations

Avatar for Mihael Francekovic

Mihael Francekovic

December 09, 2016

Other Decks in Technology

Transcript

  1. Before we start • Not a rxJava course • Will

    talk about ◦ ValueAnimators, ViewPropertyAnimators • Will not talk about ◦ Transitons, ObjectAnimator and such
  2. Background • MVP ◦ Keep presenters !android • Separation of

    concerns ◦ View handles how to animate ◦ Presenter handles when to animate • Nice looking code
  3. Source // Pre transform username.setAlpha(TRANSPARENT); username.setTranslationX(-TRANSLATION); // Create animator ViewPropertyAnimator

    animator = username.animate().setDuration(DURATION) .alpha(OPAQUE).translationXBy(TRANSLATION); // Wire animators together animator.withEndAction(() -> anotherAnimator.start()); animator.start();
  4. Lets not do this • Boolean flags for synchronization ◦

    withEndAction(...) hell ◦ Canceling animation ◦ Username and colors data are asynchronous ( data in general ) ◦ View notifies presenter when animation is over • Not a nice looking code, there must be a nicer way
  5. • Helps with synchronization • What our presenter cares about?

    ◦ start/stop animation • Completable ? Reactive?
  6. CompletableOnSubscribe public final class CompletableAction implements Completable.CompletableOnSubscribe { @Override public

    void call(final Completable.CompletableSubscriber completableSubscriber) { completableSubscriber.onSubscribe(subscription); completableSubscriber.onError(throwable); completableSubscriber.onCompleted(); } }
  7. Completable ViewPropertyAnimator public final class AnimateOnSubscribe implements Completable.CompletableOnSubscribe { private

    final ViewPropertyAnimator propertyAnimator; public static from(final ViewPropertyAnimator propertyAnimator) { return new AnimateOnSubscribe(propertyAnimator); } @Override public void call(final Completable.CompletableSubscriber completableSubscriber) { propertyAnimator.withEndAction(completableSubscriber::onCompleted); propertyAnimator.start(); } }
  8. Completable ViewPropertyAnimator Activity/Fragment // Pre transform … // Create animators

    ... // Wire animators Completable.create(AnimateOnSubscribe.from(firstPropertyAnimator)) .concatWith(Completable.create(AnimateOnSubscribe.from(secondPropertyAnimator))); Pros: - Animators do not know of each other Cons: - We still have to create animator for every view and pre transform that view
  9. Removing animator creation public final class AnimateFadeInOnSubscribe implements Completable.CompletableOnSubscribe {

    private final WeakReference<View> viewWeakRef; public AnimateOnSubscribe(final View view) { this.viewWeakRef = new WeakReference<>(view); } @Override public void call(final Completable.CompletableSubscriber completableSubscriber) { final View view = viewWeakRef.get(); if (view == null) { completableSubscriber.onCompleted(); return; } final ViewPropertyAnimatorCompat animator = ViewCompat.animate(view); animator.setDuration(DURATION).alpha(OPAQUE); animator.withEndAction(() -> completableSubscriber.onCompleted()); animator.start(); } }
  10. Removing animator creation public final class AnimateFadeInOnSubscribe implements Completable.CompletableOnSubscribe {

    private final WeakReference<View> viewWeakRef; public AnimateOnSubscribe(final View view) { this.viewWeakRef = new WeakReference<>(view); } @Override public void call(final Completable.CompletableSubscriber completableSubscriber) { final View view = viewWeakRef.get(); if (view == null) { completableSubscriber.onCompleted(); return; } final ViewPropertyAnimatorCompat animator = ViewCompat.animate(view); animator.setDuration(DURATION).alpha(OPAQUE); animator.withEndAction(() -> completableSubscriber.onCompleted()); animator.start(); } }
  11. Removing animator creation public final class AnimateFadeInOnSubscribe implements Completable.CompletableOnSubscribe {

    private final WeakReference<View> viewWeakRef; public AnimateOnSubscribe(final View view) { this.viewWeakRef = new WeakReference<>(view); } @Override public void call(final Completable.CompletableSubscriber completableSubscriber) { final View view = viewWeakRef.get(); if (view == null) { completableSubscriber.onCompleted(); return; } preTransformView(view); final ViewPropertyAnimatorCompat animator = ViewCompat.animate(view); animator.setDuration(DURATION).alpha(OPAQUE); animator.withEndAction(() -> completableSubscriber.onCompleted()); animator.start(); } }
  12. Removing animator creation - Activity/Fragment // Wire animations Completable.create(AnimateFadeInOnSubscribe.animate(welcomeMessage)) .concatWith(Completable.create(AnimateFadeInOnSubscribe.animate(username)));

    Pros: - View is not creating animators directly - View does not need to pre transform views it animates Cons: - Many classes Are we really satisfied?
  13. Builder creation public final class RxAnimationBuilder { public static RxAnimationBuilder

    animate(final View view) { return new RxAnimationBuilder(view, DEFAULT_DURATION, DEFAULT_DELAY, defaultInterpolator()); } private RxAnimationBuilder(final View view, final int duration, final int delay, final Interpolator interpolator) { this.viewWeakRef = new WeakReference<>(view); .... } }
  14. Animations actions public final class RxAnimationBuilder { private static final

    float OPAQUE = 1f; private static final float TRANSPARENT = 0f; final List<Action1<ViewPropertyAnimator>> preTransformActions; final List<Action1<ViewPropertyAnimator>> animateActions; public RxAnimationBuilder fadeIn() { preTransformActions.add(preTransform -> preTransform.alpha(TRANSPARENT)); animateActions.add(animate -> animate.alpha(OPAQUE)); return this; } }
  15. Animations actions public final class RxAnimationBuilder { private static final

    float OPAQUE = 1f; private static final float TRANSPARENT = 0f; final List<Action1<ViewPropertyAnimator>> preTransformActions; final List<Action1<ViewPropertyAnimator>> animateActions; public RxAnimationBuilder fadeIn() { preTransformActions.add(preTransform -> preTransform.alpha(TRANSPARENT)); animateActions.add(animate -> animate.alpha(OPAQUE)); return this; } public RxAnimationBuilder translateBy(final int x, final int y) { preTransformActions.add(preTransform -> preTransform.xBy(-x).yBy(-y)); animateActions.add(animate -> animate.xBy(x).yBy(y)); return this; } }
  16. Animation actions public final class AnimateOnSubscribe implements Completable.CompletableOnSubscribe { private

    final WeakReference<View> viewWeakRef; private final List<Action1<ViewPropertyAnimator>> preTransformActions; private final List<Action1<ViewPropertyAnimator>> animationActions; public AnimateOnSubscribe(final WeakReference<View> viewWeakRef, @Nullable final List<Action1<ViewPropertyAnimator>> preTransformActions, final List<Action1<ViewPropertyAnimator>> animationActions) { this.viewWeakRef = viewWeakRef; this.preTransformActions = preTransformActions; this.animationActions = animationActions; }
  17. Animation actions public final class AnimateOnSubscribe implements Completable.CompletableOnSubscribe { @Override

    public void call(final Completable.CompletableSubscriber completableSubscriber) { final View view = viewWeakRef.get(); if ( view == null ) { completableSubscriber.onCompleted(); return; } final ViewPropertyAnimator animator = view.animate(); applyActions(preTransformActions, animator); // Shared animator.setDuration(0).setStartDelay(0) .withEndAction(() -> runAnimation(completableSubscriber, animator)) .start(); }
  18. Animation actions public final class AnimateOnSubscribe implements Completable.CompletableOnSubscribe { private

    void applyActions(final List<Action1<ViewPropertyAnimator>> actions, final ViewPropertyAnimato animator) { If ( actions == null ) return; for (final Action1<ViewPropertyAnimator> action : actions) { action.call(animator); } } private void runAnimation(final Completable.CompletableSubscriber completableSubscriber, final ViewPropertyAnimator animator) { applyActions(animationActions, animator); animator.withEndAction(completableSubscriber::onCompleted) .start(); }
  19. Animations actions public final class RxAnimationBuilder { public Completable schedule()

    { return Completable.create(new AnimateOnSubscribe(viewWeakRef, preTransformActions, animateActions)); } }
  20. Static wrapper public final class RxAnimations { public static Completable

    hide(final View view) { return animate(view, IMMEDIATE).fadeOut().schedule(); } } }
  21. Static wrapper public final class RxAnimations { public static Completable

    hide(final View view) { return animate(view, IMMEDIATE).fadeOut().schedule(); } public static Completable fadeIn(final View view) { return animate(view).fadeIn().schedule(); } } }
  22. Static wrapper public final class RxAnimations { public static Completable

    hide(final View view) { return animate(view, IMMEDIATE).fadeOut().schedule(); } public static Completable fadeIn(final View view) { return animate(view).fadeIn().schedule(); } public static Completable enter(final View view, final int xOffset, final int yOffset) { return animate(view, new DecelerateInterpolator()).fadeIn().translateBy(xOffset, yOffset).schedule(); } } }
  23. Static wrapper public final class RxAnimations { public static Completable

    hide(final View view) { return animate(view, IMMEDIATE).fadeOut().schedule(); } public static Completable fadeIn(final View view) { return animate(view).fadeIn().schedule(); } public static Completable enter(final View view, final int xOffset, final int yOffset) { return animate(view, new DecelerateInterpolator()).fadeIn().translateBy(xOffset, yOffset).schedule(); } public static Completable enterViewsWithDelay(final int initialDelay, final int delay, final int duration, final int xOffset, final int yOffset, final View... views) { return Observable.range(0, views.length) .flatMap(i -> enter(views[i], duration, xOffset, yOffset, i * delay + initialDelay).toObservable()) .toCompletable(); } }
  24. The animation - Activity/Fragment @Override public Completable startInitialAnimation() { return

    animateTogether( fadeIn (...), slideIn(...) ) .concatWith( enterViewsWithDelay(...) ) .concatWith( rotate(...) ) }
  25. The animation - Activity/Fragment @Override public Completable setupInitialAnimation() { return

    animateTogether(hideViewGroupChildren(helloTextContainer, languageContainer), hide(paletteBackground, paletteViewPager, palettePageIndicator, settingsIcon)); } @Override public Completable startInitialAnimation() { return animateTogether( fadeIn(paletteBackground, LANGUAGE_ICON_ANIMATION_DURATION), doAfterDelay(VIEW_PAGER_ITEMS_ANIMATION_DELAY, this::animateViewPagerItems), enterViewsWithDelay(WELCOME_TEXT_INITIAL_DELAY, WELCOME_TEXT_DELAY, WELCOME_TEXT_DELAY, ITEMS_HORIZONTAL_OFFSET, userWelcomeMessage, username), doAfterDelay(LEVELS_INITIAL_DELAY, this::animatePageIndicator), enterViewsWithDelay(LE_COLORS_GROUP_INITIAL_DELAY, LANGUAGE_DETAILS_DELAY, ICONS_ANIMATION_DURATION, ITEMS_HORIZONTAL_OFFSET, leColorsLabel, seeMoreColorsButton), enterWithRotation(settingsIcon, ICONS_ANIMATION_DURATION, ITEMS_HORIZONTAL_OFFSET, 0, SETTINGS_ICON_DELAY, SETTINGS_ICON_ROTATION))); }
  26. The animation - Activity/Fragment private static final float OPAQUE =

    1.f; private static final int LANGUAGE_ICON_ANIMATION_DURATION = 800; private static final int ICONS_ANIMATION_DURATION = 600; private static final int ITEMS_HORIZONTAL_OFFSET = -48; private static final int SETTINGS_ICON_ROTATION = 60; private static final int VIEW_PAGER_ITEMS_ANIMATION_DELAY = 600; private static final int LEVELS_INITIAL_DELAY = 800; private static final int LE_COLORS_GROUP_INITIAL_DELAY = 1000; private static final int LANGUAGE_DETAILS_DELAY = 200; private static final int WELCOME_TEXT_INITIAL_DELAY = 500; private static final int WELCOME_TEXT_DELAY = 600; private static final int SETTINGS_ICON_DELAY = 600;
  27. Completable subscriber - cancellation public interface CompletableSubscriber { void onCompleted();

    void onError(Throwable e); /** * Called once by the Completable to set a Subscription on this instance which * then can be used to cancel the subscription at any time. * @param d the Subscription instance to call dispose on for cancellation, not null */ void onSubscribe(Subscription d); }
  28. Completable subscriber - cancellation public final class ClearSubscription implements Subscription

    { private boolean isUnsubscribed = false; private final Action0 clearAction; public ClearSubscription(final Action0 clearAction) { this.clearAction = clearAction; } @Override public void unsubscribe() { isUnsubscribed = true; clearAction.call(); } } @Override public boolean isUnsubscribed() { return isUnsubscribed; }
  29. Completable subscriber - cancellation public final class ClearSubscription implements Subscription

    { private boolean isUnsubscribed = false; private final Action0 clearAction; public ClearSubscription(final Action0 clearAction) { this.clearAction = clearAction; } @Override public void unsubscribe() { isUnsubscribed = true; clearAction.call(); } } @Override public boolean isUnsubscribed() { return isUnsubscribed; }
  30. Completable subscriber - cancellation public final class AnimateOnSubscribe implements Completable.CompletableOnSubscribe

    { @Override public void call(final Completable.CompletableSubscriber completableSubscriber) { final View view = viewWeakRef.get(); if ( view == null ) { completableSubscriber.onCompleted(); return; } final ViewPropertyAnimator animator = view.animate(); applyActions(preTransformActions, animator); animator.setDuration(0).setStartDelay(0) .withEndAction(() -> runAnimation(completableSubscriber, animator)) .start(); }
  31. Animation actions public final class AnimateOnSubscribe implements Completable.CompletableOnSubscribe { @Override

    public void call(final Completable.CompletableSubscriber completableSubscriber) { final View view = viewWeakRef.get(); if ( view == null ) { completableSubscriber.onCompleted(); return; } final ViewPropertyAnimator animator = view.animate(); completableSubscriber.onSubscribe(new ClearSubscription(animator::cancel)); applyActions(preTransformActions, animator); animator.setDuration(0).setStartDelay(0) .withEndAction(() -> runAnimation(completableSubscriber, animator)) .start(); }
  32. The animation - Activity/Fragment @Override public Completable startInitialAnimation() { animateTogether(

    fadeIn(paletteBackground, LANGUAGE_ICON_ANIMATION_DURATION), doAfterDelay(VIEW_PAGER_ITEMS_ANIMATION_DELAY, this::animateViewPagerItems), enterViewsWithDelay(WELCOME_TEXT_INITIAL_DELAY, WELCOME_TEXT_DELAY, WELCOME_TEXT_DELAY, ITEMS_HORIZONTAL_OFFSET, userWelcomeMessage, username), doAfterDelay(LEVELS_INITIAL_DELAY, this::animatePageIndicator), enterViewsWithDelay(LE_COLORS_GROUP_INITIAL_DELAY, LANGUAGE_DETAILS_DELAY, ICONS_ANIMATION_DURATION, ITEMS_HORIZONTAL_OFFSET, leColorsLabel, seeMoreColorsButton), enterWithRotation(settingsIcon, ICONS_ANIMATION_DURATION, ITEMS_HORIZONTAL_OFFSET, 0, SETTINGS_ICON_DELAY, SETTINGS_ICON_ROTATION))); }
  33. The animation - Activity/Fragment private BehaviorSubject<Boolean> viewReadySignal = BehaviorSubject.create(false); @Override

    public Completable startInitialAnimation() { return viewReadySignal.toCompletable().concatWith( animateTogether( fadeIn(paletteBackground, LANGUAGE_ICON_ANIMATION_DURATION), doAfterDelay(VIEW_PAGER_ITEMS_ANIMATION_DELAY, this::animateViewPagerItems), enterViewsWithDelay(WELCOME_TEXT_INITIAL_DELAY, WELCOME_TEXT_DELAY, WELCOME_TEXT_DELAY, ITEMS_HORIZONTAL_OFFSET, userWelcomeMessage, username), doAfterDelay(LEVELS_INITIAL_DELAY, this::animatePageIndicator), enterViewsWithDelay(LE_COLORS_GROUP_INITIAL_DELAY, LANGUAGE_DETAILS_DELAY, ICONS_ANIMATION_DURATION, ITEMS_HORIZONTAL_OFFSET, leColorsLabel, seeMoreColorsButton), enterWithRotation(settingsIcon, ICONS_ANIMATION_DURATION, ITEMS_HORIZONTAL_OFFSET, 0, SETTINGS_ICON_DELAY, SETTINGS_ICON_ROTATION))); }
  34. The animation - Activity/Fragment private BehaviorSubject<Boolean> viewReadySignal = BehaviorSubject.create(false); ...

    viewReadySignal.onCompleted(); ... @Override public Completable startInitialAnimation() { return viewReadySignal.toCompletable().concatWith( animateTogether( fadeIn(paletteBackground, LANGUAGE_ICON_ANIMATION_DURATION), doAfterDelay(VIEW_PAGER_ITEMS_ANIMATION_DELAY, this::animateViewPagerItems), enterViewsWithDelay(WELCOME_TEXT_INITIAL_DELAY, WELCOME_TEXT_DELAY, WELCOME_TEXT_DELAY, ITEMS_HORIZONTAL_OFFSET, userWelcomeMessage, username), doAfterDelay(LEVELS_INITIAL_DELAY, this::animatePageIndicator), enterViewsWithDelay(LE_COLORS_GROUP_INITIAL_DELAY, LANGUAGE_DETAILS_DELAY, ICONS_ANIMATION_DURATION, ITEMS_HORIZONTAL_OFFSET, leColorsLabel, seeMoreColorsButton), enterWithRotation(settingsIcon, ICONS_ANIMATION_DURATION, ITEMS_HORIZONTAL_OFFSET, 0, SETTINGS_ICON_DELAY, SETTINGS_ICON_ROTATION))); }
  35. ? ?

  36. ValueAnimator rxZation public final class RxObservableValueAnimator { private final ValueAnimator

    valueAnimator; private BehaviorSubject<Object> valueUpdateSubject;
  37. ValueAnimator rxZation public final class RxObservableValueAnimator { private final ValueAnimator

    valueAnimator; private BehaviorSubject<Object> valueUpdateSubject; public static RxObservableValueAnimator from(final ValueAnimator valueAnimator) { return new RxObservableValueAnimator(valueAnimator); }
  38. ValueAnimator rxZation public final class RxObservableValueAnimator { private RxObservableValueAnimator(final ValueAnimator

    valueAnimator) { this.valueAnimator = valueAnimator; this.valueUpdateSubject = BehaviorSubject.create(); this.valueAnimator.addUpdateListener(animation -> valueUpdateSubject.onNext(animation.getAnimatedValue())); this.valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(final Animator animation) { valueUpdateSubject.onCompleted(); } }); }
  39. Reveal animation: Activity/Fragment private void setupAnimation() { final ValueAnimator xAnimator

    = ValueAnimator.ofFloat(clickPoint.x, center.x); xAnimator.setDuration(DURATION).setInterpolator(new AccelerateInterpolator()); final ValueAnimator yAnimator = ValueAnimator.ofFloat(clickPoint.y, center.y); yAnimator.setDuration(DURATION).setInterpolator(new DecelerateInterpolator()); xObservableAnimator = RxObservableValueAnimator.from(xAnimator); yObservableAnimator = RxObservableValueAnimator.from(yAnimator); }
  40. Reveal animation: Activity/Fragment public Subscription revealAnimation() { return Observable.combineLatest(xObservableAnimator.schedule(), yObservableAnimator.schedule(),

    this::zipAnimationData) .subscribe(this::updateRevealView, Throwable::printStackTrace, () -> presenter.onRevealAnimationEnd()); }
  41. ValueAnimator rxZation, Completable public final class RxValueAnimator implements Completable.CompletableOnSubscribe {

    private final ValueAnimator valueAnimator; private final Action1<ValueAnimator> valueUpdateAction; public static RxValueAnimator from(final ValueAnimator valueAnimator, final Action1<ValueAnimator> valueUpdateAction) { return new RxValueAnimator(valueAnimator, valueUpdateAction); } private RxValueAnimator(final ValueAnimator valueAnimator, final Action1<ValueAnimator> valueUpdateAction) { this.valueAnimator = valueAnimator; this.valueAnimator.addUpdateListener(valueUpdateAction::call); this.valueUpdateAction = valueUpdateAction; }
  42. ValueAnimator rxZation, Completable public final class RxValueAnimator implements Completable.CompletableOnSubscribe {

    private final ValueAnimator valueAnimator; private final Action1<ValueAnimator> valueUpdateAction; public static RxValueAnimator from(final ValueAnimator valueAnimator, final Action1<ValueAnimator> valueUpdateAction) { return new RxValueAnimator(valueAnimator, valueUpdateAction); } private RxValueAnimator(final ValueAnimator valueAnimator, final Action1<ValueAnimator> valueUpdateAction) { this.valueAnimator = valueAnimator; this.valueAnimator.addUpdateListener(valueUpdateAction::call); this.valueUpdateAction = valueUpdateAction; }
  43. ValueAnimator rxZation, Completable public final class RxValueAnimator implements Completable.CompletableOnSubscribe {

    @Override public void call(final Completable.CompletableSubscriber completableSubscriber) { valueAnimator.start(); valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(final Animator animation) { valueAnimator.removeAllListeners(); completableSubscriber.onCompleted(); } }); } }
  44. ValueAnimator rxZation, Completable public final class RxValueAnimator implements Completable.CompletableOnSubscribe {

    @Override public void call(final Completable.CompletableSubscriber completableSubscriber) { completableSubscriber.onSubscribe(new ClearSubscription(valueAnimator::end)); valueAnimator.start(); valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(final Animator animation) { valueAnimator.removeAllListeners(); completableSubscriber.onCompleted(); } }); } }
  45. RxValueAnimator - Activity/Fragment public Completable animateWidget() { final ValueAnimator widgetAnimator

    = ValueAnimator.ofFloat(startValue, endValue); widgetAnimator.setDuration(DURATION); return Completable.create(RxValueAnimator.from(widgetAnimator, this::updateWidget)); } private void updateWidget(final ValueAnimator animator) { // Update view }
  46. Q&A