仿QQ消息列表item橫向滑動刪除ListView中item側滑刪除

轉載請註明來源:http://blog.csdn.net/kjunchen/article/details/51145737

仿QQ消息列表item橫向滑動刪除ListView中item側滑刪除

在最近的項目中,我的ListView中item選項是長按刪除的效果(Android的通常做法長按或點擊),老闆覺得這個效果不好,提出要求做類似QQ消息列表橫向滑動時刪除消息的效果,這種效果確實感覺挺爽的,但是我也沒做過,於是就在網上搜索了一番,找到了很多例子,實現了這個效果,但在這個過程中不盡如人意,遇到一些曲折,大多數例子確實實現了效果,但是也存在一些問題沒有解決,用着有問題,如item點擊無效、item有點擊事件時滑動item結束後執行了item的點擊事件、item中有按鈕時若手指按下在按鈕位置時滑動無效等等,自己查了很多終於解決了這些問題,現整理如下供大家參考,如有問題請多多包含。

本博文實現思路是在滑動item的過程中修改item的leftMargin實現的。先看下最終效果:

下面就一步步來實現吧。

一、item的佈局

ListView中的item佈局有講究,最外層使用的LinearLayout,設置其orientation屬性爲horizontal。然後裏面主要包含兩個視圖組件,一個是我們正常顯示時的佈局,另外一個是滑動時出現的視圖如刪除按鈕。正常顯示的視圖初始的layout_width設置爲match_parent,並且滑動時出現的視圖必須指定其layout_width,不要使用match_parent或wrap_parent,否則會獲取不到寬度。稍後你會明白的。看本博文完整的item佈局。

本例完整的item佈局文件item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:descendantFocusability="blocksDescendants"
    android:orientation="horizontal">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="8dp">

        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="text" />

        <Button
            android:layout_marginLeft="8dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button"/>
    </LinearLayout>

    <!--必須指定寬度-->
    <TextView
        android:id="@+id/txtv_delete"
        android:layout_width="96dp"
        android:layout_height="match_parent"
        android:gravity="center"
        android:textSize="18sp"
        android:background="#e53935"
        android:textColor="#eeeeee"
        android:text="刪除" />
</LinearLayout>

二、自定義ListView

需要自定義ListView,繼承於ListView。如:

public class SideslipListView extends ListView {
    ...
    public SideslipListView(Context context) {
        super(context);
    }

    public SideslipListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SideslipListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    ...
}

首先獲取屏幕寬度。

private int mScreenWidth;//屏幕的寬度
...
// 獲取屏幕寬度
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
mScreenWidth = dm.widthPixels;

接着重寫onTouchEvent(MotionEvent ev)方法,對手指按下、移動、離開分別進行相應處理。如

@Override
public boolean onTouchEvent(MotionEvent ev) {//事件響應
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            performActionDown(ev);
            break;
        case MotionEvent.ACTION_MOVE:
            performActionMove(ev);
            break;
        case MotionEvent.ACTION_UP:
            performActionUp(ev);
            break;
    }
    return super.onTouchEvent(ev);
}

當手指按下時我們需要獲取到按下時所在的item,得到正常顯示時的視圖,並設置其寬度爲屏幕的寬度;另外還需得到刪除組件的寬度。如:

private int mDownX;//手指初次按下的X座標
private int mDownY;//手指初次按下的Y座標
private boolean isDeleteShow;//刪除組件是否顯示
private ViewGroup mPointChild;//手指按下位置的item組件
private int mDeleteWidth;//刪除組件的寬度
private LinearLayout.LayoutParams mItemLayoutParams;//手指按下時所在的item的佈局參數

...

