模仿ios微信滑动选项View

这篇博客介绍了如何模仿iOS微信的滑动选项View,通过集成FrameLayout并添加两个子View来实现。作者详细讲解了测量、布局、滑动处理的实现过程,包括在dispatchTouchEvent(), onInterceptTouchEvent(), 和onTouchEvent()中的逻辑。文章提供了核心代码,并指出已在其个人项目中使用,欢迎读者交流讨论。" 121131420,11465969,Java Stream API与数据库MySQL详解,"['Java', '数据库', 'MySQL', 'SQL', '开发语言']
摘要由CSDN通过智能技术生成

滑动选项View

额…其实这个我也不知道应该叫做什么,就是滑动出来的选项,就像ios版微信那样,向左滑动,滑出选项,看着就想试试模仿一下,下面来看看如何写。

首先制定思路,滑动出来的选项是一部分,作为主体内容的是一部分,也就是说这是有两部分组成,那么集成FrameLayout,初始化的时候添加两个子view一个是主题内容的viewGroup,一个是装载选项的viewGroup,我在这里定义为三个选项,放在选项viewGroup中(下文为BtnLayout),一个是确定键,一个是取消键,一个是更多键,当然文字是可以修改的,而且选项是否显示也能控制,那么就完成第一步了,下一步就是测量,其实我这里的测量的方法不是很好,主要是在我没有重写各种addView(),这样就没有限制子view的个数,会破坏思路中的两个组成部分,我就在onMeasure()这里限制,因为初始化的时候添加了两个组成部分,所以如果有第三个view,那么这个view就会添加到主题内容viewGroup中(下文为content),然后多余的就移除掉,这样实属不好,这里设置宽度为充满父布局,高度默认为70dp,贴上代码:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY
            ? MeasureSpec.getSize(heightMeasureSpec) : SizeUtils.dp2px(context, 70);
    btnLayout.setLayoutParams(new LayoutParams(-scroll, height));
    for (int i = 2; i < getChildCount(); i++) {
        if (i == 2) {
            View view = getChildAt(i);
            removeView(view);
            LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
            if (layoutParams.width == LayoutParams.MATCH_PARENT) {
                layoutParams.width = width;
            }
            if (layoutParams.height == LayoutParams.MATCH_PARENT) {
                layoutParams.height = height;
            }
            content.addView(view, layoutParams);
        } else {
            removeView(getChildAt(i));
        }
    }
    measureChildren(widthMeasureSpec, heightMeasureSpec);
    setMeasuredDimension(width, height);
}

上面可以看到这句

btnLayout.setLayoutParams(new LayoutParams(-scroll, height));

这句就是BtnLayout的设置大小的,height就是它的父布局的高度,而宽度就是滑动的距离,记录开始点,结束的,这样计算出滑动距离,因为向左滑动是负数,所以scroll加了个负号。

测量完之后就是布局了,这样的流程估计大家都很熟悉了,其实布局也没什么,不测量还简单,直接是将contentLayout和BtnLayout的位置放好就可以了,贴代码:

protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
        for (int j = 0; j < getChildCount(); j++) {
            View view = getChildAt(j);
            if (view == btnLayout) {
                btnLayout.layout(width + scroll, 0, width, height);
            } else if (view == content) {
                content.layout(scroll, 0, width + scroll, height);
            }
        }
    }

他们的位置是根据滑动的距离改变的,而且是横向改变,当scroll为0 的时候,content充满父布局,当向左滑动的时候,content向左滑动,向左scroll为负数,所以相加就好了。

滑动主要用到dispatchTouchEvent(),onInterceptTouchEvent()和onTouchEvent()这三个方法,首先重写onInterceptTouchEvent(),判断什么情况什么时候拦截,当action为down的时候记录点下的x坐标,action为move的时候,scroll=x-start+getScroll(),其中getScroll()是根据是否打开了BtnLayout返回滑动了多少距离,比如没打开的时候,scroll的初始值是0,打开的时候,BtnLayout就是已经展示出来的了,那么就已经是滑动了BtnLayout的宽度,所以scroll的初始值就是btnLayoutd宽度,当action为up的时候就将start和scroll置为默认值,其实在这里的scroll的意义就是判断是否拦截,因为只是想点击的话就没必要拦截了,不然BtnLayout收不到分发的事件,在打开BtnLayout的时候,当scroll滑动距离大于5dp就判定为想要滑动,然后判断滑动的位置是在content还是BtnLayout,在content的话就是直接拦截,我还加了个判断是是否允许在BtnLayout滑动关闭BtnLayout,是的话就拦截,不是的话就不拦截,走super.onInterceptTouchEvent()继续分发,当BtnLayout没有打开的时候,就直接拦截了,因为不会影响到分发给BtnLayout,贴上代码:

