1. 程式人生 > >Android自定義ListView實現仿微信側滑刪除

Android自定義ListView實現仿微信側滑刪除

經常在遇到問題第一時間都會在網上搜索解決的方法,因此看到很多前輩們的比較精闢的技術文章,學習了很多東西,現在將自己平時工作中開發的一些小功能坐下總結,也寫出來,既方便自己理清思路記憶功能塊實現思路,又能與大家一起交流分享技術。
第一次寫文章,哪裡有不對的希望大家多多包涵!

通過ListView + PopupWindow的方式實現了仿微信的側滑刪除、取消關注功能。

實現的效果圖如下:

這裡寫圖片描述

下面貼出程式碼:

1、列表子項的“取消關注”和“刪除”按鈕的佈局,檔名“layout_del_cancel_btn.xml”,程式碼如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="match_parent"> <Button android:id="@+id/id_item_cancel_btn" android:layout_width="wrap_content" android:layout_height
="wrap_content" android:text="取消關注" android:background="@android:color/darker_gray" android:textColor="#ffffff" android:layout_alignParentRight="true" android:layout_centerVertical="true" />
<Button android:id="@+id/id_item_btn" android:layout_width
="60dp" android:layout_height="wrap_content" android:text="刪除" android:background="@android:color/holo_red_light" android:textColor="#ffffff" android:layout_alignParentRight="true" android:layout_centerVertical="true" />
</LinearLayout>

2、自定義的ListView,檔名“SlideDeleteCancelListView.java”,程式碼如下:

package com.record2text.vitospc.deletelistview;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.PopupWindow;

/**
 * 仿微信刪除/取消關注列表
 * @author 馬文濤 技術群:317922608
 */
public class SlideDeleteCancelListView extends ListView {

    private static final String TAG = "SlideDelListView";

    private LayoutInflater mInflater = null;

    /**
     * 使用者滑動的最小距離
     */
    private int touchSlop;
    /**
     * 是否響應滑動
     */
    private boolean isSliding;
    /**
     * 手指按下時的x座標
     */
    private int xDown;
    /**
     * 手指按下時的y座標
     */
    private int yDown;
    /**
     * 手指移動時的x座標
     */
    private int xMove;
    /**
     * 手指移動時的y座標
     */
    private int yMove;

    /**
     * 當前手指觸控的View
     */
    private View mCurrentView;
    /**
     * 單籤手指觸控的位置
     */
    private int mCurrentViewPos;

    /**
     * 為刪除按鈕提供一個回撥介面
     */
    private DelButtonClickListener mDelListener = null;
    private CancelButtonClickListener mCancelListener = null;

    private PopupWindow mPopupWindow = null;
    private Button mDelBtn = null,mCancelBtn = null;

    private int mPopupWindowWidth, mPopupWindowHeight;

