android ListView的item侧滑删除

首先看一下效果图,有个直观认识
这里写图片描述
主要功能就是ListView的item可以侧滑,出来一个删除按钮,点击delete就删除该item。

这是一个相对比较综合的例子,来看看动手之前需要准备哪些知识。

  1. 对自定义View要有一定的知识基础,参看View绘制流程
  2. 事件的拦截以及反拦截的相关知识,以便很好的解决事件冲突问题,关于事件机制,可以参看android事件处理机制
  3. 滑动器Scroller的使用,参看Scroller简单用法
  4. 自定义View中的接口回调(View状态变化时执行回调)

下面我们一步一步来实现这个功能。

1.自定义ListView中item的布局类

item的布局文件item_slide.xml,代码如下:


<com.chm.myapplication.view.SlideLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:text="content"
        android:textSize="25sp"
        android:background="#d7d7d7"
        android:gravity="center"/>
    <TextView
        android:id="@+id/menu"
        android:layout_width="wrap_content"
        android:layout_height="60dp"
        android:text="delete"
        android:textSize="25sp"
        android:background="#ff0000"
        android:gravity="center"
        android:padding="8dp"/>
com.chm.myapplication.view.SlideLayout>

其中根元素是SlideLayout.java类,这是我们自定义的一个布局类,继承自FrameLayout,代码如下:

public class SlideLayout extends FrameLayout {

    private View contentView;
    private View menuView;

    private int viewHeight; //高是相同的
    private int contentWidth;
    private int menuWidth;

    //滑动器
    private Scroller scroller;

    public SlideLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        scroller = new Scroller(context);
    }

    /**
     * 布局文件加载完成时被调用
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        contentView = findViewById(R.id.content);
        menuView = findViewById(R.id.menu);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        viewHeight = getMeasuredHeight();

        contentWidth = contentView.getMeasuredWidth();
        menuWidth = menuView.getMeasuredWidth();
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        menuView.layout(contentWidth, 0, contentWidth+menuWidth, viewHeight);
    }

    private float startX;
    private float startY;

    private float downX;
    private float downY;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                startX = event.getX();
                startY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float endX = event.getX();
                float endY = event.getY();

                //计算偏移量
                float distanceX = endX - startX;

                int toScrollX = (int) (getScrollX()-distanceX);
                //屏蔽非法值
                if (toScrollX < 0 )
                {
                    toScrollX = 0;
                }
                if (toScrollX > menuWidth)
                {
                    toScrollX = menuWidth;
                }
                System.out.println("toScroll-->"+toScrollX+"-->"+getScrollX());
                scrollTo(toScrollX,getScrollY());

                startX = event.getX();

                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;
    }
}

完成上面的代码之后,我们可以将这个布局文件(item_slide.xml)放在Activity中显示出来,可以看到这时候我们左滑每个item后可以显示出删除按钮,这个按钮之所以会显示出是因为我们已经在onLayout方法中将这个删除按钮正好放在内容View的右侧了,所以不滑动时是看不到的,只有滑动时才显示出来。

2.手势抬起时item自动回弹

现在滑动是可以了,但是我们希望滑动距离大于删除按钮宽度一半后,手抬起时可以直接显示删除按钮,当滑动距离小于删除按钮的一半时,直接回弹将删除按钮隐藏起来,所以我们修改onTouchEvent方法如下

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                downX = startX = event.getX();
                downY = startY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float endX = event.getX();
                float endY = event.getY();

                //计算偏移量
                float distanceX = endX - startX;

                int toScrollX = (int) (getScrollX()-distanceX);
                //屏蔽非法值
                if (toScrollX < 0 )
                {
                    toScrollX = 0;
                }
                if (toScrollX > menuWidth)
                {
                    toScrollX = menuWidth;
                }
                System.out.println("toScroll-->"+toScrollX+"-->"+getScrollX());
                scrollTo(toScrollX,getScrollY());

                startX = event.getX();
                break;
            case MotionEvent.ACTION_UP:

                if (getScrollX() > menuWidth/2)
                {
                    //打开menu
                    openMenu();
                }else {
                    closeMenu();
                }

                break;
        }
        return true;
    }

    /**
     * 打开menu菜单
     */
    public void openMenu() {
        int dx = menuWidth-getScrollX();
        scroller.startScroll(getScrollX(), getScrollY(),dx, getScrollY());
        invalidate();
    }

    /**
     * 关闭菜单
     */
    public void closeMenu() {
        //0表示menu移动到的目标距离,目标位置-起始位置
        int dx = 0-getScrollX();
        scroller.startScroll(getScrollX(), getScrollY(),dx, getScrollY());
        invalidate();
    }
    
	@Override
    public void computeScroll() {
        super.computeScroll();
        if (scroller.computeScrollOffset())
        {
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            invalidate();
        }
    }

