文章目录
  1. 1. 为什么需要防手抖处理?
  2. 2. 常见方案
  3. 3. 我的方案
    1. 3.0.0.1. 在BaseActivity中:
    2. 3.0.0.2. 在BaseFragment中:
  • 最后
  • 原文链接:http://www.jianshu.com/p/9dbb03203fbc

    防抖动:防止用户手抖,连续快速地点击多次同一个按钮。

    为什么需要防手抖处理?

    在Fragment(v4)之间的跳转过程中,下面两种情况会导致用户体验不佳:(以AFragment 跳转 BFragment为例)

    1、BFragment还没真正创建时(因为Fragment事务并不是立即开始的),你手抖点了2下,就会一次性启动2个BFragment;

    2、在跳转BFragment过程中,如果有转场动画的存在,在动画结束前的任意时间,你点击了BFragment页面中可点击的区域,就会触发该点击区域的事件。

    不做任何防手抖处理的话,你可能遭遇下面GIF的情况:

    无防手抖处理

    所以对Fragment进行防手抖处理还是很有必要的,RxJava系列的Rxbinding包可以帮你解决该问题,debounce操作符可以实现防手抖,但很多情况,你会使用RxJava但不想使用Rxbinding。

    其实不管哪种方案,我想你都不会愿意在每个点击事件里都手动处理一次防抖动问题。

    我们需要一个封装的基类,可以解决防抖动问题,而不必在每个启动Fragment的按钮都进行防抖动处理。

    常见方案

    有1种常见的防手抖处理方式:通过时间差

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    private static final long DEBOUNCE_TIME = 300L;
    private long mCurrentTime;
    private void addFragment(Fragment fragment) {
    long time = System.currentTimeMillis();
    if (time - mCurrentTime < DEBOUNCE_TIME) {
    return;
    }
    mCurrentTime = time;
    getSupportFragmentManager().beginTransaction()
    .add(R.id.fl_container, fragment, fragment.getClass().getSimpleName()) // replace亦一样
    ....省略设置动画 加入回退栈等
    }

    这种方式虽然也能实现防抖动的效果,但是适用性有限;
    比如如果我需要add一个Fragment,然后紧接着该Fragment又立即需要add一个子Fragment,这时add子Fragment的操作可能就会被这个时间差屏蔽掉。

    我的方案

    鉴于上述方法的拘束性,再结合Fragment的宿主是Activity,我有了下面的方案:
    利用Activity的dispatchTouchEvent(MotionEvent ev)方法。

    大致思路是,在点击后立即让Activity拦截一切Touch事件,在目标Fragment的转场动画结束后(如果是无转场动画,则是在onActivityCreated被调用后),再让Activity取消拦截。

    接下来我们就看看具体如何实现吧!

    在BaseActivity中:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    public class BaseActivity extends AppCompatActivity {
    private boolean mAllowClickable = true;
    @Override
    public boolean dispatchTouchEvent(MotionEvent event){
    // 防抖动(防止点击速度过快)
    if (!mFragmentClickable) {
    return true;
    }
    return super.dispatchTouchEvent(ev);
    }
    /**
    * 控制Activity是否拦截Touch事件
    */
    public void allowFragmentClickable(boolean clickable){
    mAllowClickable = clickable;
    }
    /**
    * 加载Fragment (replace同理)
    */
    private void addFragment(Fragment fragment) {
    // 启动Fragment时,Activity拦截一切Touch事件
    allowFragmentClickable(false);
    getSupportFragmentManager().beginTransaction()
    .add(R.id.fl_container, fragment, fragment.getClass().getSimpleName()) // replace亦一样
    ....省略设置动画 加入回退栈等
    }
    @Override
    public void onBackPressed() {
    // 这里是防止动画过程中,按返回键取消加载Fragment
    if(!mAllowClickable){
    setFragmentClickable(true);
    }
    super.onBackPressed();
    }
    }

    所有Activity中的Touch事件,首先都需要经过Activity的dispatchTouchEvent方法,该方法通过Window分发Touch事件,如果返回true,则不再往下层分发,这时我们布局内的任何Touch事件都会“无效”。

    在通过Activity来加载Fragment时,将mAllowClickable设为false,此时只到为true时,屏幕的Touch事件将都无效。

    在BaseFragment中:

    这里将有转场动画无转场动画两种情况都进行了防手抖处理:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public class BaseFragment extends Fragment {
    // 记录是否有转场动画
    private boolean mEnterAnimFlag = false;
    @Override
    public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
    if (enter && nextAnim != 0) {
    // 记录 有转场动画
    mEnterAnimFlag = true;
    Animation anim = AnimationUtils.loadAnimation(mActivity, nextAnim);
    mNoAnim.setAnimationListener(new Animation.AnimationListener() {
    ...省略
    @Override
    public void onAnimationEnd(Animation animation) {
    // 转场动画结束时,允许Touch事件
    mActivity.allowFragmentClickable(true);
    }
    });
    }
    return super.onCreateAnimation(transit, enter, nextAnim);
    }
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    // 无转场动画时 处理
    if(!mEnterAnimFlag) {
    mActivity.allowFragmentClickable(true);
    }
    }
    }

    在Fragment初始化时,其生命周期顺序:
    … -> onCreateView -> onCreateAnimation -> onActivityCreated

    由此生命周期,再结合代码上的注释,相信你一看就懂了~

    最后

    最终,利用Android的事件分发机制,使用不到50行代码就完美解决了Fragment的防手抖处理。
    最后看下效果:

    防手抖

    想查看更多Fragment优化细节或者深度使用Fragment的小伙伴,建议看看我的这个Fragmentation


    未经许可不得转载,转载请注明zilianliuxue的blog,本人保留所有版权。

    文章目录
    1. 1. 为什么需要防手抖处理?
    2. 2. 常见方案
    3. 3. 我的方案
      1. 3.0.0.1. 在BaseActivity中:
      2. 3.0.0.2. 在BaseFragment中:
  • 最后