    /** 自定義ListView的構造方法 在裡面做一些必要的一些初始化
     * @param context
     * @param attrs
     */
    public SlideDeleteCancelListView(Context context, AttributeSet attrs) {
        super(context, attrs);

        mInflater = LayoutInflater.from(context);
        //使用者手指移動的最小距離,用來判斷是否響應觸發移動事件
        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

        View view = mInflater.inflate(R.layout.layout_del_cancel_btn,null);
        //刪除按鈕
        mDelBtn = (Button) view.findViewById(R.id.id_item_btn);
        //取消按鈕
        mCancelBtn = (Button) view.findViewById(R.id.id_item_cancel_btn);
        mPopupWindow = new PopupWindow(view,
                LinearLayout.LayoutParams.WRAP_CONTENT,
                LinearLayout.LayoutParams.WRAP_CONTENT);
        /**
         * 先呼叫下measure,否則拿不到寬和高
         */
        mPopupWindow.getContentView().measure(0,0);
        mPopupWindowHeight = mPopupWindow.getContentView().getMeasuredHeight();
        mPopupWindowWidth = mPopupWindow.getContentView().getMeasuredWidth();

    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        int action = ev.getAction();
        int x = (int) ev.getX();
        int y = (int) ev.getY();

        switch (action){
            case MotionEvent.ACTION_DOWN:
                //手指按下時水平方向x的位置
                xDown = x;
                //手指按下時垂直方向y的位置
                yDown = y;
                /*
                * 如果當前popupWindow顯示,則直接隱藏,然後遮蔽ListView的Touch事件的下傳
                * */
                if (mPopupWindow.isShowing()){
                    dismissPopWindow();
                    return false;
                }
                // 獲得當前手指按下時的item的位置
                mCurrentViewPos = pointToPosition(xDown,yDown);
                // 獲得當前手指按下時的ListView的item項
                mCurrentView = getChildAt(mCurrentViewPos - getFirstVisiblePosition());
                break;
            case MotionEvent.ACTION_MOVE:
                //手指移動時x的位置
                xMove = x;
                //手指一動時y的位置
                yMove = y;
                //水平滑動的距離(可能為負值)
                int dx = xMove - xDown;
                //垂直滑動的距離(可能為負值)
                int dy = yMove - yDown;
                /*
                * 判斷是否是從右到左的滑動
                * */
                if ( xMove < xDown && Math.abs(dx) > touchSlop && Math.abs(dy) < touchSlop ){
                    Log.e(TAG, "touchslop = " + touchSlop + " , dx = " + dx + " , dy = " + dy);
                    isSliding = true;
                }
                break;
        }

        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        /*
        * 如果是從右到左的滑動才響應,之前已在 dispatchTouchEvent 中獲得了是否是從右向左滑動
        * */
        if ( isSliding ){
            switch (action){
                case MotionEvent.ACTION_MOVE:
                    int []location = new int[2];
                    //獲得當前item的位置x與y
                    mCurrentView.getLocationOnScreen(location);
                    //設定PopupWindow的動畫
                    mPopupWindow.setAnimationStyle(R.style.popwindow_delete_btn_anim_style);
                    mPopupWindow.update();
                    Log.e(TAG,"width location[0]: " + location[0]);
                    Log.e(TAG,"height location[1]: " + location[1]);
                    Log.e(TAG,"mCurrentView.getWidth(): " + mCurrentView.getWidth());
                    Log.e(TAG,"mCurrentView.getHeight(): " + mCurrentView.getHeight());
                    Log.e(TAG,"mPopupWindowHeight: " + mPopupWindowHeight);
                    //設定“取消關注”、“刪除”按鈕PopWindow的顯示位置
                    //相對於父控制元件的位置(例如正中央Gravity.CENTER,下方Gravity.BOTTOM等),可以設定偏移或無偏移
                    //相對某個控制元件的位置,有偏移;xoff表示x軸的偏移,正值表示向左,負值表示向右;yoff表示相對y軸的偏移,正值是向下,負值是向上;
                    mPopupWindow.showAtLocation(mCurrentView,
                            Gravity.LEFT | Gravity.TOP,
                            location[0] + mCurrentView.getWidth() ,
                            location[1] + mCurrentView.getHeight()/2 - mPopupWindowHeight /2);
                    //設定刪除按鈕的回撥
                    mDelBtn.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            //通過介面物件呼叫當前Item上刪除按鈕的點選方法
                            mDelListener.onDelClick(mCurrentViewPos);
                            mPopupWindow.dismiss();
                        }
                    });
                    //設定取消按鈕的回撥
                    mCancelBtn.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            //通過介面物件呼叫當前Item上取消按鈕的點選方法
                            mCancelListener.onCancelClick(mCurrentViewPos);
                            mPopupWindow.dismiss();
                        }
                    });
                    Log.e(TAG, "mPopupWindow.getHeight()=" + mPopupWindowHeight);
                    break;
                case MotionEvent.ACTION_UP:
                    //設定側滑關閉
                    isSliding = false;
                    break;
            }
            // 相應滑動期間螢幕itemClick事件,避免發生衝突
            return true;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 隱藏popupWindow
     */
    private void dismissPopWindow()
    {
        if (mPopupWindow != null && mPopupWindow.isShowing())
        {
            mPopupWindow.dismiss();
        }
    }

    /** 設定刪除按鈕點選事件監聽
     * @param listener DelButtonClickListener 刪除按鈕監聽介面物件
     */
    public void setDelButtonClickListener(DelButtonClickListener listener)
    {
        mDelListener = listener;
    }

    /** 設定取消按鈕點選事件監聽
     * @param listener CancelButtonClickListener 按鈕點選事件監聽介面物件
     */
    public void setCancelButtonClickListener(CancelButtonClickListener listener){
        mCancelListener = listener;
    }

    /**
     * 刪除按鈕監聽介面
     */
    interface DelButtonClickListener{
        void onDelClick(int position);
    }

    /**
     * 取消按鈕監聽介面
     */
    interface CancelButtonClickListener{
        void onCancelClick(int position);
    }
}