回弹使用了Scroller滑动器,invalidate()方法执行时会调用computeScroll()方法,computeScroll()方法每次执行都回调一小段距离,scroller.computeScrollOffset()判断是否需要继续回弹,这个判断里面的invalidate()执行会导致这个过程循环执行,直到回弹结束。

3.放入ListView中显示

将item_slide.xml放在ListView中显示,这个就比较简单了,直接上代码。

public class SlideActivity extends Activity {
    private ListView listView;
    private ArrayList<MyContent> mDatas;
    private MyAdapter myAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_slide);
        listView = (ListView) findViewById(R.id.main_list);

        mDatas = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            mDatas.add(new MyContent("content"+i));
        }
        myAdapter = new MyAdapter(this, mDatas);
        listView.setAdapter(myAdapter);
    }

    class MyAdapter extends BaseAdapter
    {
        private Context content;
        private ArrayList<MyContent> datas;
        private MyAdapter(Context context, ArrayList<MyContent> datas)
        {
            this.content = context;
            this.datas = datas;
        }
        @Override
        public int getCount() {
            return datas.size();
        }

        @Override
        public Object getItem(int position) {
            return datas.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder=null;
            if (convertView == null)
            {
                convertView = LayoutInflater.from(content).inflate(R.layout.item_slide, null);
                viewHolder = new ViewHolder();
                viewHolder.contentView= (TextView) convertView.findViewById(R.id.content);
                viewHolder.menuView = (TextView) convertView.findViewById(R.id.menu);
                convertView.setTag(viewHolder);
            }else {
                viewHolder = (ViewHolder) convertView.getTag();
            }
            viewHolder.contentView.setText(datas.get(position).getContent());

            return convertView;
        }

    }
    static class ViewHolder
    {
        public TextView contentView;
        public TextView menuView;
    }

}

4.解决item滑动和ListView滑动冲突问题

到这里,我们将SlideLayout放入ListView中显示出来,当我们滑动item时,再同时上下滑动时发现item是不会回弹的,这是什么原因?

这就需要你对事件机制了解清楚了,你再上下滑动的时候,滑动事件已经被ListView消耗了,SlideLayout中的onTouchEvent就得不到执行了,这是需要判断(也有重新ListView的),如果在左右滑动时,SlideLayout就需要向父级ListView请求不要拦截事件,如果是上下滑动,就不需要理会了,按默认的来。

所以我们修改SlideLayout中的onTouchEvent方法如下,Move事件处代码:

case MotionEvent.ACTION_MOVE:
                float endX = event.getX();
                float endY = event.getY();

                //计算偏移量
                float distanceX = endX - startX;

                int toScrollX = (int) (getScrollX()-distanceX);
                //屏蔽非法值
                if (toScrollX < 0 )
                {
                    toScrollX = 0;
                }
                if (toScrollX > menuWidth)
                {
                    toScrollX = menuWidth;
                }
                System.out.println("toScroll-->"+toScrollX+"-->"+getScrollX());
                scrollTo(toScrollX,getScrollY());

                startX = event.getX();

                float dx = Math.abs(event.getX()-downX);
                float dy = Math.abs(event.getY()-downY);
                if (dx > dy && dx > 6)
                {
                    //事件反拦截,使父ListView的事件传递到自身SlideLayout
                    getParent().requestDisallowInterceptTouchEvent(true);
                }

                break;

