讓你的ListView更炫酷,實(shí)現(xiàn)側(cè)滑刪除效果

博文出處:讓你的ListView更炫酷,實(shí)現(xiàn)側(cè)滑刪除效果,歡迎大家關(guān)注我的博客,謝謝!

又到了更新博客的時(shí)間了,今天給大家?guī)?lái)的是ListView側(cè)滑出現(xiàn)刪除等按鈕的效果。相信大家在平時(shí)玩app的時(shí)候都接觸過(guò)這種效果吧。比如說(shuō)QQ聊天列表側(cè)滑就會(huì)出現(xiàn)“置頂”、“標(biāo)為已讀”、“刪除”等按鈕。這篇博文將用ViewDragHelper這個(gè)神器來(lái)實(shí)現(xiàn)側(cè)滑效果。友情鏈接一下之前寫(xiě)的博文使用ViewDragHelper來(lái)實(shí)現(xiàn)側(cè)滑菜單的,點(diǎn)擊此處跳轉(zhuǎn)。如果你對(duì)ViewDragHelper不熟悉,你可以去看看鴻洋_《Android ViewDragHelper完全解析 自定義ViewGroup神器》

好了,話(huà)說(shuō)的那么多,先來(lái)看看我們實(shí)現(xiàn)的效果圖吧:

側(cè)滑ListView效果圖.gif

可以看出來(lái),我們實(shí)現(xiàn)的和QQ的效果相差無(wú)幾。下面就是源碼時(shí)間了。

先來(lái)看一下ListView的item的slip_item_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<com.yuqirong.swipelistview.view.SwipeListLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/sll_main"
android:layout_width="match_parent"
android:layout_height="wrap_content" >

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="center_vertical"
        android:orientation="horizontal" >

        <TextView
            android:id="@+id/tv_top"
            android:layout_width="80dp"
            android:layout_height="match_parent"
            android:background="#66ff0000"
            android:gravity="center"
            android:text="置頂" />

        <TextView
            android:id="@+id/tv_delete"
            android:layout_width="80dp"
            android:layout_height="match_parent"
            android:background="#330000ff"
            android:gravity="center"
            android:text="刪除" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center_vertical"
        android:background="#66ffffff"
        android:orientation="horizontal" >

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_margin="10dp"
            android:src="@drawable/head_1" />

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_marginLeft="10dp"
            android:gravity="center_vertical"
            android:text="hello" />
    </LinearLayout>

</com.yuqirong.swipelistview.view.SwipeListLayout>

我們可以看出,要先把側(cè)滑出的按鈕布局放在SwipeListLayout的第一層,而item的布局放在第二層。還有一點(diǎn)要注意的是,側(cè)滑出的按鈕如果有兩個(gè)或兩個(gè)以上,那么必須用ViewGroup作為父布局。要整體保持SwipeListLayout的直接子View為2個(gè)。

而activity的布局文件里就是一個(gè)ListView,這里就不再給出了。

下面我們直接來(lái)看看SwipeListLayout的內(nèi)容:

public SwipeListLayout(Context context) {
    this(context, null);
}

public SwipeListLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    mDragHelper = ViewDragHelper.create(this, callback);
}

// ViewDragHelper的回調(diào)
Callback callback = new Callback() {

    @Override
    public boolean tryCaptureView(View view, int arg1) {
        return view == itemView;
    }

    @Override
    public int clampViewPositionHorizontal(View child, int left, int dx) {
        if (child == itemView) {
            if (left > 0) {
                return 0;
            } else {
                left = Math.max(left, -hiddenViewWidth);
                return left;
            }
        }
        return 0;
    }

    @Override
    public int getViewHorizontalDragRange(View child) {
        return hiddenViewWidth;
    }

    @Override
    public void onViewPositionChanged(View changedView, int left, int top,
            int dx, int dy) {
        if (itemView == changedView) {
            hiddenView.offsetLeftAndRight(dx);
        }
        // 有時(shí)候滑動(dòng)很快的話(huà) 會(huì)出現(xiàn)隱藏按鈕的linearlayout沒(méi)有繪制的問(wèn)題
        // 為了確保繪制成功 調(diào)用 invalidate
        invalidate();
    }

    public void onViewReleased(View releasedChild, float xvel, float yvel) {
        // 向右滑xvel為正 向左滑xvel為負(fù)
        if (releasedChild == itemView) {
            if (xvel == 0
                    && Math.abs(itemView.getLeft()) > hiddenViewWidth / 2.0f) {
                open(smooth);
            } else if (xvel < 0) {
                open(smooth);
            } else {
                close(smooth);
            }
        }
    }

};

