使用 AnimationUtils 显示和隐藏 FAB 时的竞争条件
Race condition while showing and hiding FAB with AnimationUtils
在允许用户通过社交网络登录的 Android 应用程序中,我使用以下代码显示和隐藏 FAB
:
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
时,我首先向 ServiceIntent
发送请求以从 SQLite 获取用户数据并调用以下方法将我的 UI 设置为"waiting" 状态:
private void setBusy(boolean busy) {
mProgressBar.setVisibility(busy ? View.VISIBLE : View.INVISIBLE);
showFab(!busy);
}
几乎立即来自 SQLite 的响应通过 LocalBroadcastManager
返回,我再次调用上述方法: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);
}
}
请在这里建议可以做什么。
我已经在调试器中多次调试我的应用程序。 setBusy
(和 showFab
)在显示片段时仅被调用两次,但两次调用都发生得非常快 - 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);
}
}
首先要指出的是,没有什么比赛车条件更好的了。正确的术语只是竞争条件。为将来牢记这一点 ;-)
我想您正在使用 BroadcastReceiver 来接收消息,并且您已经创建了对象并由于动画的缘故在单独的线程中将其告知 运行。这意味着您的 showFab 方法可以一次调用两次。要处理此问题,请将方法定义为 synchronized.
您的每个动画 运行 在不同的线程中。如您所说,这会导致竞争条件。
这是怎么回事:
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);
}
}
这是我自己的解决方案-
首先,使用 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) {
}
});
在允许用户通过社交网络登录的 Android 应用程序中,我使用以下代码显示和隐藏 FAB
:
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
时,我首先向 ServiceIntent
发送请求以从 SQLite 获取用户数据并调用以下方法将我的 UI 设置为"waiting" 状态:
private void setBusy(boolean busy) {
mProgressBar.setVisibility(busy ? View.VISIBLE : View.INVISIBLE);
showFab(!busy);
}
几乎立即来自 SQLite 的响应通过 LocalBroadcastManager
返回,我再次调用上述方法: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);
}
}
请在这里建议可以做什么。
我已经在调试器中多次调试我的应用程序。 setBusy
(和 showFab
)在显示片段时仅被调用两次,但两次调用都发生得非常快 - 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);
}
}
首先要指出的是,没有什么比赛车条件更好的了。正确的术语只是竞争条件。为将来牢记这一点 ;-)
我想您正在使用 BroadcastReceiver 来接收消息,并且您已经创建了对象并由于动画的缘故在单独的线程中将其告知 运行。这意味着您的 showFab 方法可以一次调用两次。要处理此问题,请将方法定义为 synchronized.
您的每个动画 运行 在不同的线程中。如您所说,这会导致竞争条件。
这是怎么回事: 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);
}
}
这是我自己的解决方案-
首先,使用 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) {
}
});