这样我们在左右滑动item时同时上下滑动是不起作用的。

5.解决item点击事件和item滑动事件的冲突

这是,我们给item添加点击事件后,发现item又不能进行滑动了,原因同样是事件被别人消耗了,这次是被SlideLayout中的TextView消耗了,这时我们同样需要判断,如果是滑动就拦截事件,如果是点击就放行。

重新onInterceptTouchEvent方法

@Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                downX = startX = event.getX();
                downY = startY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:

                float dx = Math.abs(event.getX()-downX);
                float dy = Math.abs(event.getY()-downY);
                if (dx > dy && dx > 6)
                {
                    //拦截事件
                    return true;
                }

                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.onInterceptTouchEvent(event);
    }

6.限制只允许一个item显示删除按钮

通过暴露接口的方式,在Activity中设置监听器,当SlideLayout滑动时,调用相关状态的方法,来控制item删除按钮的显示和隐藏。

public interface OnStateChangeListener
    {
        void onOpen(SlideLayout slideLayout);
        void onMove(SlideLayout slideLayout);
        void onClose(SlideLayout slideLayout);
    }

    public OnStateChangeListener onStateChangeListener;

    public void setOnStateChangeListener(OnStateChangeListener onStateChangeListener) {
        this

Activity中的全部代码:

public class SlideActivity extends Activity {
    private ListView listView;
    private ArrayList<MyContent> mDatas;
    private MyAdapter myAdapter;
    private Set<SlideLayout> sets = new HashSet();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_slide);
        listView = (ListView) findViewById(R.id.main_list);

        mDatas = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            mDatas.add(new MyContent("content"+i));
        }
        myAdapter = new MyAdapter(this, mDatas);
        listView.setAdapter(myAdapter);
    }

    class MyAdapter extends BaseAdapter
    {
        private Context content;
        private ArrayList<MyContent> datas;
        private MyAdapter(Context context, ArrayList<MyContent> datas)
        {
            this.content = context;
            this.datas = datas;
        }
        @Override
        public int getCount() {
            return datas.size();
        }

        @Override
        public Object getItem(int position) {
            return datas.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder=null;
            if (convertView == null)
            {
                convertView = LayoutInflater.from(content).inflate(R.layout.item_slide, null);
                viewHolder = new ViewHolder();
                viewHolder.contentView= (TextView) convertView.findViewById(R.id.content);
                viewHolder.menuView = (TextView) convertView.findViewById(R.id.menu);
                convertView.setTag(viewHolder);
            }else {
                viewHolder = (ViewHolder) convertView.getTag();
            }
            viewHolder.contentView.setText(datas.get(position).getContent());

            viewHolder.contentView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(content, "click "+((TextView)v).getText(), Toast.LENGTH_SHORT).show();
                }
            });
            final MyContent myContent = datas.get(position);
            viewHolder.menuView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                	SlideLayout slideLayout = (SlideLayout) v.getParent();
                    slideLayout.closeMenu(); //解决删除item后下一个item变成open状态问题
                    datas.remove(myContent);
                    notifyDataSetChanged();
                }
            });

            SlideLayout slideLayout = (SlideLayout) convertView;
            slideLayout.setOnStateChangeListener(new MyOnStateChangeListener());


            return convertView;
        }

        public SlideLayout slideLayout = null;
        class MyOnStateChangeListener implements SlideLayout.OnStateChangeListener
        {
			/**
			 * 滑动后每次手势抬起保证只有一个item是open状态,加入sets集合中
			 **/
            @Override
	        public void onOpen(SlideLayout layout) {
	            slideLayout = layout;
	            if (sets.size() > 0) {
	                for (SlideLayout s : sets) {
	                    s.closeMenu();
	                    sets.remove(s);
	                }
	            }
	            sets.add(layout);
	        }

            @Override
            public void onMove(SlideLayout layout) {
                if (slideLayout != null && slideLayout !=layout)
                {
                    slideLayout.closeMenu();
                }
            }

            @Override
	        public void onClose(SlideLayout layout) {
	            if (sets.size() > 0) {
	                sets.remove(layout);
	            }
	            if(slideLayout ==layout){
	                slideLayout = null;
	            }
	        }
        }
    }
    static class ViewHolder
    {
        public TextView contentView;
        public TextView menuView;
    }

}

