listview 側滑刪除 仿qq

側滑原理:

側滑是通過手勢判斷,將中間的view滑出去,將右邊的view滑進來,當滑動的距離不夠完全顯示右邊的view的時候執行平滑函數,自動將右邊的view滑進去,並改變滑動狀態爲open。所以需要通過xml定義兩個view,一個作爲默認顯示的view,一個作爲隱藏(需要滑入)的view。

先從簡單練起:

開始我做了一個簡單的demo,沒有涉及listview,只是寫了簡單的一個滑入滑出的效果。

public class ScrollerLinearLayout extends LinearLayout {

    private View centerView = null; //不滑動顯示的view
    private View rightView = null; //左滑顯示的view

    //用這兩個可以實現滑動效果
    private ScrollerCompat mOpenScroller;
    private ScrollerCompat mCloseScroller;

    private int downX; //開始按下的位置

    //記錄狀態
    private int state = STATE_CLOSE;
    private static final int STATE_CLOSE = 0;
    private static final int STATE_OPEN = 1;

    private int mBaseX;

    public ScrollerLinearLayout(Context context) {
        super(context);
    }

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

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

    public void setContentView(View centerView, View rightView){
        this.centerView = centerView;
        this.rightView = rightView;

        //初始化mColoseScroller和mOpenScroller
        mCloseScroller = ScrollerCompat.create(getContext());
        mOpenScroller = ScrollerCompat.create(getContext());

        initView();
    }

    //child view的佈局參數設定好後 添加到parent view裏面
    private void initView() {

//        setLayoutParams(new AbsListView.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
        setLayoutParams(new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
        //這也是設置寬和高
        LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
        centerView.setLayoutParams(contentParams);
        rightView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT));

        //將這兩個佈局都add到這個view中
        addView(centerView);
        addView(rightView);
    }

    // 判斷是否滑出的狀態
    public boolean isOpen() {
        return state == STATE_OPEN;
    }

    /**
     * 這裏的函數是由 listview來控制的。
     * listview來監聽左滑,並判斷是否需要將rightview 顯示出來。
     * (因爲不是所有左滑都要畫出rightview,還需要判斷listview的其他item的狀態不是STATE_OPEN 狀態
     * ,所以由listview來控制自己item的rightview是否左滑最合理,這個函數目的就是讓listview來調用)
     * 反之右滑亦然
     *
     * @param event
     * @return
     */
    public boolean onSwipeLeft(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = (int) event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                int dis = (int) (downX - event.getX());
                if (state == STATE_OPEN) {
                    dis += rightView.getWidth();
                }
                //手指移動多少距離,rightview也移動多少距離
                swipe(dis);
                break;
            case MotionEvent.ACTION_UP:
                if ((downX - event.getX()) > (rightView.getWidth() / 2)) {
                    smoothOpenMenu(); //自動滑出來
                } else {
                    smoothCloseMenu(); //自動滑進去
                    return false;
                }
                break;
        }
        //消費掉事件
        return true;
    }

    @Override
    public void computeScroll() {
        if (state == STATE_OPEN) {
            if (mOpenScroller.computeScrollOffset()) {
                swipe(mOpenScroller.getCurrX());
                postInvalidate();
            }
        } else {
            if (mCloseScroller.computeScrollOffset()) {
                swipe(mBaseX - mCloseScroller.getCurrX());
                postInvalidate();
            }
        }
    }

    private void swipe(int dis) {
        if (dis > rightView.getWidth()) {
            dis = rightView.getWidth();
        }
        if (dis < 0) {
            dis = 0;
        }
        centerView.layout(-dis, centerView.getTop(), centerView.getWidth() - dis, getMeasuredHeight());
        rightView.layout(centerView.getWidth() - dis, rightView.getTop(), centerView.getWidth() + rightView.getWidth() - dis, rightView.getBottom());
    }

    public void smoothCloseMenu() {
        state = STATE_CLOSE;
        mBaseX = -centerView.getLeft();
        mCloseScroller.startScroll(0, 0, mBaseX, 0, 350);
        postInvalidate();
    }
    public void smoothOpenMenu() {
        state = STATE_OPEN;
        mOpenScroller.startScroll(-centerView.getLeft(), 0, rightView.getWidth(), 0, 350);
        postInvalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if(rightView != null)
            rightView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if(centerView != null)
            centerView.layout(0, 0, getMeasuredWidth(), centerView.getMeasuredHeight());
        if(rightView != null)
            rightView.layout(getMeasuredWidth(), 0, getMeasuredWidth() + rightView.getMeasuredWidth(), centerView.getMeasuredHeight());
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        onSwipeLeft(event);
        return true;
    }
}

