仿qq横向滑动删除的 SwipeMenuListView

仿qq横向滑动删除的 SwipeMenuListView

@(安卓开发)[android开发|自定义view]

SwipeMenuListView是利用Scroller实现的横滑item出现菜单项的listview, 源码已分享在Github,注释清晰,欢迎查看指正。


    • 仿qq横向滑动删除的 SwipeMenuListView
      • 一Demo展示
      • 二使用方式
        • 1为SwipeMenuListView 新建Adapter
        • 2为SwipeMenuListView 设置adapter
      • 三具体实现
        • 1SwipeMenuListView
        • 3SwipeMenuItemChangeListener
      • 四总结

一、Demo展示

先上效果图

二、使用方式

1、为SwipeMenuListView 新建Adapter

为ListView 新建Adapter的方式和普通的listview没有太大的不同,只是在Adapter的getView里要用item的主体View和滑动出来的菜单View来构造SwipeMenuItemView,然后将构造的SwipeMenuItemView返回给adapter。

//传入上下文context、ListView的宽度、主体内容View 和 菜单View
convertView = new SwipeMenuItemView(context, mListView.getWidth(), contentView, menuView);

adapter getView() 部分的代码如下:

   @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        Holder holder;
        if (null == convertView) {
            //获取主体内容View 和 菜单View
            View contentView = LayoutInflater.from(activity).inflate(R.layout.item_content, null);
            View menuView = LayoutInflater.from(activity).inflate(R.layout.item_menu, null);
            //构造可以横滑显示菜单项的SwipeMenuItemView
            convertView = new SwipeMenuItemView(context, mListView.getWidth(), contentView, menuView);
            holder.contentView = content;
            holder.menuView = menuView;
            convertView.setTag(holder);
        } else {
            holder = (Holder) convertView.getTag();
        }
        //横滑出的菜单项设置点击事件
        holder.menuView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //item_menu is clicked
            }
        });
        return convertView;
    }

2、为SwipeMenuListView 设置adapter

private void initData() {
        mAdapter = new CommentListAdapter(this, mListView, commentList);
        mSwipeMenuListView.setAdapter(mAdapter);
        mAdapter.notifyDataSetChanged();
    }

三、具体实现

从实用方式中相信大家也可以看出来,这套横滑显示菜单的控件核心就在SwipeMenuListViewSwipeMenuItemView,下面就详细说一下这两个部分的具体实现,其中主要涉及到事件的分发Scroller相关的知识。

1、SwipeMenuListView

SwipeMenuListView继承ListView,主要负责事件的分发,将横滑事件分发到对应的SwipeMenuItemView中,具体实现代码如下:

  @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:

                //通过点击时的坐标 获得对应点击Item的position
                int position = pointToPosition((int) ev.getX(), (int) ev.getY());

                //通过getFirstVisiblePosition()获取到屏幕显示的第一个item的position
                // 通过 getChildAt( position - getFirstVisiblePosition() )可以得到点击的item的view实例
                if (position != INVALID_POSITION) {
                    mSwipeMenuItemView = (SwipeMenuItemView) getChildAt(position - getFirstVisiblePosition());
                }
                break;
        }

        if (mSwipeMenuItemView != null) {
            //将当前的touchEvent传递给itemView·
            mSwipeMenuItemView.onTouchEvent(ev);

            //将其它 item 的 menuView 置为隐藏
            mSwipeMenuItemView.setSwipeMenuItemChangeListener(new SwipeMenuItemChangeListener() {
                @Override
                public void onSwipeMenuItemChange(boolean isMenuShow) {
                    if (isMenuShow) {
                        for (int i = getFirstVisiblePosition(); i <= getLastVisiblePosition(); i++) {
                            if (getChildAt(i)!=mSwipeMenuItemView)
                                ((SwipeMenuItemView) getChildAt(i)).setIsMenuShow(false);
                        }
                    }
                }
            });
        }
        return super.onTouchEvent(ev);
    }

2、SwipeMenuItemView
SwipeMenuItemView本质上就是一个包含了两个子View的横向的 Linearlayout,如下图 View1的宽度和SwipeMenuListView的宽度一样View2 可以自定义一个或者多个Button。
仿qq横向滑动删除的 SwipeMenuListView_第1张图片