我們主要來(lái)看callback,首先在tryCaptureView(View view, int arg1)里設(shè)置了只有是itemView的時(shí)候才能被捕獲,也就是說(shuō)當(dāng)你去滑動(dòng)“刪除”、“置頂”等按鈕的時(shí)候,側(cè)滑按鈕是不會(huì)被關(guān)閉的,因?yàn)楦揪蜎](méi)捕獲。(當(dāng)然你也可以設(shè)置都捕獲,那樣的話(huà)下面的邏輯要調(diào)整了),剩余的幾個(gè)函數(shù)中的邏輯較為簡(jiǎn)單,在onView Released(View releasedChild, float xvel, float yvel)也是判斷了當(dāng)手指抬起時(shí)itemView所處的位置。如果向左滑或者停止滑動(dòng)時(shí)按鈕已經(jīng)顯示出1/2的寬度,則打開(kāi);其余情況下都將關(guān)閉按鈕。

以下分別是close()和open()的方法:

/**
 * 側(cè)滑關(guān)閉
 * 
 * @param smooth
 *            為true則有平滑的過(guò)渡動(dòng)畫(huà)
 */
private void close(boolean smooth) {
    preStatus = status;
    status = Status.Close;
    if (smooth) {
        if (mDragHelper.smoothSlideViewTo(itemView, 0, 0)) {
            if (listener != null) {
                Log.i(TAG, "start close animation");
                listener.onStartCloseAnimation();
            }
            ViewCompat.postInvalidateOnAnimation(this);
        }
    } else {
        layout(status);
    }
    if (listener != null && preStatus == Status.Open) {
        Log.i(TAG, "close");
        listener.onStatusChanged(status);
    }
}

/**
 * 側(cè)滑打開(kāi)
 * 
 * @param smooth
 *            為true則有平滑的過(guò)渡動(dòng)畫(huà)
 */
private void open(boolean smooth) {
    preStatus = status;
    status = Status.Open;
    if (smooth) {
        if (mDragHelper.smoothSlideViewTo(itemView, -hiddenViewWidth, 0)) {
            if (listener != null) {
                Log.i(TAG, "start open animation");
                listener.onStartOpenAnimation();
            }
            ViewCompat.postInvalidateOnAnimation(this);
        }
    } else {
        layout(status);
    }
    if (listener != null && preStatus == Status.Close) {
        Log.i(TAG, "open");
        listener.onStatusChanged(status);
    }
}

SwipeListLayout大致的代碼就這些,相信對(duì)于熟悉ViewDragHelper的同學(xué)們來(lái)說(shuō)應(yīng)該是不成問(wèn)題的。其實(shí)整體的邏輯和之前用ViewDragHelper來(lái)實(shí)現(xiàn)側(cè)滑菜單大同小異。

順便下面貼出SwipeListLayout的全部代碼:

/**
 * 側(cè)滑Layout
 */
public class SwipeListLayout extends FrameLayout {

    private View hiddenView;
    private View itemView;
    private int hiddenViewWidth;
    private ViewDragHelper mDragHelper;
    private int hiddenViewHeight;
    private int itemWidth;
    private int itemHeight;
    private OnSwipeStatusListener listener;
    private Status status = Status.Close;
    private boolean smooth = true;

    public static final String TAG = "SlipListLayout";

    // 狀態(tài)
    public enum Status {
        Open, Close
    }

    /**
     * 設(shè)置側(cè)滑狀態(tài)
     * 
     * @param status
     *            狀態(tài) Open or Close
     * @param smooth
     *            若為true則有過(guò)渡動(dòng)畫(huà),否則沒(méi)有
     */
    public void setStatus(Status status, boolean smooth) {
        this.status = status;
        if (status == Status.Open) {
            open(smooth);
        } else {
            close(smooth);
        }
    }

    public void setOnSwipeStatusListener(OnSwipeStatusListener listener) {
        this.listener = listener;
    }

    /**
     * 是否設(shè)置過(guò)渡動(dòng)畫(huà)
     * 
     * @param smooth
     */
    public void setSmooth(boolean smooth) {
        this.smooth = smooth;
    }

    public interface OnSwipeStatusListener {

        /**
         * 當(dāng)狀態(tài)改變時(shí)回調(diào)
         * 
         * @param status
         */
        void onStatusChanged(Status status);

        /**
         * 開(kāi)始執(zhí)行Open動(dòng)畫(huà)
         */
        void onStartCloseAnimation();

        /**
         * 開(kāi)始執(zhí)行Close動(dòng)畫(huà)
         */
        void onStartOpenAnimation();

    }

    public SwipeListLayout(Context context) {
        this(context, null);
    }