public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                start = (int) ev.getX();
                scroll = start;
                break;
            case MotionEvent.ACTION_MOVE:
                scroll = (int) (ev.getX() - start + getScroll());
                break;
            case MotionEvent.ACTION_UP:
                start = 0;
                scroll = getScroll();
                break;
        }
        //判断是否打开BtnLayout
        if (isOpenBtn) {
            //滑动距离超过5dp判定用户意向为滑动
            if (scroll != start && scroll >= getScroll() + SizeUtils.dp2px(context, 5)) {
                //起点在contentView部分是直接滑动,在BtnLayout部分要判断是否可以滑动关闭
                if (start <= getMeasuredWidth() - getBtnWidth()) {
                    return true;
                } else if (isBtnClose) {
                    return true;
                }
            }
        } else if (scroll != start && scroll <= getScroll() - SizeUtils.dp2px(context, 5)) {
            return true;
        }
        return super.onInterceptTouchEvent(ev);
    }

那么写完是否拦截就改重写onTouchEvent(),如何消费,当拦截之后就要消费这个事件,首先也是跟拦截一样,action是down的时候就记录start的坐标,而且加入是在recyclerView之类的item布局中,那么就不让父布局拦截这个事件,使得这个事件自己消费,当action为move的时候,先判断用户是纵向滑动还是横向滑动,如果是纵向滑动就不拦截父布局,让父布局消费,反正继续自己消费,然后根据是否打开BtnLayout来刷新界面,当action为up的时候,判断滑动距离是否超过BtnLayout的宽度的三分之一,是的话且是向左滑动,也就是想打开BtnLayout,向右滑动的话就是想关闭BtnLayout,那么就自身滑动过去,这时候用的是属性动画,贴上代码:

public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                start = (int) ev.getX();
                getParent().requestDisallowInterceptTouchEvent(true);
                return true;
            case MotionEvent.ACTION_MOVE:
                //超过一定距离,判断用户想要上下滑动
                if (isTB(ev.getRawY())) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                    return false;
                } else {
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                scroll = (int) (ev.getX() - start + getScroll());
                if (isOpenBtn) {
                    if (scroll < getScroll()) {
                        scroll = getScroll();
                    } else if (scroll > 0) {
                        scroll = 0;
                    }
                } else {
                    if (scroll > getScroll()) {
                        scroll = getScroll();
                    } else if (scroll < -getBtnWidth()) {
                        scroll = -getBtnWidth();
                    }
                }
                requestLayout();
                return true;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (isOpenBtn) {
                    if (scroll >= -getBtnWidth() / 3 * 2) {
                        isOpenBtn = false;
                        if (btnChangeListener != null) {
                            btnChangeListener.onChange(false);
                        }
                        normalPosition(scroll, getScroll());
                    } else {
                        normalPosition(scroll, -getBtnWidth());
                    }
                } else {
                    if (scroll <= -getBtnWidth() / 3) {
                        isOpenBtn = true;
                        if (btnChangeListener != null) {
                            btnChangeListener.onChange(true);
                        }
                        normalPosition(scroll, getScroll());
                    } else {
                        normalPosition(scroll, 0);
                    }
                }
                return true;
        }
        return true;
    }

动画代码:

private void normalPosition(int start, int end) {
        animator = ValueAnimator.ofInt(start, end);
        animator.setDuration(Math.abs(start - end));
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                ScrollView.this.start = 0;
                scroll = (int) valueAnimator.getAnimatedValue();
                requestLayout();
            }
        });
        animator.start();
    }

基本上就这些是核心,还有的就是是否分发这个方法没重写,在这里我只是做了当动画在执行时就不分发,返回false,让父布局消费

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (animator != null && animator.isRunning()) {
            return false;
        }
        return super.dispatchTouchEvent(ev);
    }

好了,基本就是这样了,我还写了两个方法,一个是打开,一个是关闭,不过没有写成是有动画的打开关闭

public void openBtn() {
        if (!isOpenBtn) {
            if (btnChangeListener != null) {
                btnChangeListener.onChange(true);
            }
        }
        isOpenBtn = true;
        scroll = getScroll();
        requestLayout();
    }

    public void closeBtn() {
        if (isOpenBtn) {
            if (btnChangeListener != null) {
                btnChangeListener.onChange(false);
            }
        }
        isOpenBtn = false;
        scroll = getScroll();
        requestLayout();
    }

以上就是滑动选项view的主要代码,我也在自己的练习项目使用,目前没发现什么问题,如大家发现问题可以指正,如果能帮到有需要帮助的人就更好了。

也可以添加我微信互相讨论哦!

wx:Zhang—JY

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值