在SwipeMenuItemView里,主要在onTouchEvent里根据用户的横向滑动操作实时滚动itemView,并利用scroller给itemView增加自动滑动的效果。核心代码如下:

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //记录手指按下时的坐标
                mDownPosX = (int) event.getRawX();
                mDownPosY = (int) event.getRawY();
                mOldPosX = mDownPosX;
                mOldPosY = mDownPosY;
                break;
            case MotionEvent.ACTION_MOVE:
                //计算当前滑动距离
                int dX = (int) event.getRawX() - mOldPosX;
                int dY = (int) event.getRawY() - mOldPosY;
                //计算自按下时的总滑动距离
                moveDistance = (int) (event.getRawX() - mDownPosX);
                //如果右侧菜单栏隐藏就不往左滑,如果右侧菜单栏显示就不往右再滑了
                if (menuStatu == MENU_HIDE && dX > 0 || menuStatu == MENU_SHOW && dX < 0 || moveDistance == 0)
                    break;

                if (moveDistance >= mWidth) {
                    //总移动距离为正(向右) 且大于等于菜单的宽度就表示菜单栏已被隐藏
                    menuStatu = MENU_HIDE;
                } else if (moveDistance < -mMenuWidth) {
                    //总移动距离为负(向左) 且大于等于菜单的宽度就表示菜单栏已被显示
                    menuStatu = MENU_SHOW;
                }else
                    menuStatu = MENU_CHANGING;
                //如果当次滑动横向距离大于纵向距离 就将view整体横向移动对应距离(为什么scrollBy(-dx)请查看scrollBy文档)    
                if (Math.abs(dX) > Math.abs(dY))
                    scrollBy(-dX, 0);
                //记录当前位置,用于下一次滑动事件计算滑动距离
                mOldPosX = (int) event.getRawX();
                mOldPosY = (int) event.getRawY();
                break;
            case MotionEvent.ACTION_UP:
                //计算总的滑动距离
                moveDistance = (int) (event.getRawX() - mDownPosX);
                //如果右侧菜单栏隐藏就不往左滑,如果右侧菜单栏显示就不往右再滑了
                if (menuStatu == MENU_HIDE && moveDistance > 0 || menuStatu == MENU_SHOW && moveDistance < 0)
                    break;
                //如果滑动距离不足菜单宽度的0.3倍,此次滑动视为无效,itemView回到原位
                if (Math.abs(moveDistance) < 0.3 * mMenuWidth) {
                    setIsMenuShow(false);
                } else {
                    //如果滑动方向向左(距离为负)则显示右侧菜单栏,否则隐藏菜单栏
                    setIsMenuShow(moveDistance < 0);
                }
                break;
        }

        return super.onTouchEvent(event);
    }

在用户滑动item未完全显示或隐藏菜单栏时,需要我们利用Scroller来给item一个滑动效果,具体实现如下:

    public SwipeMenuItemView setIsMenuShow(boolean isMenuShow) {

        int[] position = new int[2];
        contentView.getLocationOnScreen(position);

        if (isMenuShow) {
            if (menuStatu == MENU_SHOW)
                return this;
            lis.onSwipeMenuItemChange(true);
            menuStatu = MENU_SHOW;
            smoothScrollTo(-position[0], mScroller.getFinalY(), mMenuWidth, mScroller.getFinalY(), AUTO_SCROLL_TIEM);
        } else {
            if (menuStatu == MENU_HIDE)
                return this;
            lis.onSwipeMenuItemChange(false);
            menuStatu = MENU_HIDE;
            smoothScrollTo(-position[0], mScroller.getFinalY(), 0, mScroller.getFinalY(), AUTO_SCROLL_TIEM);
        }
        invalidate();
        return this;
    }

    private void smoothScrollTo(int startX, int startY, int endX, int endY, int duration) {
        mScroller.startScroll(startX, startY, 0, 0, duration);
        mScroller.setFinalX(endX);
        mScroller.setFinalY(endY);
        invalidate();
    }

    @Override
    public void computeScroll() {

        //先判断mScroller滚动是否完成
        if (mScroller.computeScrollOffset()) {

            //这里调用View的scrollTo()完成实际的滚动
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());

            //必须调用该方法,否则不一定能看到滚动效果
            postInvalidate();
        }
        super.computeScroll();
    }

3、SwipeMenuItemChangeListener

上述实现中,用于监听item滑动事件然后将其他item的menu隐藏的SwipeMenuItemChangeListener如下:

interface SwipeMenuItemChangeListener {
    void onSwipeMenuItemChange(boolean isMenuShow);
}

四、总结

整个自定义View主要涉及到了View和ViewGroup的事件分发机制、Scroller相关知识,这两块我没有仔细总结,后期补上。

你可能感兴趣的:(android,android,listview)