MyContent.java

public class MyContent {
    private String content;

    public MyContent(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

SlideLayout全部代码:

public class SlideLayout extends FrameLayout {

    private View contentView;
    private View menuView;

    private int viewHeight; //高是相同的
    private int contentWidth;
    private int menuWidth;

    //滑动器
    private Scroller scroller;

    public SlideLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        scroller = new Scroller(context);
    }

    /**
     * 布局文件加载完成时被调用
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        contentView = findViewById(R.id.content);
        menuView = findViewById(R.id.menu);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        viewHeight = getMeasuredHeight();

        contentWidth = contentView.getMeasuredWidth();
        menuWidth = menuView.getMeasuredWidth();
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        menuView.layout(contentWidth, 0, contentWidth+menuWidth, viewHeight);
    }


    private float startX;
    private float startY;

    private float downX;
    private float downY;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                downX = startX = event.getX();
                downY = startY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float endX = event.getX();
                float endY = event.getY();

                //计算偏移量
                float distanceX = endX - startX;

                int toScrollX = (int) (getScrollX()-distanceX);
                //屏蔽非法值
                if (toScrollX < 0 )
                {
                    toScrollX = 0;
                }
                if (toScrollX > menuWidth)
                {
                    toScrollX = menuWidth;
                }
                System.out.println("toScroll-->"+toScrollX+"-->"+getScrollX());
                scrollTo(toScrollX,getScrollY());

                startX = event.getX();

                float dx = Math.abs(event.getX()-downX);
                float dy = Math.abs(event.getY()-downY);
                if (dx > dy && dx > 6)
                {
                    //事件反拦截,使父ListView的事件传递到自身SlideLayout
                    getParent().requestDisallowInterceptTouchEvent(true);
                }

                break;
            case MotionEvent.ACTION_UP:

                if (getScrollX() > menuWidth/2)
                {
                    //打开menu
                    openMenu();
                }else {
                    closeMenu();
                }

                break;
        }
        return true;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                downX = startX = event.getX();
                downY = startY = event.getY();
                if (onStateChangeListener != null)
                {
                    onStateChangeListener.onMove(this);
                }
                break;
            case MotionEvent.ACTION_MOVE:

                float dx = Math.abs(event.getX()-downX);
                float dy = Math.abs(event.getY()-downY);
                if (dx > dy && dx > 6)
                {
                    //拦截事件
                    return true;
                }

                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.onInterceptTouchEvent(event);
    }

    /**
     * 打开menu菜单
     */
    public void openMenu() {
        int dx = menuWidth-getScrollX();
        scroller.startScroll(getScrollX(), getScrollY(),dx, getScrollY());
        invalidate();
        if (onStateChangeListener != null)
        {
            onStateChangeListener.onOpen(this);
        }
    }

    /**
     * 关闭菜单
     */
    public void closeMenu() {
        //0表示menu移动到的目标距离
        int dx = 0-getScrollX();
        scroller.startScroll(getScrollX(), getScrollY(),dx, getScrollY());
        invalidate();
        if (onStateChangeListener != null)
        {
            onStateChangeListener.onClose(this);
        }
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (scroller.computeScrollOffset())
        {
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            invalidate();
        }
    }

    public interface OnStateChangeListener
    {
        void onOpen(SlideLayout slideLayout);
        void onMove(SlideLayout slideLayout);
        void onClose(SlideLayout slideLayout);
    }

    public OnStateChangeListener onStateChangeListener;

    public void setOnStateChangeListener(OnStateChangeListener onStateChangeListener) {
        this.onStateChangeListener = onStateChangeListener;
    }
}

欢迎关注公众号。
android ListView的item侧滑删除_第1张图片

你可能感兴趣的:(android,Android自定义View)