1. 程式人生 > >Android ListView滑動刪除及響應事件詳解

Android ListView滑動刪除及響應事件詳解

源代碼下載 引用 example imp cor toast don float 發現

目標:實現類似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滑動刪除及響應事件詳解