MainActivity部分:

private void initScrollerLayout() {

        View centerView1 = LayoutInflater.from(this).inflate(R.layout.view_framelayout_01, null);
        View rightView1 = LayoutInflater.from(this).inflate(R.layout.view_framelayout_02, null);
        ScrollerLinearLayout scrollerLinearLayout1 = (ScrollerLinearLayout)super.findViewById(R.id.activity_listview_scroller_layout01);
        scrollerLinearLayout1.setContentView(centerView1, rightView1);

        View centerView2 = LayoutInflater.from(this).inflate(R.layout.view_framelayout_01, null);
        View rightView2 = LayoutInflater.from(this).inflate(R.layout.view_framelayout_02, null);
        ScrollerLinearLayout scrollerLinearLayout2 = (ScrollerLinearLayout)super.findViewById(R.id.activity_listview_scroller_layout02);
        scrollerLinearLayout2.setContentView(centerView2, rightView2);
    }

XML部分:

view_framelayout_01

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:gravity="center_vertical"
    android:layout_height="match_parent">

    <com.best.android.listviewactivity.CircleImageView
        android:layout_width="50dp"
        android:layout_height="50dp" />

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">

        <RelativeLayout
            android:layout_margin="4dp"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content">

            <TextView
                android:text="android交流羣"
                android:textSize="14dp"
                android:layout_alignParentLeft="true"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

            <TextView
                android:text="15:34"
                android:layout_alignParentRight="true"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

        </RelativeLayout>

        <RelativeLayout
            android:layout_margin="4dp"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content">

            <TextView
                android:text="Emma加入羣"
                android:textColor="#666666"
                android:textSize="14dp"
                android:layout_alignParentLeft="true"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

            <TextView
                android:text="99+"
                android:gravity="center"
                android:textColor="#fff"
                android:background="@mipmap/green_bg"
                android:layout_alignParentRight="true"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

        </RelativeLayout>

    </LinearLayout>

</LinearLayout>
view_framelayout_02:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:background="#444444"
        android:gravity="center"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">

        <TextView
            android:text="置頂"
            android:textColor="#fff"
            android:textSize="16sp"
            android:layout_margin="20dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>

    <LinearLayout
        android:id="@+id/view_framelayout_02_ll_delete"
        android:background="#ee2222"
        android:gravity="center"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">

        <TextView
            android:text="刪除"
            android:textColor="#fff"
            android:textSize="16sp"
            android:layout_margin="20dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>

</LinearLayout>


基本側滑已經實現,但是不是我們最終想要的效果,所以接下來我們要加入listview

與listview關聯:

首先更改上面代碼的這個部分(將第一句取消註釋,第二句註釋掉)

