Android自定义View-SlideListView

作者: 晓峰残月 | 来源:发表于2016-04-12 10:31 被阅读710次

    在 android 中 ListView 可以说是使用最多的控件之一,ListView 有很多的用法和处理事件,比如 item 的点击和长按事件,在比较多的应用中,点击就是跳转,长按会弹出一些选择菜单等。
    这里我要介绍的是一个 ListView 侧滑出菜单的自定义控件

    效果图如下:<br />
    正常状态

    image01.png

    侧滑出菜单状态

    image02.png

    分析

    主要用到了 Scroller 这个滑动类,刚开始拦截触摸事件在 action ==MotionEvent.ACTION_DOWN的时候,根据出点获取我们点击的itemView 然后根据滑动模式(左滑动 or 右滑动)来自动获取左侧或者右侧的宽度;

    在 action == MotionEvent.ACTION_MOVE 中根据移动判断是否可以侧滑,以及侧滑的方向,并使用 itemView.scrollTo(deltaX, 0); 来移动itemView ;

    最后在 ction == MotionEvent.ACTION_UP 中判断模式和移动的距离完成侧滑或者还原到初始状态。

    实现

    1. 第一步 初始化Scroller
    scroller = new Scroller(context);
    mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    
    1. 第二步 action ==MotionEvent.ACTION_DOWN
                case MotionEvent.ACTION_DOWN:
                    if (this.mode == MOD_FORBID) {
                        return super.onTouchEvent(ev);
                    }
                    // 如果处于侧滑完成状态,侧滑回去,并直接返回
                    if (isSlided) {
                        scrollBack();
                        return false;
                    }
                    // 假如scroller滚动还没有结束,我们直接返回
                    if (!scroller.isFinished()) {
                        return false;
                    }
    
                    downX = (int) ev.getX();
                    downY = (int) ev.getY();
    
                    slidePosition = pointToPosition(downX, downY);
                    // 无效的position, 不做任何处理
                    if (slidePosition == AdapterView.INVALID_POSITION) {
                        return super.onTouchEvent(ev);
                    }
    
                    // 获取我们点击的item view
                    itemView = getChildAt(slidePosition - getFirstVisiblePosition());
                    /*此处根据设置的滑动模式,自动获取左侧或右侧菜单的长度*/
                    if (this.mode == MOD_BOTH) {
                        this.leftLength = -itemView.getPaddingLeft();
                        this.rightLength = -itemView.getPaddingRight();
                    } else if (this.mode == MOD_LEFT) {
                        this.leftLength = -itemView.getPaddingLeft();
                    } else if (this.mode == MOD_RIGHT) {
                        this.rightLength = -itemView.getPaddingRight();
                    }
                    break;
    
    1. 第三步 action == MotionEvent.ACTION_MOVE
                case MotionEvent.ACTION_MOVE:
                    if (!canMove
                            && slidePosition != AdapterView.INVALID_POSITION
                            && (Math.abs(ev.getX() - downX) > mTouchSlop && Math.abs(ev
                            .getY() - downY) < mTouchSlop)) {
                        if (mSwipeLayout != null)
                            mSwipeLayout.setEnabled(false);
                        int offsetX = downX - lastX;
                        if (offsetX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)) {
                            /*从右向左滑*/
                            canMove = true;
                        } else if (offsetX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)) {
                            /*从左向右滑*/
                            canMove = true;
                        } else {
                            canMove = false;
                        }
                        /*此段代码是为了避免我们在侧向滑动时同时触发ListView的OnItemClickListener时间*/
                        MotionEvent cancelEvent = MotionEvent.obtain(ev);
                        cancelEvent
                                .setAction(MotionEvent.ACTION_CANCEL
                                        | (ev.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
                        onTouchEvent(cancelEvent);
                    }
                    if (canMove) {
                        /*设置此属性,可以在侧向滑动时,保持ListView不会上下滚动*/
                        requestDisallowInterceptTouchEvent(true);
                        // 手指拖动itemView滚动, deltaX大于0向左滚动,小于0向右滚
                        int deltaX = downX - lastX;
                        if (deltaX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)) {
                            /*向左滑*/
                            itemView.scrollTo(deltaX, 0);
                        } else if (deltaX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)) {
                            /*向右滑*/
                            itemView.scrollTo(deltaX, 0);
                        } else {
                            itemView.scrollTo(0, 0);
                        }
                        return true;
                    }
                    break;
    
    
    1. 第四步 action == MotionEvent.ACTION_UP
                case MotionEvent.ACTION_UP:
                    if (mSwipeLayout != null)
                        mSwipeLayout.setEnabled(true);
                    //requestDisallowInterceptTouchEvent(false);
                    if (canMove){
                        canMove = false;
                        scrollByDistanceX();
                    }
                    break;
    

    完整代码

    一下是完整代码

    package com.jwenfeng.fastdev.view;
    
    import android.content.Context;
    import android.support.v4.widget.SwipeRefreshLayout;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.ViewConfiguration;
    import android.widget.AdapterView;
    import android.widget.ListView;
    import android.widget.Scroller;
    
    /**
     * 当前类注释: ListView 侧滑出菜单
     * 项目名:fastdev
     * 包名:com.jwenfeng.fastdev.view
     * 作者:jinwenfeng on 16/4/11 10:55
     * 邮箱:823546371@qq.com
     * QQ: 823546371
     * 公司:南京穆尊信息科技有限公司
     * © 2016 jinwenfeng
     * ©版权所有,未经允许不得传播
     */
    public class SlideListView extends ListView {
    
        /**下拉刷新view*/
        private SwipeRefreshLayout mSwipeLayout;
        /**
         * 禁止侧滑模式
         */
        public static int MOD_FORBID = 0;
        /**
         * 从左向右滑出菜单模式
         */
        public static int MOD_LEFT = 1;
        /**
         * 从右向左滑出菜单模式
         */
        public static int MOD_RIGHT = 2;
        /**
         * 左右均可以滑出菜单模式
         */
        public static int MOD_BOTH = 3;
        /**
         * 当前的模式
         */
        private int mode = MOD_FORBID;
        /**
         * 左侧菜单的长度
         */
        private int leftLength = 0;
        /**
         * 右侧菜单的长度
         */
        private int rightLength = 0;
    
        /**
         * 当前滑动的ListView position
         */
        private int slidePosition;
        /**
         * 手指按下X的坐标
         */
        private int downY;
        /**
         * 手指按下Y的坐标
         */
        private int downX;
        /**
         * ListView的item
         */
        private View itemView;
        /**
         * 滑动类
         */
        private Scroller scroller;
        /**
         * 认为是用户滑动的最小距离
         */
        private int mTouchSlop;
    
        /**
         * 判断是否可以侧向滑动
         */
        private boolean canMove = false;
        /**
         * 标示是否完成侧滑
         */
        private boolean isSlided = false;
    
        public SlideListView(Context context) {
            this(context, null);
        }
    
        public SlideListView(Context context, AttributeSet attrs) {
            this(context, attrs,0);
        }
    
        public SlideListView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            scroller = new Scroller(context);
            mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
        }
    
        /**
         * 初始化菜单的滑出模式
         *
         * @param mode
         */
        public void initSlideMode(int mode) {
            this.mode = mode;
        }
    
        /**
         * 处理我们拖动ListView item的逻辑
         */
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            final int action = ev.getAction();
            int lastX = (int) ev.getX();
    
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    if (this.mode == MOD_FORBID) {
                        return super.onTouchEvent(ev);
                    }
                    // 如果处于侧滑完成状态,侧滑回去,并直接返回
                    if (isSlided) {
                        scrollBack();
                        return false;
                    }
                    // 假如scroller滚动还没有结束,我们直接返回
                    if (!scroller.isFinished()) {
                        return false;
                    }
    
                    downX = (int) ev.getX();
                    downY = (int) ev.getY();
    
                    slidePosition = pointToPosition(downX, downY);
                    // 无效的position, 不做任何处理
                    if (slidePosition == AdapterView.INVALID_POSITION) {
                        return super.onTouchEvent(ev);
                    }
    
                    // 获取我们点击的item view
                    itemView = getChildAt(slidePosition - getFirstVisiblePosition());
                    /*此处根据设置的滑动模式,自动获取左侧或右侧菜单的长度*/
                    if (this.mode == MOD_BOTH) {
                        this.leftLength = -itemView.getPaddingLeft();
                        this.rightLength = -itemView.getPaddingRight();
                    } else if (this.mode == MOD_LEFT) {
                        this.leftLength = -itemView.getPaddingLeft();
                    } else if (this.mode == MOD_RIGHT) {
                        this.rightLength = -itemView.getPaddingRight();
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (!canMove
                            && slidePosition != AdapterView.INVALID_POSITION
                            && (Math.abs(ev.getX() - downX) > mTouchSlop && Math.abs(ev
                            .getY() - downY) < mTouchSlop)) {
                        if (mSwipeLayout != null)
                            mSwipeLayout.setEnabled(false);
                        int offsetX = downX - lastX;
                        if (offsetX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)) {
                            /*从右向左滑*/
                            canMove = true;
                        } else if (offsetX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)) {
                            /*从左向右滑*/
                            canMove = true;
                        } else {
                            canMove = false;
                        }
                        /*此段代码是为了避免我们在侧向滑动时同时触发ListView的OnItemClickListener时间*/
                        MotionEvent cancelEvent = MotionEvent.obtain(ev);
                        cancelEvent
                                .setAction(MotionEvent.ACTION_CANCEL
                                        | (ev.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
                        onTouchEvent(cancelEvent);
                    }
                    if (canMove) {
                        /*设置此属性,可以在侧向滑动时,保持ListView不会上下滚动*/
                        requestDisallowInterceptTouchEvent(true);
                        // 手指拖动itemView滚动, deltaX大于0向左滚动,小于0向右滚
                        int deltaX = downX - lastX;
                        if (deltaX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)) {
                            /*向左滑*/
                            itemView.scrollTo(deltaX, 0);
                        } else if (deltaX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)) {
                            /*向右滑*/
                            itemView.scrollTo(deltaX, 0);
                        } else {
                            itemView.scrollTo(0, 0);
                        }
                        return true;
                    }
                    break;
    
                case MotionEvent.ACTION_UP:
                    if (mSwipeLayout != null)
                        mSwipeLayout.setEnabled(true);
                    //requestDisallowInterceptTouchEvent(false);
                    if (canMove){
                        canMove = false;
                        scrollByDistanceX();
                    }
                    break;
            }
    
            return super.onTouchEvent(ev);
        }
    
        private void scrollByDistanceX() {
            if(this.mode == MOD_FORBID){
                return;
            }
            if(itemView.getScrollX() > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)){
                /*从右向左滑*/
                if (itemView.getScrollX() >= rightLength / 2) {
                    scrollLeft();
                }  else {
                    // 滚回到原始位置
                    scrollBack();
                }
            }else if(itemView.getScrollX() < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)){
                /*从左向右滑*/
                if (itemView.getScrollX() <= -leftLength / 2) {
                    scrollRight();
                } else {
                    // 滚回到原始位置
                    scrollBack();
                }
            }else{
                // 滚回到原始位置
                scrollBack();
            }
        }
    
        /**
         * 往右滑动,getScrollX()返回的是左边缘的距离,就是以View左边缘为原点到开始滑动的距离,所以向右边滑动为负值
         */
        private void scrollRight() {
            isSlided = true;
            final int delta = (leftLength + itemView.getScrollX());
            // 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
            scroller.startScroll(itemView.getScrollX(), 0, -delta, 0,
                    Math.abs(delta));
            postInvalidate(); // 刷新itemView
        }
    
        /**
         * 向左滑动,根据上面我们知道向左滑动为正值
         */
        private void scrollLeft() {
            isSlided = true;
            final int delta = (rightLength - itemView.getScrollX());
            // 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
            scroller.startScroll(itemView.getScrollX(), 0, delta, 0,
                    Math.abs(delta));
            postInvalidate(); // 刷新itemView
    
        }
    
        private void scrollBack() {
            isSlided = false;
            scroller.startScroll(itemView.getScrollX(), 0, -itemView.getScrollX(),
                    0, Math.abs(itemView.getScrollX()));
            postInvalidate(); // 刷新itemView
        }
    
        @Override
        public void computeScroll() {
            // 调用startScroll的时候scroller.computeScrollOffset()返回true,
            if (scroller.computeScrollOffset()) {
                // 让ListView item根据当前的滚动偏移量进行滚动
                itemView.scrollTo(scroller.getCurrX(), scroller.getCurrY());
                postInvalidate();
            }
        }
    
        /**
         * 提供给外部调用,用以将侧滑出来的滑回去
         */
        public void slideBack() {
            this.scrollBack();
        }
    
        public void setSwipeLayout(SwipeRefreshLayout mSwipeLayout) {
            this.mSwipeLayout = mSwipeLayout;
        }
    }
    
    

    最后

    本文地址:http://www.jianshu.com/p/8330df032ddb <br />
    尊重原创,转载请注明:From 晓峰残月(http://jwenfeng.com) 侵权必究!

    相关文章

      网友评论

      本文标题:Android自定义View-SlideListView

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