private void performActionDown(MotionEvent ev) {
    mDownX = (int) ev.getX();
    mDownY = (int) ev.getY();
    if (isDeleteShow) {
        ViewGroup tmpViewGroup = (ViewGroup) getChildAt(pointToPosition(mDownX, mDownY) - getFirstVisiblePosition());
        Log.i(TAG, "*********mPointChild.equals(tmpViewGroup): " + mPointChild.equals(tmpViewGroup));
        if (!mPointChild.equals(tmpViewGroup)) {
            turnNormal();
        }
    }
    //獲取當前的item
    mPointChild = (ViewGroup) getChildAt(pointToPosition(mDownX, mDownY) - getFirstVisiblePosition());

    mDeleteWidth = mPointChild.getChildAt(1).getLayoutParams().width;//獲取刪除組件的寬度
    Log.i(TAG, "**********pointToPosition(x,y): " + pointToPosition(mDownX, mDownY)
            + ", getFirstVisiblePosition() = " + getFirstVisiblePosition()
            + ", mDeleteWidth = " + mDeleteWidth);
    mItemLayoutParams = (LinearLayout.LayoutParams) mPointChild.getChildAt(0).getLayoutParams();

    mItemLayoutParams.width = mScreenWidth;
    mPointChild.getChildAt(0).setLayoutParams(mItemLayoutParams);
}

當手指移動時動態修改正常是顯示視圖組件的leftMargin,如果刪除按鈕沒有顯示,手指向左滑動刪除按鈕就會根據滑動的距離顯示相應的可見寬度。如:

private boolean performActionMove(MotionEvent ev) {
    int nowX = (int) ev.getX();
    int nowY = (int) ev.getY();
    int diffX = nowX - mDownX;
    if (Math.abs(diffX) > Math.abs(nowY - mDownY) && Math.abs(nowY - mDownY) < 20) {
        if (!isDeleteShow && nowX < mDownX) {//刪除按鈕未顯示時向左滑
            if (-diffX >= mDeleteWidth) {//如果滑動距離大於刪除組件的寬度時進行偏移的最大處理
                diffX = -mDeleteWidth;
            }
            mItemLayoutParams.leftMargin = diffX;
            mPointChild.getChildAt(0).setLayoutParams(mItemLayoutParams);
            isAllowItemClick = false;
        } else if (isDeleteShow && nowX > mDownX) {//刪除按鈕顯示時向右滑
            if (diffX >= mDeleteWidth) {
                diffX = mDeleteWidth;
            }
            mItemLayoutParams.leftMargin = diffX - mDeleteWidth;
            mPointChild.getChildAt(0).setLayoutParams(mItemLayoutParams);
            isAllowItemClick = false;
        }
        return true;
    }
    return super.onTouchEvent(ev);
}

當手指離開時,根據移動的距離判斷是否顯示刪除按鈕。如:

private void performActionUp(MotionEvent ev) {
    //如果向左滑出超過隱藏的二分之一就全部顯示
    if (-mItemLayoutParams.leftMargin >= mDeleteWidth / 2) {
        mItemLayoutParams.leftMargin = -mDeleteWidth;
        isDeleteShow = true;
        mPointChild.getChildAt(0).setLayoutParams(mItemLayoutParams);
    } else {
        turnNormal();
    }
}

到這裏基本上就實現了側滑時刪除按鈕的顯示和隱藏了。不過還有些問題。

  • ListView設置了點擊事件時左右滑動會觸發改事件衝突處理

當ListView中的item設置了點擊事件時,我們左右滑動時就會觸發這個點擊事件,那麼如何避免呢?在自定義的ListView中設置標誌isAllowItemClick,當手指按下時設置其值爲isAllowItemClick = true,在手指移動的過程中,若是發生左右移動,則設置其值爲isAllowItemClick = false,另外向外公佈方法:

public boolean isAllowItemClick() {
    return isAllowItemClick;
}

當爲Listview設置點擊事件時這樣做就可以避免了。

mSideslipListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        if (mSideslipListView.isAllowItemClick()) {
            Log.i(TAG, mDataList.get(position) + "被點擊了");
            Toast.makeText(MainActivity.this, mDataList.get(position) + "被點擊了",
                    Toast.LENGTH_SHORT).show();
        }
    }
});
  • 當item中有按鈕時手指按下在改位置時滑動無效處理

