热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

androidListView的item侧滑删除

首先看一下效果图,有个直观认识主要功能就是ListView的item可以侧滑,出来一个删除按钮,点击delete就删除该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 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 datas;
private MyAdapter(Context context, ArrayList 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 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 datas;
private MyAdapter(Context context, ArrayList 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;
}
}

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


推荐阅读
  • 使用TabActivity实现Android顶部选项卡功能
    本文介绍如何通过继承TabActivity来创建Android应用中的顶部选项卡。通过简单的步骤,您可以轻松地添加多个选项卡,并实现基本的界面切换功能。 ... [详细]
  • Maven + Spring + MyBatis + MySQL 环境搭建与实例解析
    本文详细介绍如何使用MySQL数据库进行环境搭建,包括创建数据库表并插入示例数据。随后,逐步指导如何配置Maven项目,整合Spring框架与MyBatis,实现高效的数据访问。 ... [详细]
  • 一、Advice执行顺序二、Advice在同一个Aspect中三、Advice在不同的Aspect中一、Advice执行顺序如果多个Advice和同一个JointPoint连接& ... [详细]
  • 在使用mybatis进行mapper.xml测试的时候发生必须为元素类型“mapper”声明属性“namespace”的错误项目目录结构UserMapper和UserMappe ... [详细]
  • Android 中的布局方式之线性布局
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 本文介绍了SIP(Session Initiation Protocol,会话发起协议)的基本概念、功能、消息格式及其实现机制。SIP是一种在IP网络上用于建立、管理和终止多媒体通信会话的应用层协议。 ... [详细]
  • Android与JUnit集成测试实践
    本文探讨了如何在Android项目中集成JUnit进行单元测试,并详细介绍了修改AndroidManifest.xml文件以支持测试的方法。 ... [详细]
  • 本文探讨了如何利用 Android 的 Movie 类来展示 GIF 动画,并详细介绍了调整 GIF 尺寸以适应不同布局的方法。同时,提供了相关的代码示例和注意事项。 ... [详细]
  • Spring Security基础配置详解
    本文详细介绍了Spring Security的基础配置方法,包括如何搭建Maven多模块工程以及具体的安全配置步骤,帮助开发者更好地理解和应用这一强大的安全框架。 ... [详细]
  • 本文提供了一种有效的方法来解决当Android Studio因电脑意外重启而导致的所有import语句出现错误的问题。通过清除缓存和重建项目结构,可以快速恢复开发环境。 ... [详细]
  • 本文探讨了如何在PHP与MySQL环境中实现高效的分页查询,包括基本的分页实现、性能优化技巧以及高级的分页策略。 ... [详细]
  • 处理Android EditText中数字输入与parseInt方法
    本文探讨了如何在Android应用中从EditText组件安全地获取并解析用户输入的数字,特别是用于设置端口号的情况。通过示例代码和异常处理策略,展示了有效的方法来避免因非法输入导致的应用崩溃。 ... [详细]
  • 本文探讨了如何通过Service Locator模式来简化和优化在B/S架构中的服务命名访问,特别是对于需要频繁访问的服务,如JNDI和XMLNS。该模式通过缓存机制减少了重复查找的成本,并提供了对多种服务的统一访问接口。 ... [详细]
  • 深入理解:AJAX学习指南
    本文详细探讨了AJAX的基本概念、工作原理及其在现代Web开发中的应用,旨在为初学者提供全面的学习资料。 ... [详细]
  • HTML:  将文件拖拽到此区域 ... [详细]
author-avatar
1074017584_789ded
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有