Fragment防手抖 另类实践

作者: YoKey | 来源:发表于2016-05-27 16:07 被阅读2756次

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

为什么需要防手抖处理?

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

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

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

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


无防手抖处理

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

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

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

常见方案

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

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中:
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中:

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

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

相关文章

  • Fragment防手抖 另类实践

    防抖动:防止用户手抖,连续快速地点击多次同一个按钮。 为什么需要防手抖处理? 在Fragment(v4)之间的跳转...

  • 索尼机身防抖和镜头防抖

    索尼微单系统拥有带防抖功能的机身和带防抖功能的镜头。无论防抖组件位于哪里,它们都属于光学防抖。 机身防抖 机身防抖...

  • js 防抖 节流

    节流 防抖1 防抖2

  • javaScript 防抖函数

    一. 防抖函数的定义与使用 防抖函数的定义 防抖函数的调用 二. 防抖函数应用场景 其实在HTML 和javaSc...

  • 防抖与节流

    1. 防抖函数 1.1 防抖定义: 函数防抖(debounce):当持续触发事件时(例如mousemove),一定...

  • 项目常用代码

    节流 } 防抖 } 页面滚动(requestAnimationFrame) vue全局点击防抖

  • 高质量小视频拍摄———技巧篇

    · 拍摄小技艺: 1、手不能抖,时刻对焦;自于如何不让手机不抖,我们将会在下一篇进行介绍,《抖音小视频制作之防抖篇...

  • 函数防抖防抖/节流

    众所周知,函数节流和函数防抖都是优化高频率执行js代码的一种方法,二者的区别及使用方法如下: 函数节流 函数防抖,...

  • JS函数防抖

    JS 中的函数防抖 一、什么是函数防抖? 概念: 函数防抖(debounce), 就是指触发事件后,在 n 秒内函...

  • js 防抖和节流

    防抖 防抖是js优化的重要的一部分,也是面试中手写代码最常考的题目。那么我们为什么要防抖?防抖是什么意思?比如我们...

网友评论

  • 578c78bc92ee:YoKey大神你好,文章我有几个问题想请教下

    BaseActivity和BaseFragment的处理是需要一起使用的吗
    BaseActivty中出现了mAllowClickable和mFragmentClickable两个标记,mFragmentClickable是不是写错了
    BaseFragment中的mActivity是从哪里获取的以及anim 和mNoAnim是不是同一个东西只是名字写错了 :joy:
  • 2ivy:谢谢楼主,学习了!
  • Rainbow冰糖葫芦娃:文主关于这篇文章,我有一些疑问
    1 截断整个activity的touch事件,在fragment占据了整个activity的时候表现是ok的,如果fragment没有占据整个activity的话 在交互上会有些不妥。
    2 fragment和activity的耦合性太大,使得遵守这套逻辑需要诸多的限制
    3 只涵盖到跳转fragment的case,没有涵盖例如双击某个view弹出2个dialog或者两个popwindow的case。

    目前解决上面问题,想到最好的方法是,点击时,在fragment的根root上加上一个透明的mask View,一定间隔后自动将其remove掉。希望能给出更好的方法 :grin:
    ca63366e0798:Good Idea
    YoKey:@Rainbow冰糖葫芦娃 :joy: 是的,这些问题是存在;
    该文其实是分享一个我们正常使用Fragment时防抖动的新思路,对于特殊情况可能就需要我们结合实际情况特殊处理,对于一般View弹出dialog等非跳转Fragment的情况,并不适用,这是一个仅对于Fragment的防抖动的解决方案 :stuck_out_tongue_closed_eyes:

    你的第一个疑问,我觉得一个布局内有多个Fragment的容器时,某个Fragment的跳转一般是无动画的,(有动画的情景太少了,特殊情况就需要自己来处理了),无动画时,防抖动的时间也就几十毫秒,基本是没有任何感知和影响的;
    关于耦合性问题,可以用回调来处理,低耦合;

    你说的方案很赞,更万能,只是需要Mask View的额外开支,感谢你分享的方案~ :smile:
  • PANWCS:思路间接明了,很不错。
  • 采蘑菇的里奥马:简洁明了! :+1:
  • HuDP:学习思路了,赞一个 :wink:

本文标题:Fragment防手抖 另类实践

本文链接:https://www.haomeiwen.com/subject/fikxdttx.html