這個跟Android的事件分發機制有關係,不知道可以去仔細看看查查資料。我們可以重寫onInterceptTouchEvent(MotionEvent ev)這個方法來解決,若手指按下並且是左右移動時則最該Touch事件進行攔截。如:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {//事件攔截
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            isAllowItemClick = true;

            //側滑刪除
            mDownX = (int) ev.getX();
            mDownY = (int) ev.getY();
            mPointPosition = pointToPosition(mDownX, mDownY);
            Log.i(TAG, "*******pointToPosition(mDownX, mDownY): " + mPointPosition);
            if (mPointPosition != -1) {
                if (isDeleteShow) {
                    ViewGroup tmpViewGroup = (ViewGroup) getChildAt(mPointPosition - getFirstVisiblePosition());
                    if (!mPointChild.equals(tmpViewGroup)) {
                        turnNormal();
                    }
                }
                //獲取當前的item
                mPointChild = (ViewGroup) getChildAt(mPointPosition - getFirstVisiblePosition());

                mDeleteWidth = mPointChild.getChildAt(1).getLayoutParams().width;
                mItemLayoutParams = (LinearLayout.LayoutParams) mPointChild.getChildAt(0).getLayoutParams();

                Log.i(TAG, "*********mItemLayoutParams.height: " + mItemLayoutParams.height +
                        ", mDeleteWidth: " + mDeleteWidth);
                mItemLayoutParams.width = mScreenWidth;
                mPointChild.getChildAt(0).setLayoutParams(mItemLayoutParams);
            }
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            int nowX = (int) ev.getX();
            int nowY = (int) ev.getY();
            int diffX = nowX - mDownX;
            Log.i(TAG, "******dp2px(4): " + dp2px(8) + ", dp2px(8): " + dp2px(8) +
                    ", density: " + getContext().getResources().getDisplayMetrics().density);
            if (Math.abs(diffX) > dp2px(4) || Math.abs(nowY - mDownY) > dp2px(4)) {
                return true;//避免子佈局中有點擊的控件時滑動無效
            }
            break;
        }
    }
    return super.onInterceptTouchEvent(ev);
}

public float dp2px(int dp) {
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
            getContext().getResources().getDisplayMetrics());
}

到此,我們的自定義ListView基本上就搞定了。最後貼上我的自定義SideslipListView的完整源碼。

完整的自定義 SideslipListView.java

package com.junkchen.toucheventtest;

import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.ListView;

/**
 * Created by JunkChen on 2016/3/29 0029.
 */
public class SideslipListView extends ListView {
    private static final String TAG = "SideslipListView";

    private int mScreenWidth;//屏幕的寬度
    private boolean isDeleteShow;//刪除組件是否顯示
    private ViewGroup mPointChild;//手指按下位置的item組件
    private int mDeleteWidth;//刪除組件的寬度
    private LinearLayout.LayoutParams mItemLayoutParams;//手指按下時所在的item的佈局參數
    private int mDownX;//手指初次按下的X座標
    private int mDownY;//手指初次按下的Y座標

    private int mPointPosition;//手指按下位置所在的item位置
    private boolean isAllowItemClick;//是否允許item點擊

    public SideslipListView(Context context) {
        super(context);
        init(context);
    }

