Android 中解决滑动的方案有2种:外部拦截法 和内部拦截法。
滑动冲突也存在2种场景: 横竖滑动冲突、同向滑动冲突。
所以我就写了4个例子来学习如何解决滑动冲突的,这四个例子分别为: 外部拦截法解决横竖冲突、外部拦截法解决同向冲突、内部拦截法解决横竖冲突、内部拦截法解决同向冲突。
先上效果图:
二、实战
1、外部拦截法,解决横竖冲突
思路是,重写父控件的onInterceptTouchEvent方法,然后根据具体的需求,来决定父控件是否拦截事件。如果拦截返回返回true,不拦截返回false。关于为什么返回true就代表拦截事件 。 如果父控件拦截了事件,则在父控件的onTouchEvent进行相应的事件处理。
我的这个例子,是一个横向滑动的ViewGroup里面包含了3个竖向滑动的ListView。下面我附上代码:
HorizontalEx.java
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
* Created by blueberry on 2016/6/20.
*
* 解决交错的滑动冲突
*
* 外部拦截法
*/
public
class
HorizontalEx
extends
ViewGroup {
private
static
final
String TAG =
"HorizontalEx"
;
private
boolean
isFirstTouch =
true
;
private
int
childIndex;
private
int
childCount;
private
int
lastXIntercept, lastYIntercept, lastX, lastY;
private
Scroller mScroller;
private
VelocityTracker mVelocityTracker;
public
HorizontalEx(Context context) {
super
(context);
init();
}
public
HorizontalEx(Context context, AttributeSet attrs) {
super
(context, attrs);
init();
}
public
HorizontalEx(Context context, AttributeSet attrs,
int
defStyleAttr) {
super
(context, attrs, defStyleAttr);
init();
}
private
void
init() {
mScroller =
new
Scroller(getContext());
mVelocityTracker = VelocityTracker.obtain();
}
@Override
protected
void
onMeasure(
int
widthMeasureSpec,
int
heightMeasureSpec) {
int
width = MeasureSpec.getSize(widthMeasureSpec);
int
height = MeasureSpec.getSize(heightMeasureSpec);
int
widthMode = MeasureSpec.getMode(widthMeasureSpec);
int
heightMode = MeasureSpec.getMode(heightMeasureSpec);
childCount = getChildCount();
measureChildren(widthMeasureSpec, heightMeasureSpec);
if
(childCount ==
0
) {
setMeasuredDimension(
0
,
0
);
}
else
if
(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
width = childCount * getChildAt(
0
).getMeasuredWidth();
height = getChildAt(
0
).getMeasuredHeight();
setMeasuredDimension(width, height);
}
else
if
(widthMode == MeasureSpec.AT_MOST) {
width = childCount * getChildAt(
0
).getMeasuredWidth();
setMeasuredDimension(width, height);
}
else
{
height = getChildAt(
0
).getMeasuredHeight();
setMeasuredDimension(width, height);
}
}
@Override
protected
void
onLayout(
boolean
changed,
int
l,
int
t,
int
r,
int
b) {
int
left =
0
;
for
(
int
i =
0
; i < getChildCount(); i++) {
final
View child = getChildAt(i);
child.layout(left + l, t, r + left, b);
left += child.getMeasuredWidth();
}
}
/**
* 拦截事件
* @param ev
* @return
*/
@Override
public
boolean
onInterceptTouchEvent(MotionEvent ev) {
boolean
intercepted =
false
;
int
x = (
int
) ev.getX();
int
y = (
int
) ev.getY();
switch
(ev.getAction()) {
/*如果拦截了Down事件,则子类不会拿到这个事件序列*/
case
MotionEvent.ACTION_DOWN:
lastXIntercept = x;
lastYIntercept = y;
intercepted =
false
;
if
(!mScroller.isFinished()) {
mScroller.abortAnimation();
intercepted =
true
;
}
break
;
case
MotionEvent.ACTION_MOVE:
final
int
deltaX = x - lastXIntercept;
final
int
deltaY = y - lastYIntercept;
/*根据条件判断是否拦截该事件*/
if
(Math.abs(deltaX) > Math.abs(deltaY)) {
intercepted =
true
;
}
else
{
intercepted =
false
;
}
break
;
case
MotionEvent.ACTION_UP:
intercepted =
false
;
break
;
}
lastXIntercept = x;
lastYIntercept = y;
return
intercepted;
}
@Override
public
boolean
onTouchEvent(MotionEvent event) {
int
x = (
int
) event.getX();
int
y = (
int
) event.getY();
mVelocityTracker.addMovement(event);
ViewConfiguration configuration = ViewConfiguration.get(getContext());
switch
(event.getAction()) {
case
MotionEvent.ACTION_DOWN:
if
(!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break
;
case
MotionEvent.ACTION_MOVE:
/*因为这里父控件拿不到Down事件,所以使用一个布尔值,
当事件第一次来到父控件时,对lastX,lastY赋值*/
if
(isFirstTouch) {
lastX = x;
lastY = y;
isFirstTouch =
false
;
}
final
int
deltaX = x - lastX;
scrollBy(-deltaX,
0
);
break
;
case
MotionEvent.ACTION_UP:
int
scrollX = getScrollX();
final
int
childWidth = getChildAt(
0
).getWidth();
mVelocityTracker.computeCurrentVelocity(
1000
, configuration.getScaledMaximumFlingVelocity());
float
xVelocity = mVelocityTracker.getXVelocity();
if
(Math.abs(xVelocity) > configuration.getScaledMinimumFlingVelocity()) {
childIndex = xVelocity <
0
? childIndex +
1
: childIndex -
1
;
}
else
{
childIndex = (scrollX + childWidth /
2
) / childWidth;
}
childIndex = Math.min(getChildCount() -
1
, Math.max(childIndex,
0
));
smoothScrollBy(childIndex * childWidth - scrollX,
0
);
mVelocityTracker.clear();
isFirstTouch =
true
;
break
;
}
lastX = x;
lastY = y;
return
true
;
}
void
smoothScrollBy(
int
dx,
int
dy) {
mScroller.startScroll(getScrollX(), getScrollY(), dx, dy,
500
);
invalidate();
}
@Override
public
void
computeScroll() {
if
(mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
@Override
protected
void
onDetachedFromWindow() {
super
.onDetachedFromWindow();
mVelocityTracker.recycle();
}
}
|
调用代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<code
class
=
"hljs lasso"
>
@Override
public
void
showOutHVData(List<string> data1, List<string> data2, List<string> data3) {
ListView listView1 =
new
ListView(getContext());
ArrayAdapter<string> adapter1 =
new
ArrayAdapter<string>(getContext(), android.R.layout.simple_list_item_1, data1);
listView1.setAdapter(adapter1);
ListView listView2 =
new
ListView(getContext());
ArrayAdapter<string> adapter2 =
new
ArrayAdapter<string>(getContext(), android.R.layout.simple_list_item_1, data2);
listView2.setAdapter(adapter2);
ListView listView3 =
new
ListView(getContext());
ArrayAdapter<string> adapter3 =
new
ArrayAdapter<string>(getContext(), android.R.layout.simple_list_item_1, data3);
listView3.setAdapter(adapter3);
ViewGroup.LayoutParams params
=
new
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mHorizontalEx.addView(listView1, params);
mHorizontalEx.addView(listView2, params);
mHorizontalEx.addView(listView3, params);
}
|
其实外部拦截的主要思想都在于对onInterceptTouchEvent的重写。
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
|
<code
class
=
"hljs java"
>
@Override
public
boolean
onInterceptTouchEvent(MotionEvent ev) {
boolean
intercepted =
false
;
int
x = (
int
) ev.getX();
int
y = (
int
) ev.getY();
switch
(ev.getAction()) {
/*如果拦截了Down事件,则子类不会拿到这个事件序列*/
case
MotionEvent.ACTION_DOWN:
lastXIntercept = x;
lastYIntercept = y;
intercepted =
false
;
if
(!mScroller.isFinished()) {
mScroller.abortAnimation();
intercepted =
true
;
}
break
;
case
MotionEvent.ACTION_MOVE:
final
int
deltaX = x - lastXIntercept;
final
int
deltaY = y - lastYIntercept;
/*根据条件判断是否拦截该事件*/
if
(Math.abs(deltaX) > Math.abs(deltaY)) {
intercepted =
true
;
}
else
{
intercepted =
false
;
}
break
;
case
MotionEvent.ACTION_UP:
intercepted =
false
;
break
;
}
lastXIntercept = x;
lastYIntercept = y;
return
intercepted;
}</code>
|
这几乎是一个实现外部拦截事件的模板,这里一定不要在ACTION_DOWN 中返回 true,否则会让子VIew没有机会得到事件,因为如果在ACTION_DOWN的时候返回了 true,同一个事件序列ViewGroup的disPatchTouchEvent就不会在调用onInterceptTouchEvent方法了,如果不明白可以参考我的上一遍文章。
还有就是 在ACTION_UP中返回false,因为如果父控件拦截了ACTION_UP,那么子View将得不到UP事件,那么将会影响子View的 Onclick方法等。但这对父控件是没有影响的,因为如果是父控件子ACITON_MOVE中 就拦截了事件,他们UP事件必定也会交给它处理,因为有那么一条定律叫做:父控件一但拦截了事件,那么同一个事件序列的所有事件都将交给他处理。这条结论在我的上一篇文章中已经分析过。
最后就是在 ACTION_MOVE中根据需求决定是否拦截。
2、内部拦截法,解决横竖冲突
内部拦截主要依赖于父控件的 requestDisallowInterceptTouchEvent方法,关于这个方法我的上篇文章其实已经分析过。他设置父控件的一个标志(FLAG_DISALLOW_INTERCEPT)
这个标志可以决定父控件是否拦截事件,如果设置了这个标志则不拦截,如果没设这个标志,它就会调用父控件的onInterceptTouchEvent()来询问父控件是否拦截。但这个标志对Down事件无效。
可以参考一下源码:
ViewGroup#dispatchTouchEvent
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
|
<code
class
=
"hljs coffeescript"
>
// Handle an initial down.
if
(actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
//清楚标志
resetTouchState();
}
// Check for interception.
final
boolean
intercepted;
if
(actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget !=
null
) {
//标志
final
boolean
disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) !=
0
;
if
(!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
// restore action in case it was changed
}
else
{
intercepted =
false
;
}
}
else
{
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted =
true
;
}
</code>
|
那么我们如果想使用 内部拦截法拦截事件。
第一步:
a、我们要重写父控件的onInterceptTouchEvent,在ACTION_DOWN的时候返回false,负责的话子View调用requestDisallowInterceptTouchEvent也将无能为力。
b、还有就是其他事件的话都返回true,这样就把能否拦截事件的权利交给了子View。
第二步:
在子View的dispatchTouchEvent中 来决定是否让父控件拦截事件。
a. 先要在MotionEvent.ACTION_DOWN:的时候使用mHorizontalEx2.requestDisallowInterceptTouchEvent(true);,负责的话,下一个事件到来时,就交给父控件了。
b. 然后在MotionEvent.ACTION_MOVE: 根据业务逻辑决定是否调用mHorizontalEx2.requestDisallowInterceptTouchEvent(false);来决定父控件是否拦截事件。
上代码:
HorizontalEx2.java
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
/**
* Created by blueberry on 2016/6/20.
* <p/>
* 内部拦截
* 和 ListViewEx配合使用
*/
public
class
HorizontalEx2
extends
ViewGroup {
private
int
lastX, lastY;
private
int
childIndex;
private
Scroller mScroller;
private
VelocityTracker mVelocityTracker;
public
HorizontalEx2(Context context) {
super
(context);
init();
}
public
HorizontalEx2(Context context, AttributeSet attrs) {
super
(context, attrs);
init();
}
public
HorizontalEx2(Context context, AttributeSet attrs,
int
defStyleAttr) {
super
(context, attrs, defStyleAttr);
init();
}
private
void
init() {
mScroller =
new
Scroller(getContext());
mVelocityTracker = VelocityTracker.obtain();
}
@Override
protected
void
onMeasure(
int
widthMeasureSpec,
int
heightMeasureSpec) {
int
width = MeasureSpec.getSize(widthMeasureSpec);
int
widthMode = MeasureSpec.getMode(widthMeasureSpec);
int
height = MeasureSpec.getSize(heightMeasureSpec);
int
heightMode = MeasureSpec.getMode(heightMeasureSpec);
int
childCount = getChildCount();
measureChildren(widthMeasureSpec, heightMeasureSpec);
if
(childCount ==
0
) {
setMeasuredDimension(
0
,
0
);
}
else
if
(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
height = getChildAt(
0
).getMeasuredHeight();
width = childCount * getChildAt(
0
).getMeasuredWidth();
setMeasuredDimension(width, height);
}
else
if
(widthMode == MeasureSpec.AT_MOST) {
width = childCount * getChildAt(
0
).getMeasuredWidth();
setMeasuredDimension(width, height);
}
else
{
height = getChildAt(
0
).getMeasuredHeight();
setMeasuredDimension(width, height);
}
}
@Override
protected
void
onLayout(
boolean
changed,
int
l,
int
t,
int
r,
int
b) {
int
leftOffset =
0
;
for
(
int
i =
0
; i < getChildCount(); i++) {
View child = getChildAt(i);
child.layout(l + leftOffset, t, r + leftOffset, b);
leftOffset += child.getMeasuredWidth();
}
}
/**
* 不拦截Down事件,其他一律拦截
* @param ev
* @return
*/
@Override
public
boolean
onInterceptTouchEvent(MotionEvent ev) {
if
(ev.getAction() == MotionEvent.ACTION_DOWN) {
if
(!mScroller.isFinished()) {
mScroller.abortAnimation();
return
true
;
}
return
false
;
}
else
{
return
true
;
}
}
private
boolean
isFirstTouch =
true
;
@Override
public
boolean
onTouchEvent(MotionEvent event) {
int
x = (
int
) event.getX();
int
y = (
int
) event.getY();
mVelocityTracker.addMovement(event);
ViewConfiguration configuration = ViewConfiguration.get(getContext());
switch
(event.getAction()) {
case
MotionEvent.ACTION_DOWN:
if
(!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break
;
case
MotionEvent.ACTION_MOVE:
if
(isFirstTouch) {
isFirstTouch =
false
;
lastY = y;
lastX = x;
}
final
int
deltaX = x - lastX;
scrollBy(-deltaX,
0
);
break
;
case
MotionEvent.ACTION_UP:
isFirstTouch =
true
;
int
scrollX = getScrollX();
mVelocityTracker.computeCurrentVelocity(
1000
, configuration.getScaledMaximumFlingVelocity());
float
mVelocityX = mVelocityTracker.getXVelocity();
if
(Math.abs(mVelocityX) > configuration.getScaledMinimumFlingVelocity()) {
childIndex = mVelocityX <
0
? childIndex +
1
: childIndex -
1
;
}
else
{
childIndex = (scrollX + getChildAt(
0
).getWidth() /
2
) / getChildAt(
0
).getWidth();
}
childIndex = Math.min(getChildCount() -
1
, Math.max(
0
, childIndex));
smoothScrollBy(childIndex*getChildAt(
0
).getWidth()-scrollX,
0
);
mVelocityTracker.clear();
break
;
}
lastX = x;
lastY = y;
return
true
;
}
private
void
smoothScrollBy(
int
dx,
int
dy) {
mScroller.startScroll(getScrollX(), getScrollY(), dx, dy,
500
);
invalidate();
}
@Override
public
void
computeScroll() {
if
(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
postInvalidate();
}
}
@Override
protected
void
onDetachedFromWindow() {
super
.onDetachedFromWindow();
mVelocityTracker.recycle();
}
}
|
ListViewEx.java
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
|
<code
class
=
"hljs java"
>
/**
* Created by blueberry on 2016/6/20.
* 内部拦截事件
*/
public
class
ListViewEx
extends
ListView {
private
int
lastXIntercepted, lastYIntercepted;
private
HorizontalEx2 mHorizontalEx2;
public
ListViewEx(Context context) {
super
(context);
}
public
ListViewEx(Context context, AttributeSet attrs) {
super
(context, attrs);
}
public
ListViewEx(Context context, AttributeSet attrs,
int
defStyleAttr) {
super
(context, attrs, defStyleAttr);
}
public
HorizontalEx2 getmHorizontalEx2() {
return
mHorizontalEx2;
}
public
void
setmHorizontalEx2(HorizontalEx2 mHorizontalEx2) {
this
.mHorizontalEx2 = mHorizontalEx2;
}
/**
* 使用 outter.requestDisallowInterceptTouchEvent();
* 来决定父控件是否对事件进行拦截
* @param ev
* @return
*/
@Override
public
boolean
dispatchTouchEvent(MotionEvent ev) {
int
x = (
int
) ev.getX();
int
y = (
int
) ev.getY();
switch
(ev.getAction()) {
case
MotionEvent.ACTION_DOWN:
mHorizontalEx2.requestDisallowInterceptTouchEvent(
true
);
break
;
case
MotionEvent.ACTION_MOVE:
final
int
deltaX = x-lastYIntercepted;
final
int
deltaY = y-lastYIntercepted;
if
(Math.abs(deltaX)>Math.abs(deltaY)){
mHorizontalEx2.requestDisallowInterceptTouchEvent(
false
);
}
break
;
case
MotionEvent.ACTION_UP:
break
;
}
lastXIntercepted = x;
lastYIntercepted = y;
return
super
.dispatchTouchEvent(ev);
}
}
</code>
|
调用代码:
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
|
<code
class
=
"hljs lasso"
>
@Override
public
void
showInnerHVData(List<string> data1, List<string> data2, List<string> data3) {
ListViewEx listView1 =
new
ListViewEx(getContext());
ArrayAdapter<string> adapter1 =
new
ArrayAdapter<string>(getContext(), android.R.layout.simple_list_item_1, data1);
listView1.setAdapter(adapter1);
listView1.setmHorizontalEx2(mHorizontalEx2);
ListViewEx listView2 =
new
ListViewEx(getContext());
ArrayAdapter<string> adapter2 =
new
ArrayAdapter<string>(getContext(), android.R.layout.simple_list_item_1, data2);
listView2.setAdapter(adapter2);
listView2.setmHorizontalEx2(mHorizontalEx2);
ListViewEx listView3 =
new
ListViewEx(getContext());
ArrayAdapter<string> adapter3 =
new
ArrayAdapter<string>(getContext(), android.R.layout.simple_list_item_1, data3);
listView3.setAdapter(adapter3);
listView3.setmHorizontalEx2(mHorizontalEx2);
ViewGroup.LayoutParams params
=
new
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mHorizontalEx2.addView(listView1, params);
mHorizontalEx2.addView(listView2, params);
mHorizontalEx2.addView(listView3, params);
}
|