Android仿QQ实现ListView滑动删除

效果图

这里写图片描述

自定义一个可以滑动删除listView的item布局

item_listview.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.xiaoyehai.swipedelete.widget.SwipeLayout
        android:id="@+id/swipelayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <include layout="@layout/layout_content" />

        <include layout="@layout/layout_delete" />

    </com.xiaoyehai.swipedelete.widget.SwipeLayout>

</LinearLayout>

layout_content.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="80dp"
    android:background="@android:color/white"
    android:gravity="center_vertical"
    android:orientation="horizontal"
    android:paddingLeft="15dp">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@mipmap/ic_launcher" />

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:text="名称"
        android:textColor="#99000000"
        android:textSize="20sp" />

</LinearLayout>

layout_delete.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="80dp"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/tv_zhiding"
        android:layout_width="100dp"
        android:layout_height="match_parent"
        android:background="#66ff0000"
        android:gravity="center"
        android:text="置顶"
        android:textSize="18sp" />

    <TextView
        android:id="@+id/tv_delete"
        android:layout_width="100dp"
        android:layout_height="match_parent"
        android:background="#330000ff"
        android:gravity="center"
        android:text="删除"
        android:textSize="18sp" />

</LinearLayout>

自定义控件:可删除滑动的item

package com.xiaoyehai.swipedelete.widget;

import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

import com.xiaoyehai.swipedelete.SwipeLayoutManager;

/**
 * 自定义一个可以滑动删除listView的item布局
 * Created by xiaoyehai on 2018/8/2 0002.
 */

public class SwipeLayout extends FrameLayout {

    private View contentView; //item内容区域的view
    private View deleteView; //delete区域的view
    private int contentHeight; ///item内容区域的高度
    private int contentWidth; //item内容区域的宽
    private int deleteHeight; //delete区域的高度
    private int deleteWidth;  //delete区域的宽度

    private ViewDragHelper mViewDragHelper; //可以对viewgroup中的子view进行拖拽

    private float downX, downY;

    enum SwipeState {
        Open, Close;
    }

    private SwipeState currentState = SwipeState.Close; //当前默认是关闭状态

    public SwipeLayout(Context context) {
        this(context, null);
    }