    public SideslipListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public SideslipListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        // 獲取屏幕寬度
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        mScreenWidth = dm.widthPixels;
        Log.i(TAG, "***********mScreenWidth: " + mScreenWidth);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {//事件攔截
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                isAllowItemClick = true;

                //側滑刪除
                mDownX = (int) ev.getX();
                mDownY = (int) ev.getY();
                mPointPosition = pointToPosition(mDownX, mDownY);
                Log.i(TAG, "*******pointToPosition(mDownX, mDownY): " + mPointPosition);
                if (mPointPosition != -1) {
                    if (isDeleteShow) {
                        ViewGroup tmpViewGroup = (ViewGroup) getChildAt(mPointPosition - getFirstVisiblePosition());
                        if (!mPointChild.equals(tmpViewGroup)) {
                            turnNormal();
                        }
                    }
                    //獲取當前的item
                    mPointChild = (ViewGroup) getChildAt(mPointPosition - getFirstVisiblePosition());

                    mDeleteWidth = mPointChild.getChildAt(1).getLayoutParams().width;
                    mItemLayoutParams = (LinearLayout.LayoutParams) mPointChild.getChildAt(0).getLayoutParams();

                    Log.i(TAG, "*********mItemLayoutParams.height: " + mItemLayoutParams.height +
                            ", mDeleteWidth: " + mDeleteWidth);
                    mItemLayoutParams.width = mScreenWidth;
                    mPointChild.getChildAt(0).setLayoutParams(mItemLayoutParams);
                }
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                int nowX = (int) ev.getX();
                int nowY = (int) ev.getY();
                int diffX = nowX - mDownX;
                Log.i(TAG, "******dp2px(4): " + dp2px(8) + ", dp2px(8): " + dp2px(8) +
                        ", density: " + getContext().getResources().getDisplayMetrics().density);
                if (Math.abs(diffX) > dp2px(4) || Math.abs(nowY - mDownY) > dp2px(4)) {
                    return true;//避免子佈局中有點擊的控件時滑動無效
                }
                break;
            }
        }
        return super.onInterceptTouchEvent(ev);
    }

    public float dp2px(int dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
                getContext().getResources().getDisplayMetrics());
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {//事件響應
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                performActionDown(ev);
                break;
            case MotionEvent.ACTION_MOVE:
                performActionMove(ev);
                break;
            case MotionEvent.ACTION_UP:
                performActionUp(ev);
                break;
        }
        return super.onTouchEvent(ev);
    }

    private void performActionDown(MotionEvent ev) {
        mDownX = (int) ev.getX();
        mDownY = (int) ev.getY();
        if (isDeleteShow) {
            ViewGroup tmpViewGroup = (ViewGroup) getChildAt(pointToPosition(mDownX, mDownY) - getFirstVisiblePosition());
            Log.i(TAG, "*********mPointChild.equals(tmpViewGroup): " + mPointChild.equals(tmpViewGroup));
            if (!mPointChild.equals(tmpViewGroup)) {
                turnNormal();
            }
        }
        //獲取當前的item
        mPointChild = (ViewGroup) getChildAt(pointToPosition(mDownX, mDownY) - getFirstVisiblePosition());

        mDeleteWidth = mPointChild.getChildAt(1).getLayoutParams().width;//獲取刪除組件的寬度
        Log.i(TAG, "**********pointToPosition(x,y): " + pointToPosition(mDownX, mDownY)
                + ", getFirstVisiblePosition() = " + getFirstVisiblePosition()
                + ", mDeleteWidth = " + mDeleteWidth);
        mItemLayoutParams = (LinearLayout.LayoutParams) mPointChild.getChildAt(0).getLayoutParams();

        mItemLayoutParams.width = mScreenWidth;
        mPointChild.getChildAt(0).setLayoutParams(mItemLayoutParams);
    }

    private boolean performActionMove(MotionEvent ev) {
        int nowX = (int) ev.getX();
        int nowY = (int) ev.getY();
        int diffX = nowX - mDownX;
        if (Math.abs(diffX) > Math.abs(nowY - mDownY) && Math.abs(nowY - mDownY) < 20) {
            if (!isDeleteShow && nowX < mDownX) {//刪除按鈕未顯示時向左滑
                if (-diffX >= mDeleteWidth) {//如果滑動距離大於刪除組件的寬度時進行偏移的最大處理
                    diffX = -mDeleteWidth;
                }
                mItemLayoutParams.leftMargin = diffX;
                mPointChild.getChildAt(0).setLayoutParams(mItemLayoutParams);
                isAllowItemClick = false;
            } else if (isDeleteShow && nowX > mDownX) {//刪除按鈕顯示時向右滑
                if (diffX >= mDeleteWidth) {
                    diffX = mDeleteWidth;
                }
                mItemLayoutParams.leftMargin = diffX - mDeleteWidth;
                mPointChild.getChildAt(0).setLayoutParams(mItemLayoutParams);
                isAllowItemClick = false;
            }
            return true;
        }
        return super.onTouchEvent(ev);
    }

    private void performActionUp(MotionEvent ev) {
        //如果向左滑出超過隱藏的二分之一就全部顯示
        if (-mItemLayoutParams.leftMargin >= mDeleteWidth / 2) {
            mItemLayoutParams.leftMargin = -mDeleteWidth;
            isDeleteShow = true;
            mPointChild.getChildAt(0).setLayoutParams(mItemLayoutParams);
        } else {
            turnNormal();
        }
    }

    /**
     * 轉換爲正常隱藏情況
     */
    public void turnNormal() {
        mItemLayoutParams.leftMargin = 0;
        mPointChild.getChildAt(0).setLayoutParams(mItemLayoutParams);
        isDeleteShow = false;
    }

    /**
     * 是否允許Item點擊
     *
     * @return
     */
    public boolean isAllowItemClick() {
        return isAllowItemClick;
    }
}

