6

在允许用户通过社交网络登录的 Android 应用程序中,我FAB使用以下代码显示和隐藏 a:

应用截图

public abstract class LoginFragment extends Fragment {

    private FloatingActionButton mFab;
    private Animation mShowFab;
    private Animation mHideFab;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mShowFab = AnimationUtils.makeInAnimation(getContext(), false);
        mShowFab.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                mFab.setVisibility(View.VISIBLE);
            }

            @Override
            public void onAnimationEnd(Animation animation) {
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }
        });

        mHideFab = AnimationUtils.makeOutAnimation(getContext(), true);
        mHideFab.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                mFab.setVisibility(View.INVISIBLE);
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }
        });
    }

    private void showFab(boolean show) {
        boolean visible = mFab.isShown();

        if (show && !visible) {
                mFab.startAnimation(mShowFab);
        } else if (!show && visible) {
                mFab.startAnimation(mHideFab);
        }
    }

showFab当我调用上述方法足够慢时,这很好用。

在开始任何动画之前,我会检查当前的FloatingActionButton可见性,以便动画只播放一次- 即使我showFab(true)连续调用几次。

我的问题:

LoginFragment我的应用程序中显示 a 时,我首先向 a 发送请求以ServiceIntent从 SQLite 获取用户数据并调用以下方法将我的 UI 设置为“等待”状态:

private void setBusy(boolean busy) {
    mProgressBar.setVisibility(busy ? View.VISIBLE : View.INVISIBLE);
    showFab(!busy);
}

几乎立即来自 SQLite 的响应回来了 - 通过 aLocalBroadcastManager我再次调用上述方法:setBusy(false).

然后发生错误并且FAB不可见。

如果我用无FAB动画代码替换该方法,一切正常:

private void showFab(boolean show) {
    mFab.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
}

但是对于动画 - 似乎会出现赛车情况。

作为一种解决方法,我尝试取消这两个动画 - 但这无济于事:

private void showFab(boolean show) {
    mShowFab.cancel();
    mShowFab.reset();

    mHideFab.cancel();
    mHideFab.reset();

    boolean visible = mFab.isShown();

    if (show && !visible) {
            mFab.startAnimation(mShowFab);
    } else if (!show && visible) {
            mFab.startAnimation(mHideFab);
    }
}

请建议在这里可以做什么。

我已经在调试器中多次通过我的应用程序。( setBusyand showFab) 在显示 Fragment 时仅被调用两次,但两次调用都发生得非常快 - 并且FAB未显示 -

第一次运行:

第一次运行

第二次运行:

第二次运行

更新:

不幸的是,制作该方法synchronized也无济于事 -FAB保持隐藏:

private synchronized void showFab(boolean show) {
    mShowFab.cancel();
    mShowFab.reset();

    mHideFab.cancel();
    mHideFab.reset();

    boolean visible = mFab.isShown();

    if (show && !visible) {
        mFab.startAnimation(mShowFab);
    } else if (!show && visible) {
        mFab.startAnimation(mHideFab);
    }
}
4

3 回答 3

3

您的每个动画都在单独的线程中运行。正如你所说,这会导致竞争条件。

这是发生的事情:showFab 以 true 触发,然后以 false 触发。

  • mShowFab onAnimationStart 方法执行并将 FAB 设置为可见。
  • mHideFab onAnimationStart 方法执行并且什么都不做。
  • mShowFab onAnimationEnd 方法执行并且什么都不做。
  • mHideFab onAnimationEnd 方法执行并将 FAB 设置为不可见。
  • annnd 你最终会得到一个不可见的 FAB,它应该是可见的。

第二次运行中的变量显示 mShowFab 动画将永远不会运行。

您应该能够通过侦听隐藏动画的结束来解决竞争条件。像这样的东西:

private void showFab(boolean show) {
    if (show) {
        // if you have an animation currently running and you want to show the fab 
        if (mFab.getAnimation() != null && !mFab.getAnimation().hasEnded()) {
            // then wait for it to complete and begin the next one
            mFab.getAnimation().setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {

                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    mFab.setVisibility(View.INVISIBLE);
                    mFab.startAnimation(mShowFab);
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });
        } else {
            mFab.startAnimation(mShowFab);
        }
    } else {
        mFab.startAnimation(mHideFab);
    }
}
于 2015-10-24T01:24:13.147 回答
1

首先要指出的是,没有什么比赛车条件更好的了。正确的术语只是竞争条件。请记住这一点,以备将来使用;-)

我想您正在使用 BroadcastReceiver 来接收消息,并且您已经创建了对象并告诉它由于动画而在单独的线程中运行。这意味着您的 showFab 方法可以一次调用两次。要处理此问题,请将方法定义为synchronized

于 2015-10-22T13:27:06.557 回答
0

这是我自己的解决方案 -

首先,使用synchronized在这里没有帮助,因为动画在同一个线程中运行。

我的解决方案是引入一个单独的boolean变量来保存预期的最终 FAB 状态(可见或不可见):

private FloatingActionButton mFab;
private boolean mFabVisible;
private Animation mShowFab;
private Animation mHideFab;

private void showFab(boolean show) {

    if (show && !mFabVisible) {
        mFab.startAnimation(mShowFab);
    } else if (!show && mFabVisible) {
        mFab.startAnimation(mHideFab);
    }

    mFabVisible = show;
}

另外,我认为另一种可能性可能是调用mFab.setVisibility(View.VISIBLE)两次 - 如下面的代码所示。但我还没有真正测试过:

    mShowFab = AnimationUtils.makeInAnimation(getContext(), false);
    mShowFab.setAnimationListener(new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {
            mFab.setVisibility(View.VISIBLE);
        }

        @Override
        public void onAnimationEnd(Animation animation) {
            mFab.setVisibility(View.VISIBLE);
        }

        @Override
        public void onAnimationRepeat(Animation animation) {
        }
    });

    mHideFab = AnimationUtils.makeOutAnimation(getContext(), true);
    mHideFab.setAnimationListener(new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {
        }

        @Override
        public void onAnimationEnd(Animation animation) {
            mFab.setVisibility(View.INVISIBLE);
        }

        @Override
        public void onAnimationRepeat(Animation animation) {
        }
    });
于 2015-11-03T15:04:57.137 回答