3、滑動展示“刪除”、“取消關注”時需要展示和隱藏的動畫,在自定義的ListView類“SlideDeleteCancelListView”中用到了,分別如下:
a.展示動畫,檔名“delete_btn_show.xml”,程式碼如下:

<?xml version="1.0" encoding="utf-8"?>

http://schemas.android.com/apk/res/android">

<scale android:interpolator="@android:anim/accelerate_decelerate_interpolator"

android:fromXScale="0.0"
android:toXScale="1.0"
android:fromYScale="1.0"
android:toYScale="1.0"
android:pivotX="100%"
android:pivotY="50%"
android:fillAfter="false"
android:duration="200" >

</scale>

</set>

b.隱藏動畫,檔名“delete_btn_hide.xml”,程式碼如下:

<?xml version="1.0" encoding="utf-8"?>

http://schemas.android.com/apk/res/android">

<scale android:interpolator="@android:anim/accelerate_decelerate_interpolator"

android:fromXScale="1.0"
android:toXScale="0.0"
android:fromYScale="1.0"
android:toYScale="1.0"
android:pivotX="100%"
android:pivotY="50%"
android:fillAfter="false"
android:duration="200" >

</scale>

</set>

4、以上部分已經完成了自定義有刪除和取消關注功能的ListView,緊接著我們看一下如何使用這個ListView吧,首先貼上佈局檔案中的呼叫程式碼,檔名“activity_main.xml”,程式碼如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.record2text.vitospc.deletelistview.MainActivity">

<com.record2text.vitospc.deletelistview.SlideDeleteCancelListView
    android:id="@+id/id_listview"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</com.record2text.vitospc.deletelistview.SlideDeleteCancelListView>
</RelativeLayout>

5、下來我們看一下主頁面“MainActivity.java”中如何呼叫該段程式碼,並給響應的部分新增事件的,程式碼如下:

package com.record2text.vitospc.deletelistview;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 仿微信刪除/取消關注列表
 * @author 馬文濤 技術群:317922608
 */
public class MainActivity extends AppCompatActivity {

    private SlideDeleteCancelListView mListView = null;
    private List<String> mDatas = null;
    private ArrayAdapter mAdapter = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mListView = (SlideDeleteCancelListView) findViewById(R.id.id_listview);
        mDatas = new ArrayList<>(Arrays.asList("北京市", "天津市", "上海市", "重慶市", "安徽省", "福建省",
                "甘肅省", "廣東省", "貴州省", "海南省", "河北省","河南省","黑龍江省","湖北省","湖南省","吉林省"));
        mAdapter = new ArrayAdapter(MainActivity.this,android.R.layout.simple_list_item_1, mDatas);
        mListView.setAdapter(mAdapter);
        //設定列表項Item刪除按鈕的點選監聽事件
        mListView.setDelButtonClickListener(new SlideDeleteCancelListView.DelButtonClickListener() {
            @Override
            public void onDelClick(int position) {
                Toast.makeText(MainActivity.this, "刪除:" + position + " : " + mAdapter.getItem(position), Toast.LENGTH_SHORT).show();
                //從列表中移除當前項
                mAdapter.remove(mAdapter.getItem(position));
            }
        });
        //設定列表項Item的取消按鈕點選監聽事件
        mListView.setCancelButtonClickListener(new SlideDeleteCancelListView.CancelButtonClickListener() {
            @Override
            public void onCancelClick(int position) {
                Toast.makeText(MainActivity.this, "取消關注:" + position + " : " + mAdapter.getItem(position), Toast.LENGTH_SHORT).show();
            }
        });
        //設定列表項Item點選監聽事件
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener()
        {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id)
            {
                Toast.makeText(MainActivity.this, "您點選的是第 " + position + " 項: " + mAdapter.getItem(position), Toast.LENGTH_SHORT).show();
            }
        });
    }
}

已經完成了一個自定義的具有側滑“刪除”、“取消關注”的自定義ListView控制元件,在使用過程中大家只需要結合自己的專案進行修改即可。

新人初次發文章,有不足之處希望大家多多海涵,有興趣的朋友可以加QQ群317922608,大家一起多多交流,多多指教,相互學習!