三、自定義SideslipListView使用

在佈局中的使用。

本例完整的佈局文件 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center_horizontal"
    tools:context="com.junkchen.toucheventtest.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_margin="8dp"
        android:text="ListView item側滑刪除" />

    <!--使用自定義的SideslipListView-->
    <com.junkchen.toucheventtest.SideslipListView
        android:id="@+id/sideslipListView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </com.junkchen.toucheventtest.SideslipListView>
</LinearLayout>

本例完整的Java文件 MainActivity.java

package com.junkchen.toucheventtest;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private SideslipListView mSideslipListView;

    /**
     * 初始化數據
     */
    private ArrayList<String> mDataList = new ArrayList<String>() {
        {
            for (int i = 0; i < 50; i++) {
                add("ListView item  " + i);
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mSideslipListView = (SideslipListView) findViewById(R.id.sideslipListView);
        mSideslipListView.setAdapter(new CustomAdapter());//設置適配器
        //設置item點擊事件
        mSideslipListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (mSideslipListView.isAllowItemClick()) {
                    Log.i(TAG, mDataList.get(position) + "被點擊了");
                    Toast.makeText(MainActivity.this, mDataList.get(position) + "被點擊了",
                            Toast.LENGTH_SHORT).show();
                }
            }
        });
        //設置item長按事件
        mSideslipListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                if (mSideslipListView.isAllowItemClick()) {
                    Log.i(TAG, mDataList.get(position) + "被長按了");
                    Toast.makeText(MainActivity.this, mDataList.get(position) + "被長按了",
                            Toast.LENGTH_SHORT).show();
                    return true;//返回true表示本次事件被消耗了,若返回
                }
                return false;
            }
        });
    }

    /**
     * 自定義ListView適配器
     */
    class CustomAdapter extends BaseAdapter {
        @Override
        public int getCount() {
            return mDataList.size();
        }

        @Override
        public Object getItem(int position) {
            return mDataList.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder;
            if (null == convertView) {
                convertView = View.inflate(MainActivity.this, R.layout.item, null);
                viewHolder = new ViewHolder();
                viewHolder.textView = (TextView) convertView.findViewById(R.id.textView);
                viewHolder.txtv_delete = (TextView) convertView.findViewById(R.id.txtv_delete);
                convertView.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }
            viewHolder.textView.setText(mDataList.get(position));
            final int pos = position;
            viewHolder.txtv_delete.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(MainActivity.this, mDataList.get(pos) + "被刪除了",
                            Toast.LENGTH_SHORT).show();
                    mDataList.remove(pos);
                    notifyDataSetChanged();
                    mSideslipListView.turnNormal();
                }
            });
            return convertView;
        }
    }

    class ViewHolder {
        public TextView textView;
        public TextView txtv_delete;
    }
}

上面這個應用就不詳細介紹了,註釋寫得比較清楚。可以自行新建工程測試,想要完整源碼請加羣聯繫羣主。

如有問題歡迎加qq羣討論學習:365532949
HomePage:http://junkchen.com

發佈了39 篇原創文章 · 獲贊 22 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章