博文出處:讓你的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)的效果圖吧:
可以看出來(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的源碼下載:
GitHub:
~have fun!~