//        setLayoutParams(new AbsListView.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
        setLayoutParams(new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
並將ScrollerLinearLayout裏面的ontouchevent函數去掉,因爲現在事件監聽將在listview裏面做判斷。


主要難點在自定義listview裏面的ontouchEvent方法 這裏詳細講裏面的邏輯。。。。。

首先梳理下思路: 在action.down的時候我們都需要做哪些呢?

1.記錄下按下的位置。

2.記錄下是listview裏面的哪個item

3.狀態要改變

4.獲取當前的item的view對象。

5.判斷item是否和上次點擊的item相同,如果不相同那麼就直接讓view關閉,如果相同那麼記錄下x座標

action.move的時候我們做的事情比較簡單,記錄下移動的距離,判斷是否是x方向的移動,是的話就改變滑動狀態,並直接調用onswipeleft()執行滑動。否則就是讓listview上下滾動了。

action.up,我們就需要判斷滑動是否執行到位,不到位就調用smoothOpen方法,將沒有滑出的部分滑出,沒有滑入的部分滑入。

自定義Listview ontouch部分代碼

@Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (ev.getAction() != MotionEvent.ACTION_DOWN && mTouchView == null)
            return super.onTouchEvent(ev);
        int action = MotionEventCompat.getActionMasked(ev);
//        int disY;
        action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                //按住的item的position
                int oldPos = mTouchPosition;
                //記錄位置
                mDownX = ev.getX();
                mDownY = ev.getY();
                mTouchState = TOUCH_STATE_NONE;
                //獲取點擊的item的position
                mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());

                //判斷當前點擊的是否和上次點擊的item是同一個,如果是同一個,並且狀態是打開了的就記錄狀態和座標
                //mTouchView.onSwipeLeft(ev);在down方法裏就是記錄X座標的
                if (mTouchPosition == oldPos && mTouchView != null && mTouchView.isOpen()) {
                    mTouchState = TOUCH_STATE_X;
                    mTouchView.onSwipeLeft(ev);
                    return true;
                }
                //獲取當前的item的View
                View view = getChildAt(mTouchPosition - getFirstVisiblePosition());

                //如果不是同一個item 那麼點擊的話就關閉掉之前打開的item
                if (mTouchView != null && mTouchView.isOpen()) {
                    mTouchView.smoothCloseMenu();
                    mTouchView = null;
                    return super.onTouchEvent(ev);
                }
                //判斷該view的類型
                if (view instanceof ScrollerLinearLayout) {
                    mTouchView = (ScrollerLinearLayout) view;
                }
                if (mTouchView != null) {
                    mTouchView.onSwipeLeft(ev);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                float dy = Math.abs((ev.getY() - mDownY));
                float dx = Math.abs((ev.getX() - mDownX));
                if (mTouchState == TOUCH_STATE_X) {
                    if (mTouchView != null) {
                        //執行滑動
                        mTouchView.onSwipeLeft(ev);
                    }
                    return true;
                } else if (mTouchState == TOUCH_STATE_NONE) {
                    //判斷滑動方向,x方向執行滑動,Y方向執行滾動
                    if (Math.abs(dy) > MAX_Y) {
                        mTouchState = TOUCH_STATE_Y;
                    } else if (dx > MAX_X) {
                        mTouchState = TOUCH_STATE_X;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mTouchState == TOUCH_STATE_X) {
                    if (mTouchView != null) {
                        mTouchView.onSwipeLeft(ev);
                        //如過最後狀態是打開 那麼就重新初始化
                        if (!mTouchView.isOpen()) {
                            mTouchPosition = -1;
                            mTouchView = null;
                        }
                    }
                    ev.setAction(MotionEvent.ACTION_CANCEL);
                    super.onTouchEvent(ev);
                    return true;
                }
                break;
        }
        return super.onTouchEvent(ev);
    }

listview     adapter部分代碼

@Override
    public View getView(int position, View convertView, ViewGroup parent) {

        if(convertView==null){
            View view01 = LayoutInflater.from(context).inflate(R.layout.view_framelayout_01, null);
            View view02 = LayoutInflater.from(context).inflate(R.layout.view_framelayout_02, null);

            LinearLayout layoutDelete = (LinearLayout)view02.findViewById(R.id.view_framelayout_02_ll_delete);
            layoutDelete.setOnClickListener(new OnDeleteClickListener(position));

//            convertView = new MyFrameLayout(view01, view02);
            ScrollerLinearLayout scrollerLinearLayout = new ScrollerLinearLayout(context);
            scrollerLinearLayout.setContentView(view01, view02);
            convertView = scrollerLinearLayout;
        }

        return convertView;
    

最後效果圖:



等下拉刷新功能完成後,會將源碼一起上傳上去,不過上面都已經將的很詳細了,應該都能實現開發功能了。。。。


最後總結下:其實這個思路不僅僅是實現listview的側滑,按照這個思路來gridview,recycleview都是能實現側滑效果的,大家多多舉一反三。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章