概要
QQ消息列表侧滑删除在网上也有很多相关代码介绍,介绍的实现方式也各不一样,本文是将侧滑删除自定义为一个控件,这样使用就可以不仅仅限制在ListView中了。如果想理解本文逻辑首先得对ViewDragHelper有一定的了解,可以参考一下ViewDragHelper解析,在文章中对ViewDragHelper进行了简单的介绍并配有示例demo,通过本文也可以加深对ViewDragHelper理解。
本文最终效果图如下:
侧滑删除控件实现分析
有关自定义控件的学习笔记已经记录了四篇了,有兴趣可以查看以前的文章:
本文的自定义控件是要重写一个ViewGroup,在以前的笔记中还没有记录过关于自定义控件来布局一个ViewGroup,SwipeLayout通过继承FragmeLayout重写onLayout方法布局子View,事实上SwipeLayout布局重点就在于如何布局子View,而侧滑删除的操作重点在于ViewDragHelper的如何使用,通过上面的简单分析,重点就在这两个地方了。
onLayout布局子View
FrameLayout是帧布局,就是说布局的时候都是上下层级的关系,这种情况下就需要动态来布局了,侧滑操作初始时时看不到的,也就是说初始时放在内容布局的后面就可以了,同时我们可以侧滑的范围就是侧滑菜单的宽度。
SwipeLayout布局文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
<com.sunny.demo.view.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipeLayout"
android:layout_width="match_parent"
android:layout_height="60dp" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent" >
<TextView
android:layout_width="70dp"
android:background="#ccc"
android:layout_height="match_parent"
android:gravity="center"
android:textColor="#fff"
android:text="置顶" />
<TextView
android:id="@+id/tv_del"
android:layout_width="70dp"
android:background="#f00"
android:textColor="#fff"
android:layout_height="match_parent"
android:gravity="center"
android:clickable="true"
android:text="刪除" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical" >
<ImageView
android:id="@+id/imageView"
android:src="@drawable/kxg"
android:layout_width="50dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_height="50dp"/>
<TextView
android:id="@+id/textView"
android:textColor="#444"
android:textSize="18sp"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"/>
</LinearLayout>
</com.sunny.demo.view.SwipeLayout>
|
SwipeLayout布局代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
private ViewDragHelper dragHelper;
private View backView;//侧滑菜单
private View frontView;//内容区域
private int height;//自定义控件布局高
private int width;//自定义控件布局宽
private int range;//侧滑菜单可滑动范围
//重写三个构造方法
public SwipeLayout(Context context) {
this(context, null);
}
public SwipeLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SwipeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
dragHelper = ViewDragHelper.create(this, callback);
}
//获取两个View
protected void onFinishInflate() {
super.onFinishInflate();
int childCount = getChildCount();
if (childCount < 2) {
throw new IllegalStateException("you need 2 children view");
}
if (!(getChildAt(0) instanceof ViewGroup) || !(getChildAt(1) instanceof ViewGroup)) {
throw new IllegalArgumentException("your children must be instance of ViewGroup");
}
backView = getChildAt(0);//侧滑菜单
frontView = getChildAt(1);//内容区域
}
//初始化布局的高height宽width以及可滑动的范围range
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
height = frontView.getMeasuredHeight();
width = frontView.getMeasuredWidth();
range = backView.getMeasuredWidth();
}
//布局子View
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
layoutContent(false);
}
/**
* @param isOpen 侧滑菜单是否打开
*/
private void layoutContent(boolean isOpen) {
Rect frontRect = computeFrontViewRect(isOpen);
frontView.layout(frontRect.left, frontRect.top, frontRect.right, frontRect.bottom);
Rect backRect = computeBackViewRect(frontRect);
backView.layout(backRect.left, backRect.top, backRect.right, backRect.bottom);
//调整顺序
// bringChildToFront(frontView);
}
/**
* 通过内容区域所占矩形坐标计算侧滑菜单的矩形位置区域
* @param frontRect 内容区域所占矩形
* @return
*/
private Rect computeBackViewRect(Rect frontRect) {
int left = frontRect.right;
return new Rect(left, 0, left + range, height);
}
/**
* 通过菜单打开与否isOpen计算内容区域的矩形区
* @param isOpen
* @return
*/
private Rect computeFrontViewRect(boolean isOpen) {
int left = 0;
if (isOpen) {
left = -range;
}
return new Rect(left, 0, left + width, height);
}
|
侧滑操作ViewDragHelper的实现
ViewDragHelper解析我们已经分析过三步走,第一步在构造函数中已经做过了,接下来就是剩下的两步了,关键在于第三步。
接管touch事件
1
2
3
4
5
6
7
8
|
public boolean onInterceptTouchEvent(android.view.MotionEvent ev) {
return dragHelper.shouldInterceptTouchEvent(ev);
};
public boolean onTouchEvent(MotionEvent event) {
dragHelper.processTouchEvent(event);
return true;
}
|
自定义Callback
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
//所有子View都可拖拽
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
//水平拖拽后处理
public int clampViewPositionHorizontal(View child, int left, int dx) {
if (child == frontView) {
if (left > 0) {
return 0;
} else if (left < -range) {
return -range;
}
} else if (child == backView) {
if (left > width) {
return width;
} else if (left < width - range) {
return width - range;
}
}
return left;
}
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
if (changedView == frontView) {
backView.offsetLeftAndRight(dx);
} else if (changedView == backView) {
frontView.offsetLeftAndRight(dx);
}
//事件派发
dispatchSwipeEvent();
//兼容低版本
invalidate();
};
//松手后根据侧滑位移确定菜单打开与否
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (xvel == 0 && frontView.getLeft() < -range * 0.5f) {
open();
} else if (xvel < 0) {
open();
} else {
close();
}
};
//子View如果是clickable,必须重写的方法
public int getViewHorizontalDragRange(View child) {
return 1;
}
public int getViewVerticalDragRange(View child) {
return 1;
}
};
|
拖拽伴随动画操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
// 持续平滑动画 高频调用
public void computeScroll() {
// 如果返回true,动画还需要继续
if (dragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
};
public void open() {
open(true);
}
public void open(boolean isSmooth) {
int finalLeft = -range;
if (isSmooth) {
if (dragHelper.smoothSlideViewTo(frontView, finalLeft, 0)) {
ViewCompat.postInvalidateOnAnimation(this);
}
} else {
layoutContent(true);
}
}
public void close() {
close(true);
}
public void close(boolean isSmooth) {
int finalLeft = 0;
if (isSmooth) {
if (dragHelper.smoothSlideViewTo(frontView, finalLeft, 0)) {
ViewCompat.postInvalidateOnAnimation(this);
}
} else {
layoutContent(false);
}
}
|
拖拽状态以及事件回调
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
private Status status=Status.CLOSE;//拖拽状态 默认关闭
public static enum Status {
OPEN, CLOSE, DRAGING
}
//拖拽事件监听器
public static interface OnSwipeChangeListener {
void onDraging(SwipeLayout mSwipeLayout);
void onOpen(SwipeLayout mSwipeLayout);
void onClose(SwipeLayout mSwipeLayout);
void onStartOpen(SwipeLayout mSwipeLayout);
void onStartClose(SwipeLayout mSwipeLayout);
}
//更改状态
private Status updateStatus() {
int left=frontView.getLeft();
if(left==0){
return Status.CLOSE;
}else if(left==-range){
return Status.OPEN;
}
return Status.DRAGING;
}
//根据当前状态判断回调事件
protected void dispatchSwipeEvent() {
Status preStatus=status;
status=updateStatus();
if(swipeChangeListener!=null){
swipeChangeListener.onDraging(this);
}
if(preStatus!=status&&swipeChangeListener!=null){
if(status==Status.CLOSE){
swipeChangeListener.onClose(this);
}else if(status==Status.OPEN){
swipeChangeListener.onOpen(this);
}else if(status==Status.DRAGING){
if(preStatus==Status.CLOSE){
swipeChangeListener.onStartOpen(this);
}else if(preStatus==Status.OPEN){
swipeChangeListener.onStartClose(this);
}
}
}
}
|
小结
在一个ListView列表中当我们侧滑出一个菜单后需要其余的已经打开的菜单全部关闭,这时候我们可以将所有的打开的SwipeLayout放入一个List中,每次打开后将该SwipeLayout是存入List中,当我们关闭后再把SwipeLayout从List中移除掉,详细的代码就不再贴出来了,可以下载示例源代码查看。