android ListView的item侧滑删除

来源:互联网 发布:人工智能相关的影片 编辑:程序博客网 时间:2024/06/04 18:33

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

这是一个相对比较综合的例子,来看看动手之前需要准备哪些知识。
1. 对自定义View要有一定的知识基础,参看View绘制流程
2. 事件的拦截以及反拦截的相关知识,以便很好的解决事件冲突问题,关于事件机制,可以参看android事件处理机制
3. 滑动器Scroller的使用,参看Scroller简单用法
4. 自定义View中的接口回调(View状态变化时执行回调)

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

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

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

<?xml version="1.0" encoding="utf-8"?><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;    @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) {                    datas.remove(myContent);                    notifyDataSetChanged();                }            });            SlideLayout slideLayout = (SlideLayout) convertView;            slideLayout.setOnStateChangeListener(new MyOnStateChangeListener());            return convertView;        }        public SlideLayout slideLayout = null;        class MyOnStateChangeListener implements SlideLayout.OnStateChangeListener        {            @Override            public void onOpen(SlideLayout layout) {                slideLayout = layout;            }            @Override            public void onMove(SlideLayout layout) {                if (slideLayout != null && slideLayout !=layout)                {                    slideLayout.closeMenu();                }            }            @Override            public void onClose(SlideLayout 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;    }}

欢迎关注公众号。
这里写图片描述

0 0
原创粉丝点击