解决下拉栏横竖滑动的层级问题

最近接到老哥的SystemUI遗留代码维护工作,老哥已经去到更高端的应用,飘渺地留下一句:我写的SystemUI就是一座**……就离去了。
望着这个寄生在庞大原生中的小应用,作者流下了痛苦的泪水。活儿还得干,此文记录解决下拉栏横竖滑动的层级问题过程。

问题描述

测试姐姐抛出的问题是:下拉栏的滑动收回区域太小了,应该把空白区域都做成跟手滑动的!
乍一看这个需求很合理,而且不难解决,只需要重写onTouchEvent()改变窗口y值即可,然而这个bug能被挂出来证明没这么简单。
左页
右页
自画了下拉栏的情况,水印为某软件自带。下拉栏分两页,真是天才的想法(真有人拉下拉栏来看时间吗?!)。这个分页设计是如何实现的呢?老哥想了个很自然的方法:ViewPager。只要把页面装入其中,可以很自然地实现两页的切换。
viewpager
看上去是个很精巧的设定,但是viewpager占据了用户大部分手指触摸的区域,在上面的滑动事件都被其拦截。

解决办法

找到比onTouchEvent更高级的触摸分发事件

由于onTouchEvent是从子view开始分发的,因此使用onIntercerptTouchEvent处理触摸事件。
这样会导致几个问题:

  • 子view不响应,由于在Intercerpt阶段把触摸事件拦截完了,子view便罢工了。
  • 滑动事件判定问题,用户希望横滑和竖划都有用,前者换页,后者收起下拉栏,那么如果斜着划会怎么样呢……

正确拦截划动事件

我们可以确定的是action_down阶段是不能返回true拦截事件的,不然后面子view们都没戏唱了,因此应该研究action_move阶段。
首先区分横滑竖划,经过我对其他手机应用的研究,大部分的横竖划响应不同的页面只会响应一种事件,在视觉上有反馈之后就会确定这种事件,即使先横后长久地竖划,也不会产生两种事件在一次划动中反馈的情形。
因此在action_down阶段记录坐标,在move时确定是横滑还是竖划,竖划则返回true,此后不再分发给子view。
以上设计解决了竖划区域太小的问题,不过这并不是最完善的方法,自测还会出现以下bug:

  • 快速设置栏还是没反应,如果区分横滑竖划是下拉栏整体与viewpager的博弈,那么viewpager的按钮们又该怎么在夹缝中求生?
  • 同样属于竖划的进度条们,都是竖划,如果在onIntercerpt阶段就不给子
  • view,那么相当于孙子的音量和亮度进度条又如何得到处理机会?

为设置项开后门

在投屏中用鼠标点点可以做到点一下就是一个down,不过实机上手会发现move事件像流水一样,不能让等于0的差别来确定横竖划。如此可以确保点击事件不被拖动事件影响。
滑动进度条则使用一个特殊的值isSetting确定,当首次触摸的区域为进度条区域时,使用接口回调来改变isSetting,如此,在后续的触摸中,下拉栏默认不拦截事件。

结果

结果当然是欢欢喜喜地解决了。自测和内测均能稳定下来。

后记

古老的代码有多难改造呢,就是时时刻刻都想着重构……当然据说这个想法只存在于新手脑海中,以后就是“谁能跑都行”。开后门的时候作者还发现古老的手绘进度条是按照一些double转int的精度来绘制的,判断划动的代码也很迷。改来改去都改不动,忍无可忍地还是重构了……本次解决问题的最大困难就是viewPager关系太暧昧,后续还产生了一些进度条定位的连带问题,这东西太陷阱了,能不用就不用吧。
如果有不同的意见或者更好的建议,欢迎留言讨论。

附录

完整(伪)代码,此处返回true则可以触发onTouchEvent的划动响应,否则,分发给子view

boolean getDirection = false;
    boolean isUp = true, isSetting = false;
    float oriX, oriY;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int act = ev.getAction();
        float x = ev.getX();
        float y = ev.getY();
        switch (act) {
            case MotionEvent.ACTION_DOWN:
                oriX = x;
                oriY = y;
                isUp = false;
                getDirection = false;
                isSetting = false;
                break;
            case MotionEvent.ACTION_MOVE:
                float moveX = Math.abs(x - oriX);
                float moveY = Math.abs(y - oriY);
                //判断滑动方向
                if (!getDirection) {
                    getDirection = true;
                    if (moveX > moveY) {
                        isUp = false;
                    } else if (moveX < moveY) {
                        isUp = true;

                    } else {
                        getDirection = false;
                    }
                    Log.d(TAG, "isUp = " + isUp + "   " + moveX + "<->" + moveY);

                }
                if (!isSetting && isUp && getDirection)
                {
                    return true; // 拦截子view事件
                } else {
                    if (isSetting) {
                        Log.d(TAG, "is setting……");
                    }
                    mOnTouchDownY = ev.getRawY(); // 防止测出子view区域后界面突然跳动
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值