RxJava for Android中的动画

时间:2016-09-03 21:07:31

标签: android functional-programming rx-java rx-android

我正在尝试使用RxJava为Android中的视图设置动画。我需要在一个间隔内更改视图的Y位置,但它无法正常工作。这是我开始的功能:

private void initiateCurrentWordFall(){
    txtFalling.setY(fallingWordStartY);
    Observable<Long> observable = Observable.interval(100, TimeUnit.MILLISECONDS);

    fallingSubs = observable
            .subscribeOn(Schedulers.computation())
            .observeOn(Schedulers.computation())
            .subscribe(new Observer<Long>() {

                @Override
                public void onCompleted() {

                }

                @Override
                public void onError(Throwable e) {

                }

                @Override
                public void onNext(Long aLong) {
                    if(txtFalling.getY()!=screenHeight) {
                        txtFalling.setY(txtFalling.getY() + 1);
                        Log.d("Txt Y", String.valueOf(txtFalling.getY()));
                    }
                    else
                        wrongAnswerOrWordFeel();
                }
            });
}

我的执行中screenHeight等于1776。

这段代码应该每0.1秒将视图的Y减1个像素。我甚至试着记录正在发生的事情,这就是我得到的:

09-03 18:03:13.882 701-701/com.feliperrm.babbelproject D/Txt Y: -70.0
09-03 18:03:13.890 701-701/com.feliperrm.babbelproject D/Txt Y: -69.0
09-03 18:03:13.922 701-701/com.feliperrm.babbelproject D/Txt Y: -68.0
09-03 18:03:13.923 701-701/com.feliperrm.babbelproject D/Txt Y: -67.0
09-03 18:03:13.964 701-701/com.feliperrm.babbelproject D/Txt Y: -117.0
09-03 18:03:13.974 701-701/com.feliperrm.babbelproject D/Txt Y: -116.0
09-03 18:03:13.990 701-701/com.feliperrm.babbelproject D/Txt Y: -115.0
09-03 18:03:14.021 701-701/com.feliperrm.babbelproject D/Txt Y: -114.0
09-03 18:03:14.044 701-701/com.feliperrm.babbelproject D/Txt Y: -113.0
09-03 18:03:14.058 701-701/com.feliperrm.babbelproject D/Txt Y: -112.0

这种情况一直重复(文本视图到达某一点并重新开始)。我做错了什么?

编辑:

发现了问题,但我仍然对它为何会发生这种情况感到好奇。 我上面发布的这个函数被多次调用,因此当RxJava使textView掉线时,第一行

txtFalling.setY(fallingWordStartY);

让它再次恢复。但为什么函数被多次调用? 这就是我称之为的地方:

txtCurrentWord.animate().setDuration(1000).setStartDelay(150).alpha(1f).scaleX(1f).scaleY(1f).setInterpolator(new AccelerateInterpolator())
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        txtCurrentWord.animate()
                                .y(topWordY - txtCurrentWord.getHeight()/5)
                                .scaleX(0.6f).scaleY(0.6f).setInterpolator(new AccelerateDecelerateInterpolator()).setStartDelay(250).setDuration(750).start();
                        bgNewWord.animate().alpha(0f).setStartDelay(250).setDuration(750)
                                .setListener(new AnimatorListenerAdapter() {
                                    @Override
                                    public void onAnimationEnd(Animator animation) {
                                        super.onAnimationEnd(animation);
                                        bgNewWord.setVisibility(View.GONE);
                                        initiateCurrentWordFall();
                                    }
                                }).start();
                    }
                }).start();

很抱歉它非常难看,有很多嵌套动画,因为我只能在另一个结束后开始。奇怪的是onAnimationEnd被多次调用,即使我没有重新启动它们!

2 个答案:

答案 0 :(得分:1)

我刚刚发现了自己的问题。对于那些感兴趣的人,我在一个有动画监听器设置的视图上开始动画,所以即使在这个动画设置期间没有设置这个监听器,它仍然被调用,并且它被设置为一遍又一遍地调用自己。我只需要在内部txtCurrentWord动画上添加.setListener(null)。

答案 1 :(得分:0)

当你最初考虑使用RxJava链接你的动画时,我认为你走在了正确的轨道上。作为一个示例,我编写了一个将动画转换为Observable的包装器。 observable返回正在设置动画的View,以便于清理。作为奖励,它不使用AnimationListener。这样做的副作用是,如果取消动画,您将无法收到通知,但如果需要,可以使用AnimationListener替换.withEndAction()。这个基本习惯用法也可以用于其他动画类型,例如ObjectAnimators。

public class RxAnimation {
    public static Observable<View> wrapViewPropertyAnimation(View view, Func1<View, ViewPropertyAnimator> animationCall) {
        return Observable.<View>create(emitter -> {
            ViewPropertyAnimator animation =
                    animationCall.call(view)
                    .withEndAction(() -> emitter.onNext(view));
            animation.start();
            emitter.setSubscription(new MainThreadSubscription() {
                @Override
                protected void onUnsubscribe() {
                    animation.cancel();
                }
            });
        }, Emitter.BackpressureMode.BUFFER)
                .subscribeOn(AndroidSchedulers.mainThread());
    }

    public static void initiateCurrentWordFall() {
        // Whatever this does...
    }

    public static void runSampleCode(View txtCurrentWord, View bgNewWord, int topWordY) {
        wrapViewPropertyAnimation(txtCurrentWord, v -> 
                v.animate().setDuration(1000).setStartDelay(150)
                .alpha(1f).scaleX(1f).scaleY(1f)
                .setInterpolator(new AccelerateInterpolator()))
                .switchMap(lastView -> wrapViewPropertyAnimation(lastView, v ->
                        v.animate().y(topWordY - txtCurrentWord.getHeight()/5)
                        .scaleX(0.6f).scaleY(0.6f)
                        .setInterpolator(new AccelerateDecelerateInterpolator())
                        .setStartDelay(250).setDuration(750)))
                .switchMap(lastView -> wrapViewPropertyAnimation(bgNewWord, v ->
                        v.animate().alpha(0f).setStartDelay(250).setDuration(750))
                )
                .doOnNext(lastView -> {
                    lastView.setVisibility(View.GONE);
                    initiateCurrentWordFall();
                })
                .observeOn(AndroidSchedulers.mainThread())
                // You should make sure your observable is bound to an Activity lifecycle
                // to avoid leaks. I use RxLifecycle for this.
                .subscribe();
    }
}