Android ListView滑动删除及响应事件详解
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android ListView滑动删除及响应事件详解相关的知识,希望对你有一定的参考价值。
目标:实现类似QQ,微信的消息列表滑动删除
具体操作:
1. 主页面布局
首先在布局文件(本例是activity_main.xml)中引入ListView控件,并指定id(如下代码中黑体部分)。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
2. 定义滑动布局类
我们需有个工具类提供滑动的布局效果,即监听触摸动作,产生页面滑动效果。github上有很多强大的滑动类,博主最终引用了非常简易,容易操作的SwipeLayout.java。(如果你不关心滑动布局的实现机制,那么在工程中创建SwipeLayout.java类,拷贝附录1中的代码可。如果你想了解更多,可参考博客见具体实现过程:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0422/2771.html)
3. 定制ListView子项的布局
ListView确定的是整体样式,即列表排列,我们还可以定制列表中每行所放内容的样式。接下来我们的目标是像QQ信息列表那样,每行以图片开头,图片旁是联系人姓名。所以,在drawable目录下准备几张头像图片以及删除按钮的图标,然后在layout目录下新建activity_item.xml,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent">
<!--引用步骤2中添加的SwipeLayout.java布局类,包名为你项目中SwaipeLayout.java存放的路径--> <com.example.whjth.swipelayout_demo.SwipeLayout android:layout_width="match_parent" android:layout_height="50dp">
<LinearLayout android:layout_width="match_parent" android:layout_height="50dp" android:background="#ffffff"> <ImageView android:id="@+id/image" android:layout_width="50dp" android:layout_height="50dp" android:padding="5dp" android:src="@drawable/p10"/> <!--p10为存放在drawable目录下的头像图片名称--> <TextView android:id="@+id/name" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center_horizontal" android:padding="5dp" android:layout_marginLeft="10dp" android:text= "xingming" /> </LinearLayout> <LinearLayout android:layout_width="50dp" android:layout_height="50dp"> <RelativeLayout android:id="@+id/delete_button" android:clickable="true" android:layout_width="50dp" android:layout_height="50dp" android:background="#ff0000"> <View android:layout_centerInParent="true" android:layout_width="28dp" android:layout_height="28dp" android:background="@drawable/trash"/> <!--trash为删除图标--> </RelativeLayout> </LinearLayout> </com.example.whjth.swipelayout_demo.SwipeLayout> </LinearLayout>
在上述代码中,第二个LinearLayout标签的宽度属性是“match_parent",这代表它与父布局(屏幕)宽度相同,从而遮蔽了第三个LineaLayout的内容,无法显现,只有通过滑动才可见。效果如下面左图所示;如果注释掉第二个LinearLayout内容,则可以看见第三个LinearLayout中的内容,如下面右图所示。
4. 创建展示内容的类对象
布局文件都准备好了,下面就是往列表中写入内容了。我们发现列表中的每一项都具有同样的属性,即图片+名称。所以为了方便操作,我们把准备写入的内容当成一个类对象,为它定义图片和名称属性,实质上就是一个Java Bean。本例将其命名为Person.java,代码见下:
public class Person { private String name; private int ImageId; public Person(String name, int ImageId){ this.name = name; this.ImageId = ImageId; } public String getName(){ return name; } public int getImageId(){ return ImageId; } }
5. 定义传递数据至ListView的适配器
让我们暂时停一下,理理思路,总结一下之前的工作:(1)我们在总布局中引入ListView控件(2)并为它定制了样式(3)载入了可以感知滑动的工具类(4)又将需要放入其中的内容整理成了类对象,这使我们可以通过数组的形式引用大量数据。
所以,我们剩下的工作是将数据传递到ListView中。不过,数组中的数据无法直接传递给ListView的,我们需要借助适配器完成。Android中提供了很多适配器的实现类,本例中使用的是ArrayAdapter。它可以通过泛型来指定要适配的数据类型(本例数据类型为步骤4中定义的Person),然后在构造函数中把要适配的数据传入, 然后在ArrayAdapter的构造函数中依次传入当前上下文、ListView子项布局的id,以及要适配的数据。本例将其命名为PersonAdapter.java,代码见下:
import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import java.util.List; public class PersonAdapter extends ArrayAdapter<Person>{ private int resourceId; private Context context;
/* 重写构造函数,即上文中划横线部分的实现 */ public PersonAdapter(Context context, int textViewResourceId, List<Person> objects){ super(context, textViewResourceId, objects); resourceId = textViewResourceId; this.context = context; }
/* 重写getView()方法,该方法在每个子项被滚动到屏幕内的时候会被调用 */ public View getView(int position, View convertView, final ViewGroup parent){ final Person person = getItem(position); //获取当前项的Person实例 View view; ViewHolder viewHolder; //下文中定义的内部类,用于保存image,name,delete等实例,而不是每次滑动加载时都通过findViewById的方法获得控件实例 /* getView()方法中的converView参数表示之前加载的布局 */
if(convertView == null){
/* 如果converView参数值为null,则使用LayoutInflater去加载布局 */
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false); viewHolder = new ViewHolder();
/* 调用View的findViewById()方法分别获得image和name实例 */
viewHolder.image = (ImageView) view.findViewById(R.id.image); viewHolder.name = (TextView) view.findViewById(R.id.name); viewHolder.delete = view.findViewById(R.id.delete_button); view.setTag(viewHolder); } else{
/* 否则,直接对converView进行重用 */ view = convertView; viewHolder = (ViewHolder) view.getTag(); }
/* 调用setImageResource()和setText()方法来设置显示的图片和文字 */ viewHolder.image.setImageResource(person.getImageId()); viewHolder.name.setText(person.getName());
/* 以下黑体为事件监听响应部分,即点击删除图标和头像会分别显示提醒信息 */ viewHolder.delete = view.findViewById(R.id.delete_button); viewHolder.delete.setOnClickListener(new View.OnClickListener(){ public void onClick(View v){ Toast.makeText(getContext(), "you clicked delete button", Toast.LENGTH_SHORT).show(); } }); viewHolder.image.setOnClickListener(new View.OnClickListener(){ public void onClick(View v){ Toast.makeText(getContext(), "you clicked image", Toast.LENGTH_SHORT).show(); } });
return view; //返回布局 } class ViewHolder{ ImageView image; TextView name; View delete; } }
数据适配完成,下面只用在主程序中调用ListView的setAdapter()方法,将构建好的适配器对象传递进去,这样ListView和数据之间的关联就完成了。
6. 编写主程序
在主程序中初始化列表条目,并通过适配器将其值传入ListView。参考代码MainActivity.java见下:
import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.ListView; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private List<Person> PersonList = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initPerson(); PersonAdapter adapter = new PersonAdapter(MainActivity.this, R.layout.activity_item, PersonList); ListView listView = (ListView) findViewById(R.id.list_view); listView.setAdapter(adapter); } private void initPerson(){ for(int i=0; i<2; i++) { Person person = new Person("Alex", R.drawable.p1); PersonList.add(person); person = new Person("Brandon", R.drawable.p2); PersonList.add(person); person = new Person("Charles", R.drawable.p3); PersonList.add(person); person = new Person("Davie", R.drawable.p4); PersonList.add(person); person = new Person("Eric", R.drawable.p5); PersonList.add(person); person = new Person("Fanny", R.drawable.p6); PersonList.add(person); person = new Person("Grace", R.drawable.p7); PersonList.add(person); person = new Person("Helen", R.drawable.p8); PersonList.add(person); person = new Person("Isabelle", R.drawable.p9); PersonList.add(person); person = new Person("Jenny", R.drawable.p10); PersonList.add(person); } } }
效果图:
附录1:SwipeLayout.java
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.LinearLayout;
/** * Created by Bruce on 11/24/14. */ public class SwipeLayout extends LinearLayout { private ViewDragHelper viewDragHelper; private View contentView; private View actionView; private int dragDistance; private final double AUTO_OPEN_SPEED_LIMIT = 800.0; private int draggedX; public SwipeLayout(Context context) { this(context, null); } public SwipeLayout(Context context, AttributeSet attrs) { this(context, attrs, -1); } public SwipeLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); viewDragHelper = ViewDragHelper.create(this, new DragHelperCallback()); } @Override protected void onFinishInflate() { contentView = getChildAt(0); actionView = getChildAt(1); actionView.setVisibility(GONE); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); dragDistance = actionView.getMeasuredWidth(); } private class DragHelperCallback extends ViewDragHelper.Callback { @Override public boolean tryCaptureView(View view, int i) { return view == contentView || view == actionView; } @Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { draggedX = left; if (changedView == contentView) { actionView.offsetLeftAndRight(dx); } else { contentView.offsetLeftAndRight(dx); } if (actionView.getVisibility() == View.GONE) { actionView.setVisibility(View.VISIBLE); } invalidate(); } @Override public int clampViewPositionHorizontal(View child, int left, int dx) { if (child == contentView) { final int leftBound = getPaddingLeft(); final int minLeftBound = -leftBound - dragDistance; final int newLeft = Math.min(Math.max(minLeftBound, left), 0); return newLeft; } else { final int minLeftBound = getPaddingLeft() + contentView.getMeasuredWidth() - dragDistance; final int maxLeftBound = getPaddingLeft() + contentView.getMeasuredWidth() + getPaddingRight(); final int newLeft = Math.min(Math.max(left, minLeftBound), maxLeftBound); return newLeft; } } @Override public int getViewHorizontalDragRange(View child) { return dragDistance; } @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { super.onViewReleased(releasedChild, xvel, yvel); boolean settleToOpen = false; if (xvel > AUTO_OPEN_SPEED_LIMIT) { settleToOpen = false; } else if (xvel < -AUTO_OPEN_SPEED_LIMIT) { settleToOpen = true; } else if (draggedX <= -dragDistance / 2) { settleToOpen = true; } else if (draggedX > -dragDistance / 2) { settleToOpen = false; } final int settleDestX = settleToOpen ? -dragDistance : 0; viewDragHelper.smoothSlideViewTo(contentView, settleDestX, 0); ViewCompat.postInvalidateOnAnimation(SwipeLayout.this); } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if(viewDragHelper.shouldInterceptTouchEvent(ev)) { return true; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { viewDragHelper.processTouchEvent(event); return true; } @Override public void computeScroll() { super.computeScroll(); if(viewDragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } } }
项目完整源代码下载:https://github.com/Vera97/ListView-SwipeLayout-Demo
以上是关于Android ListView滑动删除及响应事件详解的主要内容,如果未能解决你的问题,请参考以下文章
android listview上下滑动时为啥不触发点击事件
Android之NineOldAndroids实现绚丽的ListView左右滑动删除Item效果