    public SwipeLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SwipeLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        mViewDragHelper = ViewDragHelper.create(this, callback);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        contentView = getChildAt(0);
        deleteView = getChildAt(1);
    }

    /**
     * 这个方法会在onMeasure执行完后执行,可以在该方法中获取给控件自己和子控件的宽高
     *
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        contentHeight = contentView.getMeasuredHeight();
        contentWidth = contentView.getMeasuredWidth();
        deleteHeight = deleteView.getMeasuredHeight();
        deleteWidth = deleteView.getMeasuredWidth();
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        //摆放contentView
        contentView.layout(0, 0, contentWidth, contentHeight);
        //摆放deleteView
        deleteView.layout(contentView.getRight(), 0, contentView.getRight() + deleteWidth, deleteHeight);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // 让ViewDragHelper帮我们判断是否应该拦截
        boolean result = mViewDragHelper.shouldInterceptTouchEvent(ev);

        //如果当前有打开的,则需要直接拦截,交给onTouch处理
        if (!SwipeLayoutManager.getInstance().isShouldSwipe(this)) {
            //先关闭已经打开的layout
            SwipeLayoutManager.getInstance().closeCurrentLayout();
            result = true;
        }
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //如果当前有打开的,就不能再打开新的,要先关闭已经打开的,才能打开新的,则下面的逻辑不能执行
        if (!SwipeLayoutManager.getInstance().isShouldSwipe(this)) {
            requestDisallowInterceptTouchEvent(true);  //listview不能滑动
            return true;
        }

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float moveX = event.getX();
                float moveY = event.getY();

                //获取x和y方向移动的距离
                float dx = moveX - downX;
                float dy = moveY - downY;

                /**
                 *问题1: 水平滚动的时候,让listview不能上下滑动
                 */
                if (Math.abs(dx) > Math.abs(dy)) {
                    //表示移动是偏向于水平方向,那么应该SwipeLayout应该处理,请求listview不要拦截
                    //就是SwipeLayout能滑动,listview不能滑动
                    requestDisallowInterceptTouchEvent(true); //请求父类不要拦截
                }
                //更新downX,downY
                downX = moveX;
                downY = moveY;
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        mViewDragHelper.processTouchEvent(event);
        return true;
    }


    private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
        /**
         * 用于判断是否捕获当前child的触摸事件
         * @param child 当前触摸的子View
         * @param pointerId
         * @return true:捕获并解析  false:不处理
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return child == contentView || child == deleteView;
        }

        /**
         * 获取view水平方向的拖拽范围,但是目前不能限制边界,返回的值目前用在手指抬起的时候
         *  view缓慢移动的动画时间的计算; 最好不要返回0
         * @param child
         * @return
         */
        @Override
        public int getViewHorizontalDragRange(View child) {
            return deleteWidth;
        }


        /**
         * 控制child在水平方向的移动
         * @param child 当前触摸的子View
         * @param left 当前child的即将移动到的位置,left=chile.getLeft()+dx
         * @param dx 本次child水平方向移动的距离
         * @return 表示你真正想让child的left变成的值
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            //限定边界
            if (child == contentView) {
                if (left > 0) {
                    left = 0;
                }
                //当deleteView完全滑出来时,contentView不能再滑动
                if (left < -deleteWidth) {
                    left = -deleteWidth;
                }
            } else if (child == deleteView) {
                if (left > contentWidth) {
                    left = contentWidth;
                }
                if (left < contentWidth - deleteWidth) {
                    left = contentWidth - deleteWidth;
                }
            }
            return left;
        }

        /**
         * 当child的位置改变的时候执行,一般用来做其他子View跟随该view移动
         * @param changedView 当前位置改变的child
         * @param left child当前最新的left
         * @param top child当前最新的top
         * @param dx 本次水平移动的距离
         * @param dy 本次垂直移动的距离
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);
            if (changedView == contentView) {
                //contentView移动的时候,deleteview伴随移动
                deleteView.layout(deleteView.getLeft() + dx, deleteView.getTop() + dy,
                        deleteView.getRight() + dx, deleteView.getBottom() + dy);
            } else if (changedView == deleteView) {
                //deleteview移动的时候,contentview伴随移动
                contentView.layout(contentView.getLeft() + dx, contentView.getTop() + dy,
                        contentView.getRight() + dx, contentView.getBottom() + dy);
            }

            /**
             * 问题2:不能同时打开多个条目,只能打开一个条目
             */
            //判断开和关的状态
            if (contentView.getLeft() == 0 && currentState != SwipeState.Close) {
                currentState = SwipeState.Close; //更改为关闭状态

                //回调接口关闭的方法
                if (listener != null) {
                    listener.onClose(getTag());
                }
                //说明当前的SwipeLayout已经关闭,需要让Manager清空一下
                SwipeLayoutManager.getInstance().clearCurrentLayout();

            } else if (contentView.getLeft() == -deleteWidth && currentState != SwipeState.Open) {
                currentState = SwipeState.Open;

                //回调接口打开的方法
                if (listener != null) {
                    listener.onOpen(getTag());
                }
                //当前的Swipelayout已经打开,需要让Manager记录一下下
                SwipeLayoutManager.getInstance().setSwipeLayout(SwipeLayout.this);
            }
        }

        /**
         * 手指抬起的执行该方法
         * @param releasedChild 当前抬起的view
         * @param xvel x方向的移动速度有 正:向右移动
         * @param yvel 方向的移动速度
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            if (contentView.getLeft() < -deleteWidth / 2) {
                //滑动超过一半,打开
                open();
            } else {
                //滑动小于一半,关闭
                close();
            }
        }
    };

    /**
     * 打开的方法
     */
    public void open() {
        mViewDragHelper.smoothSlideViewTo(contentView, -deleteWidth, contentView.getTop());
        ViewCompat.postInvalidateOnAnimation(SwipeLayout.this); //刷新
    }

    /**
     * 关闭的方法
     */
    public void close() {
        mViewDragHelper.smoothSlideViewTo(contentView, 0, contentView.getTop());
        ViewCompat.postInvalidateOnAnimation(SwipeLayout.this);
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        //如果动画还没结束
        if (mViewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    private OnSwipeStateChangeListener listener;

    public void setOnSwipeStateChangeListener(OnSwipeStateChangeListener listener) {
        this.listener = listener;
    }

    //把打开或关闭的状态暴露给外界
    public interface OnSwipeStateChangeListener {

        void onOpen(Object tag);

        void onClose(Object tag);
    }
}

对SwipeLayout的管理类

package com.xiaoyehai.swipedelete;


import com.xiaoyehai.swipedelete.widget.SwipeLayout;

/**
 * 对SwipeLayout的管理类
 * Created by xiaoyehai on 2016/12/1.
 */
public class SwipeLayoutManager {

    private SwipeLayoutManager() {
    }

    private static SwipeLayoutManager mInstance = new SwipeLayoutManager();

    public static SwipeLayoutManager getInstance() {
        return mInstance;
    }

    private SwipeLayout currentLayout; //用来记录当前打开的SwipeLayout

    public void setSwipeLayout(SwipeLayout layout) {
        this.currentLayout = layout;
    }

    /**
     * 清空当前所记录的已经打开的layout
     */
    public void clearCurrentLayout() {
        currentLayout = null;
    }

    /**
     * 关闭当前已经打开的SwipeLayout
     */
    public void closeCurrentLayout() {
        if (currentLayout != null) {
            currentLayout.close();
        }
    }

    /**
     * 判断当前是否应该能够滑动,如果没有打开的,则可以滑动。
     * 如果有打开的,则判断打开的layout和当前按下的layout是否是同一个,是同一个,可以滑动
     *
     * @return
     */
    public boolean isShouldSwipe(SwipeLayout swipeLayout) {
        if (currentLayout == null) {
            //说明当前木有打开的layout
            return true;
        } else { //说明有打开的layout
            //判断打开的layout和当前按下的layout是否是同一个
            return currentLayout == swipeLayout;
        }
    }
}

使用:

package com.xiaoyehai.swipedelete;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.AbsListView;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;

/**
 * listView的item布局实现滑动删除
 * 1.应用场景:替换item长按删除
 * 2.实现逻辑:
 * a.自定义一个可以滑动的布局;
 * b.将该布局放入adapter的布局中,需要处理滑动冲突;
 */
public class MainActivity extends AppCompatActivity {

    private ListView mListView;

    private List<String> list;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mListView = (ListView) findViewById(R.id.listview);
        initListView();
    }

    private void initListView() {
        list = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            list.add("条目 " + i);
        }
        mListView.setAdapter(new MyAdapter(this, list));

        mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
                    //如果垂直滑动,则需要关闭已经打开的layout
                    SwipeLayoutManager.getInstance().closeCurrentLayout();
                }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

            }
        });
    }
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white">

    <ListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:divider="@android:color/darker_gray"
        android:dividerHeight="1dp"></ListView>


</RelativeLayout>

代码地址

猜你喜欢

转载自blog.csdn.net/qq_36699930/article/details/81389118