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
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滑動刪除及響應事件詳解