    public SwipeListLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mDragHelper = ViewDragHelper.create(this, callback);
    }

    // ViewDragHelper的回調(diào)
    Callback callback = new Callback() {

        @Override
        public boolean tryCaptureView(View view, int arg1) {
            return view == itemView;
        }

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if (child == itemView) {
                if (left > 0) {
                    return 0;
                } else {
                    left = Math.max(left, -hiddenViewWidth);
                    return left;
                }
            }
            return 0;
        }

        @Override
        public int getViewHorizontalDragRange(View child) {
            return hiddenViewWidth;
        }

        @Override
        public void onViewPositionChanged(View changedView, int left, int top,
                int dx, int dy) {
            if (itemView == changedView) {
                hiddenView.offsetLeftAndRight(dx);
            }
            // 有時(shí)候滑動(dòng)很快的話(huà) 會(huì)出現(xiàn)隱藏按鈕的linearlayout沒(méi)有繪制的問(wèn)題
            // 為了確保繪制成功 調(diào)用 invalidate
            invalidate();
        }

        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            // 向右滑xvel為正 向左滑xvel為負(fù)
            if (releasedChild == itemView) {
                if (xvel == 0
                        && Math.abs(itemView.getLeft()) > hiddenViewWidth / 2.0f) {
                    open(smooth);
                } else if (xvel < 0) {
                    open(smooth);
                } else {
                    close(smooth);
                }
            }
        }

    };
    private Status preStatus = Status.Close;

    /**
     * 側(cè)滑關(guān)閉
     * 
     * @param smooth
     *            為true則有平滑的過(guò)渡動(dòng)畫(huà)
     */
    private void close(boolean smooth) {
        preStatus = status;
        status = Status.Close;
        if (smooth) {
            if (mDragHelper.smoothSlideViewTo(itemView, 0, 0)) {
                if (listener != null) {
                    Log.i(TAG, "start close animation");
                    listener.onStartCloseAnimation();
                }
                ViewCompat.postInvalidateOnAnimation(this);
            }
        } else {
            layout(status);
        }
        if (listener != null && preStatus == Status.Open) {
            Log.i(TAG, "close");
            listener.onStatusChanged(status);
        }
    }

    /**
     * 
     * @param status
     */
    private void layout(Status status) {
        if (status == Status.Close) {
            hiddenView.layout(itemWidth, 0, itemWidth + hiddenViewWidth,
                    itemHeight);
            itemView.layout(0, 0, itemWidth, itemHeight);
        } else {
            hiddenView.layout(itemWidth - hiddenViewWidth, 0, itemWidth,
                    itemHeight);
            itemView.layout(-hiddenViewWidth, 0, itemWidth - hiddenViewWidth,
                    itemHeight);
        }
    }

    /**
     * 側(cè)滑打開(kāi)
     * 
     * @param smooth
     *            為true則有平滑的過(guò)渡動(dòng)畫(huà)
     */
    private void open(boolean smooth) {
        preStatus = status;
        status = Status.Open;
        if (smooth) {
            if (mDragHelper.smoothSlideViewTo(itemView, -hiddenViewWidth, 0)) {
                if (listener != null) {
                    Log.i(TAG, "start open animation");
                    listener.onStartOpenAnimation();
                }
                ViewCompat.postInvalidateOnAnimation(this);
            }
        } else {
            layout(status);
        }
        if (listener != null && preStatus == Status.Close) {
            Log.i(TAG, "open");
            listener.onStatusChanged(status);
        }
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        // 開(kāi)始執(zhí)行動(dòng)畫(huà)
        if (mDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    // 讓ViewDragHelper來(lái)處理觸摸事件
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        if (action == MotionEvent.ACTION_CANCEL) {
            mDragHelper.cancel();
            return false;
        }
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }

    // 讓ViewDragHelper來(lái)處理觸摸事件
    public boolean onTouchEvent(MotionEvent event) {
        mDragHelper.processTouchEvent(event);
        return true;
    };

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        hiddenView = getChildAt(0); // 得到隱藏按鈕的linearlayout
        itemView = getChildAt(1); // 得到最上層的linearlayout
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 測(cè)量子View的長(zhǎng)和寬
        itemWidth = itemView.getMeasuredWidth();
        itemHeight = itemView.getMeasuredHeight();
        hiddenViewWidth = hiddenView.getMeasuredWidth();
        hiddenViewHeight = hiddenView.getMeasuredHeight();
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
            int bottom) {
        layout(Status.Close);
    }

}

最后,提供SwipeListLayout的源碼下載:

SwipeListView.rar

GitHub:

SwipeListView

~have fun!~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,055評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,365評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,561評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,346評(píng)論 6 404
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,889評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,118評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,637評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,558評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,739評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,980評(píng)論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,362評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,619評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,347評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,702評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容