现阶段你以为过不去的事,天大的事,当过去以后就会发现那只是屁大点事。实际上,也真的只是屁大点事儿。所有发生的都是应当发生的,无论是对是错。你需要为自己所做的任何决定和选择进行承担和负责。你需要做的是不断充实自己,做自己喜欢做的事情,让自己忙碌起来。你要感谢所有的人,所有人的到来,都是为了增加你感情的丰富程度,教会你如何处世如何为人,是他们让你波澜不惊的生活起了波澜,给你的生活增添了色彩,你会感激你所遇到的每一个人。你会发现你自身存在的问题,然后慢慢去修正。人生太丰富多彩了,你必须去接触新鲜的事物,在老的时候才可以回忆。任何一种感情最多只能占自己的百分之十,可以调整所占的幅度,但是都不能当成人生的全部。你要提高其它事情在你生命中所占的比重。
我以前觉得咖啡很苦,而且对身体不好,坚持了很久没有喝。但是有人再次请我喝的时候,我突然发现也没有那么苦。原来感觉会随着时间慢慢地改变。咖啡对身体有好也有坏,原来认知也在慢慢地改变。
我以前特别喜欢吃冰的东西,我所有的东西都是冰的。包括冬天喝的水都是放在冰箱的,最多是放到冰箱外让它变成常温。我冬天还经常吃雪糕,而且一吃就是好几块儿。但是,现在,我也开始尝试喝暖暖的东西,因为对身体好。原来,以前坚持的现在不一定会坚持。
但是,我还是点了一杯红茶,而且是热饮。
我不知道星巴克的咖啡怎么样,我的味觉很迟钝,我喝不出来,对我来说都一样。但是我觉得星巴克的学习氛围很好,也很让人放松。我今天感觉阳光格外地灿烂,天气格外地好,过得很充实。
我突然很喜欢很喜欢现在的自己。我不知道我过去是怎么了,是一个什么样的人,做了什么事。
我只知道我一直在迷茫,一直在痛苦,一直在沉默,一直在排斥,一直在厌恶着周围的一切,一直在自我封闭,一直在找寻自己,一直在问自己是谁?一直在问自己想做什么?到底要做什么?想成为一个什么样的人?到底打算去哪儿?到底想去哪儿?到底该跟别人说什么?到底如何说?到底应该如何与别人相处?
我不知道我伤害了多少人?我不知道我影响了多少人?我不知道我使多少人感到厌烦?我不知道我让多少人感到为难?我不知道多少人觉得我是个麻烦?我不知道我到底让多少人感到不知所措?
但是,我现在很感谢那些人,感谢那些经过我生命的人,感谢自己经历的一切。
我也分不清谁对谁错,反正只要存在问题,那么所有的人都有错。但是,也许我自身的问题更大吧。也许,全部是我的问题。
感谢别人所做的一切努力,不管是对的还是错的,好的还是坏的。
感谢自己所做的努力,感谢自己的挣扎,感谢自己的控制,感谢自己的忍耐,感谢我所做的一切,不管是对的还是错的。
对的是经验,错的是教训。
但是,无论如何,我都感谢,也全部接受。
感谢所有的一切,让我成为一个更好的人!
我现在知道了,我想做一个开心、快乐的人!
动冲突
首先讲解一下什么是滑动冲突。当你需要在一个ScrollView中嵌套使用ListView或者RecyclerView的时候你会发现只有ScrollView能够滑动,而ListView或RecyclerView不能滑动,这个就违背了我们写这段代码的意愿。我们想要的结果是当我们滑动ListView的时候ListView滑动,滑动ListView以外的地方的时候ScrollView滑动。这时候滑动冲突就产生了,我们需要想办法解决这个冲突。
你可以在这里看到这个引文的demo:https://github.com/onlynight/SlidingConfict
View Touch事件分发
首先我们了解下Android的控件组织结构。View是显示组件的基类,ViewGroup继承自View是布局的基类。ViewGroup中可包含View和ViewGroup,这样就形成了View树。View的Touch事件总是从View根节点开始向下传递的,根据点击的位置判断该传递给哪个子View,直到子节点再没有子节点这时候,如果这个事件被该View消耗那么事件的传递就此结束,如果该View没有使用这个事件那么这个事件会依次向上传递直到有View消耗了这个事件,如果没有View消耗这个事件,那么该事件就会被传递给Activity处理。以上就是Vieww Touch事件传递的过程。
我们来看View的dispatchEvent方法:
- 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
从这段代码我们可以看出OnTouchListener的优先级高于onTouchEvent。
下面我们再来看看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
- 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
可以看到ViewGroup在处理事件前有一个touch事件是否被拦截onInterceptTouchEvent的判断,如果被拦截则不再向下一级分发;如果没有拦截则向下分发,处理方式会根据ViewGroup中是否包含子元素来判断,如果包含子元素则将事件交由子元素处理touch事件handled = child.dispatchTouchEvent(event);
,如果不包含子元素则由自身处理handled = child.dispatchTouchEvent(event);
处理流程和View相同。
实线箭头为touch事件正向传递,虚线为向上传递touch事件。
通过上面的分发的逻辑我们可以知道父控件有能力把事件不传递给子View,从而不让子控件接收Touch事件,那么子控件有没有能力让父控件失去响应Touch事件的能力呢,下面我们来看看具体的源码,看源码的顺序是由下而上的,这回我们反其道而行,我们知道事件的入口然后依次向下找。
Activity分发事件到ViewGroup
根据上面的图我们知道View的touch事件是由Activity传递过来的,那么我们先看看Activity有没有类似的方法,正如我们所料,Activity的dispatchTouchEvent函数如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
显而易见我们要看的是getWindow().superDispatchTouchEvent(ev)
,我们深入进去看到Window类中的这个方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
Window类是个抽象类,它的唯一实现类是PhoneWindow,PhoneWindow类中的实现如下:
- 1
- 2
- 3
- 4
- 5
mDecor是DecorView,我们看看这个DectorView是从哪里来的:
- 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
这里我们就看出了mDecorView中包含了mContentParent,并且DecorView继承自FramLayout,所以touch事件的分发也符合View的事件分发,mDecorView之后会添加到Activity关联的Window上(这里我们不再深究),下面我们来看DecorView的superDispatchTouchEvent:
- 1
- 2
- 3
- 4
至此,activity的dispatchTouchEvent方法就最终分发到了我们的布局上,最后总结一下:
- 1
- 2
解决滑动冲突的原理
看了上面的源码解析,我们知道Viewtouch事件分发过程中重要的三个函数:
dispatchTouchEvent
负责touch事件的分发onInterceptTouchEvent
负责拦截touch事件onTouchEvent
最终处理touch事件
其中dispatchTouchEvent和onInterceptTouchEvent可以控制touch事件流不传递给子控件,这两个方法中可以控制事件流的向下分发,那么是不是有方法控制事件流向上分发呢?我们找到ViewGroup中有这样一个函数:
- 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
实际上requestDisallowInterceptTouchEvent是修改了disallowIntercept的状态,再结合ViewGroup的dispatchTouchEvent方法查看,我们就明白这个方法的最终意义。ViewGroup的子元素可以通过调用这个方法禁止ViewGroup拦截touch事件。到这里我们就找到了自下而上的touch事件的拦截方法。
滑动冲突两种解决办法
1. 外部拦截法
通过上面的原理分析我们知道我们可以在dispatchTouchEvent的时候不分发事件或者onInterceptTouchEvent时候拦截事件,实际上onInterceptTouchEvent方法是一个空方法,是android专门提供给我们处理touch事件拦截的方法,所以这里我们在onInterceptTouchEvent方法中拦截touch事件。
具体做法就是当你不想把事件传递给子控件的时候在onInterceptTouchEvent方法中返回true即可拦截事件,这时候子控件将不会再接收到这一次的touch事件流(所谓touch事件流是以ACTION_DOWN开始,中间包含若干个ACTION_MOVE,以ACTION_UP结束的一连串事件)。伪代码如下:
- 1
- 2
- 3
- 4
- 5
- 6
这里的condition将会再下一章节中具体讲解。
2. 内部拦截法
首先,我们让父控件拦截除了ACTION_DOWN以外的所有事件,如果连ACTION_DOWN都拦截那么子控件将无法收到任何touch事件:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
然后,在控件的内部分发事件的时候请求需要的事件(实际上就是禁止父控件拦截事件):
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
这样,就可以解决touch事件的冲突问题,从控件本身解决。内部拦截法使用起来稍显复杂,需要修改两个控件,一般情况下我们都通过外部拦截法解决滑动冲突,如果有特殊情况需要使用内部拦截法才会使用内部拦截法。
事件拦截Condition
试想以下情况:
MapView的功能是内部可以任意滑动(包括上下,左右以及任意方向滑动),ScrollView需要上下滑动。这时候我们在MapView内部上下滑动时会出现什么结果?我们期望的结果是MapView内部滑动,但是我们看到的实际情况却是ScrollView在上下滑动,滑动冲突就产生了,解决这个滑动冲突的方法很简单,直接上代码:
- 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
isMapViewTouched这个函数就是我们这个情况下的condition,具体的含义就是当前点击的是MapView那么所有的touch事件都不允许拦截,交由MapView处理。
这是一种很简单的滑动冲突情况,没有判断滑动的方向以及速度等因素,一般的我们通过判断滑动的方向作为判断条件,下面我们再来看一种情况:
ViewPager需要左右滑动,ListView需要上下滑动,当我们斜向滑动时就出现了滑动冲突。实际上ViewPage已经解决了这种滑动冲突,这里我们假定它没有解决这种滑动冲突,我们自己来解决这个滑动冲突。当我们斜向滑动时候示意图如下:
当我们从start滑动到end时,x方向的坐标变化我们称之为dx,y方向的坐标变化我们称之为dy。
- 当dx > dy时我们视其为水平滑动
- 当dx < dy时我们视其为竖直滑动
通过外部拦截法的代码如下:
- 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
滑动冲突解决拓展
滑动冲突的解决方法我们已经知道了,以后无论遇到多么复杂的情况解决滑动冲突的原则都是不变的,根据你的业务需求进行不同的事件拦截即可。
Touch事件的来源深入篇
如果你想知道Activity中的Touch事件是从哪来的,你可以查看任玉刚大神的这篇文章:http://blog.csdn.net/singwhatiwanna/article/details/50775201
三、
android多种滑动冲突的解决方案
一、前言
Android 中解决滑动的方案有2种:外部拦截法 和内部拦截法。
滑动冲突也存在2种场景: 横竖滑动冲突、同向滑动冲突。
所以我就写了4个例子来学习如何解决滑动冲突的,这四个例子分别为: 外部拦截法解决横竖冲突、外部拦截法解决同向冲突、内部拦截法解决横竖冲突、内部拦截法解决同向冲突。
先上效果图:
二、实战
1、外部拦截法,解决横竖冲突
思路是,重写父控件的onInterceptTouchEvent方法,然后根据具体的需求,来决定父控件是否拦截事件。如果拦截返回返回true,不拦截返回false。如果父控件拦截了事件,则在父控件的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
|
/**
* 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
|
@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
|
@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;
}
|
这几乎是一个实现外部拦截事件的模板,这里一定不要在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事件无效。
可以参考一下源码:
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
|
// 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
;
}
|
那么我们如果想使用 内部拦截法拦截事件。
第一步:
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
|
/**
* Created by blueberry on 2016/6/20.
* 内部拦截
* 和 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
|
/**
* 内部拦截事件
*/
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);
}
}
|
调用代码:
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
|
@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);
}
|
至此,2种拦截方法已经学习完毕,下面我们来学习如何解决同向滑动冲突。
其实和上面的2个例子思路是一样的,只是用来判断是否拦截的那块逻辑不同而已。
下面的例子,是一个下拉刷新的一个控件。
3、外部拦截 解决同向滑动冲突
RefreshLayoutBase.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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
|
package
com.blueberry.sample.widget.refresh;
import
android.content.Context;
import
android.graphics.Color;
import
android.util.AttributeSet;
import
android.util.DisplayMetrics;
import
android.util.Log;
import
android.util.TypedValue;
import
android.view.MotionEvent;
import
android.view.View;
import
android.view.ViewConfiguration;
import
android.view.ViewGroup;
import
android.view.WindowManager;
import
android.widget.ProgressBar;
import
android.widget.Scroller;
import
android.widget.TextView;
import
com.blueberry.sample.R;
/**
*外部拦截(同向)
*
*/
public
abstract
class
RefreshLayoutBase<T
extends
View>
extends
ViewGroup {
private
static
final
String TAG =
"RefreshLayoutBase"
;
public
static
final
int
STATUS_LOADING =
1
;
public
static
final
int
STATUS_RELEASE_TO_REFRESH =
2
;
public
static
final
int
STATUS_PULL_TO_REFRESH =
3
;
public
static
final
int
STATUS_IDLE =
4
;
public
static
final
int
STATUS_LOAD_MORE =
5
;
private
static
int
SCROLL_DURATION =
500
;
protected
ViewGroup mHeadView;
protected
ViewGroup mFootView;
private
T contentView;
private
ProgressBar headProgressBar;
private
TextView headTv;
private
ProgressBar footProgressBar;
private
TextView footTv;
private
boolean
isFistTouch =
true
;
protected
int
currentStatus = STATUS_IDLE;
private
int
mScreenWidth;
private
int
mScreenHeight;
private
int
mLastXIntercepted;
private
int
mLastYIntercepted;
private
int
mLastX;
private
int
mLastY;
protected
int
mInitScrollY =
0
;
private
int
mTouchSlop;
protected
Scroller mScoller;
private
OnRefreshListener mOnRefreshListener;
public
RefreshLayoutBase(Context context) {
this
(context,
null
);
}
public
RefreshLayoutBase(Context context, AttributeSet attrs) {
this
(context, attrs,
0
);
}
public
RefreshLayoutBase(Context context, AttributeSet attrs,
int
defStyleAttr) {
super
(context, attrs, defStyleAttr);
getScreenSize();
initView();
mScoller =
new
Scroller(context);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
setPadding(
0
,
0
,
0
,
0
);
}
public
void
setContentView(T view) {
addView(view,
1
);
}
public
OnRefreshListener getOnRefreshListener() {
return
mOnRefreshListener;
}
public
void
setOnRefreshListener(OnRefreshListener mOnRefreshListener) {
this
.mOnRefreshListener = mOnRefreshListener;
}
private
void
initView() {
setupHeadView();
setupFootView();
}
private
void
getScreenSize() {
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics =
new
DisplayMetrics();
wm.getDefaultDisplay().getMetrics(metrics);
mScreenWidth = metrics.widthPixels;
mScreenHeight = metrics.heightPixels;
}
private
int
dp2px(
int
dp) {
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics =
new
DisplayMetrics();
wm.getDefaultDisplay().getMetrics(metrics);
return
(
int
) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics);
}
/**
* 设置头布局
*/
private
void
setupHeadView() {
mHeadView = (ViewGroup) View.inflate(getContext(), R.layout.fresh_head_view,
null
);
mHeadView.setBackgroundColor(Color.RED);
headProgressBar = (ProgressBar) mHeadView.findViewById(R.id.head_progressbar);
headTv = (TextView) mHeadView.findViewById(R.id.head_tv);
/*设置 实际高度为 1/4 ,但内容区域只有 100dp*/
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, mScreenHeight / 4);
mHeadView.setLayoutParams(layoutParams);
mHeadView.setPadding(0, mScreenHeight / 4 - dp2px(100), 0, 0);
addView(mHeadView);
}
/**
* 设置尾布局
*/
private void setupFootView() {
mFootView = (ViewGroup) View.inflate(getContext(), R.layout.fresh_foot_view, null);
mFootView.setBackgroundColor(Color.BLUE);
footProgressBar = (ProgressBar) mFootView.findViewById(R.id.fresh_foot_progressbar);
footTv = (TextView) mFootView.findViewById(R.id.fresh_foot_tv);
addView(mFootView);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int finalHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
finalHeight += child.getMeasuredHeight();
}
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
widthSize = getChildAt(0).getMeasuredWidth();
setMeasuredDimension(widthSize, finalHeight);
} else if (widthMode == MeasureSpec.AT_MOST) {
widthSize = getChildAt(0).getMeasuredWidth();
setMeasuredDimension(widthSize, height);
} else {
setMeasuredDimension(widthSize, finalHeight);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int topOffset = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.layout(getPaddingLeft(), getPaddingTop() + topOffset, r, getPaddingTop() + child.getMeasuredHeight() + topOffset);
topOffset += child.getMeasuredHeight();
}
mInitScrollY = mHeadView.getMeasuredHeight() + getPaddingTop();
scrollTo(0, mInitScrollY);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastXIntercepted = x;
mLastYIntercepted = y;
break;
case MotionEvent.ACTION_MOVE:
final int deltaY = x - mLastYIntercepted;
if (isTop() && deltaY > 0 && Math.abs(deltaY) > mTouchSlop) {
/*下拉*/
intercepted = true;
}
break;
case MotionEvent.ACTION_UP:
break;
}
mLastXIntercepted = x;
mLastYIntercepted = y;
return intercepted;
}
private void doRefresh() {
Log.i(TAG, "doRefresh: ");
if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
mScoller.startScroll(0, getScrollY(), 0, mInitScrollY - getScrollY(), SCROLL_DURATION);
currentStatus = STATUS_IDLE;
} else if (currentStatus == STATUS_PULL_TO_REFRESH) {
mScoller.startScroll(0,getScrollY(),0,0-getScrollY(),SCROLL_DURATION);
if (null != mOnRefreshListener) {
currentStatus = STATUS_LOADING;
mOnRefreshListener.refresh();
}
}
invalidate();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (!mScoller.isFinished()) {
mScoller.abortAnimation();
}
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
if (isFistTouch) {
isFistTouch = false;
mLastX = x;
mLastY = y;
}
final int deltaY = y - mLastY;
if (currentStatus != STATUS_LOADING) {
changeScrollY(deltaY);
}
break;
case MotionEvent.ACTION_UP:
isFistTouch = true;
doRefresh();
break;
}
mLastX = x;
mLastY = y;
return true;
}
private void changeScrollY(int deltaY) {
Log.i(TAG, "changeScrollY: ");
int curY = getScrollY();
if (deltaY > 0) {
/*下拉*/
if (curY - deltaY > getPaddingTop()) {
scrollBy(0, -deltaY);
}
} else {
/*上拉*/
if (curY - deltaY <= mInitScrollY) {
scrollBy(0, -deltaY);
}
}
curY = getScrollY();
int slop = mInitScrollY / 2;
if (curY > 0 && curY <=slop) {
currentStatus = STATUS_PULL_TO_REFRESH;
} else if (curY > 0 && curY >= slop) {
currentStatus = STATUS_RELEASE_TO_REFRESH;
}
}
@Override
public void computeScroll() {
if (mScoller.computeScrollOffset()) {
scrollTo(mScoller.getCurrX(), mScoller.getCurrY());
postInvalidate();
}
}
/**
* 加载完成调用这个方法
*/
public void refreshComplete() {
mScoller.startScroll(0, getScrollY(), 0, mInitScrollY - getScrollY(), SCROLL_DURATION);
currentStatus = STATUS_IDLE;
invalidate();
}
/**
* 显示 Footer
*/
public void showFooter() {
if(currentStatus==STATUS_LOAD_MORE) return ;
currentStatus = STATUS_LOAD_MORE ;
mScoller.startScroll(0, getScrollY(), 0, mFootView.getMeasuredHeight()
, SCROLL_DURATION);
invalidate();
}
/**
* loadMore完成之后调用
*/
public
void
footerComplete() {
mScoller.startScroll(
0
, getScrollY(),
0
, mInitScrollY - getScrollY(), SCROLL_DURATION);
invalidate();
currentStatus = STATUS_IDLE;
}
public
interface
OnRefreshListener {
void
refresh();
}
abstract
boolean
isTop();
abstract
boolean
isBottom();
}
|
它是一个抽象类,需要编写子类继承isTop()和 isBottom()方法。下面给出它的一个实现类:
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
|
package
com.blueberry.sample.widget.refresh;
import
android.content.Context;
import
android.util.AttributeSet;
import
android.widget.AbsListView;
import
android.widget.ListView;
/**
* Created by blueberry on 2016/6/21.
*
* RefreshLayoutBase 的一个实现类
*/
public
class
RefreshListView
extends
RefreshLayoutBase<ListView> {
private
static
final
String TAG =
"RefreshListView"
;
private
ListView listView;
private
OnLoadListener loadListener;
public
RefreshListView(Context context) {
super
(context);
}
public
RefreshListView(Context context, AttributeSet attrs) {
super
(context, attrs);
}
public
RefreshListView(Context context, AttributeSet attrs,
int
defStyleAttr) {
super
(context, attrs, defStyleAttr);
}
public
ListView getListView() {
return
listView;
}
public
void
setListView(
final
ListView listView) {
this
.listView = listView;
setContentView(listView);
this
.listView.setOnScrollListener(
new
AbsListView.OnScrollListener() {
@Override
public
void
onScrollStateChanged(AbsListView view,
int
scrollState) {
}
@Override
public
void
onScroll(AbsListView view,
int
firstVisibleItem,
int
visibleItemCount,
int
totalItemCount) {
/*这里存在一个bug: 当listView滑动到底部的时候,如果下拉也会出现footer
* 这是因为,暂时还没有想到如何判断是下拉还是上拉。
* 如果要解决此问题,我觉得应该重写listView 的onTouchEvent来判断手势方向
* 次模块主要解决竖向滑动冲突,故现将此问题放下。
* */
if
(currentStatus == STATUS_IDLE
&& getScrollY() <= mInitScrollY && isBottom()
) {
showFooter();
if
(
null
!= loadListener) {
loadListener.onLoadMore();
}
}
}
});
}
public
OnLoadListener getLoadListener() {
return
loadListener;
}
public
void
setLoadListener(OnLoadListener loadListener) {
this
.loadListener = loadListener;
}
@Override
boolean
isTop() {
return
listView.getFirstVisiblePosition() ==
0
&& getScrollY() <= mHeadView.getMeasuredHeight();
}
@Override
boolean
isBottom() {
return
listView.getLastVisiblePosition() == listView.getAdapter().getCount() -
1
;
}
public
interface
OnLoadListener {
void
onLoadMore();
}
}
|
4、内部拦截法解决同向滑动
同样是一个下拉刷新组件,因为实现原理都一样,所以这个写的比较随意些。主要还是如果解决滑动冲突。
RefreshLayoutBase2.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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
|
package
com.blueberry.sample.widget.refresh;
import
android.content.Context;
import
android.graphics.Color;
import
android.util.AttributeSet;
import
android.util.Log;
import
android.view.MotionEvent;
import
android.view.View;
import
android.view.ViewGroup;
import
android.widget.ArrayAdapter;
import
android.widget.ListView;
import
android.widget.Scroller;
import
com.blueberry.sample.R;
import
java.util.ArrayList;
import
java.util.List;
/**
* Created by blueberry on 2016/6/22.
* 结合内部类 ListVieEx
* 内部拦截法,同向
*/
public
class
RefreshLayoutBase2
extends
ViewGroup {
private
static
final
String TAG =
"RefreshLayoutBase2"
;
private
static
List<String> datas;
static
{
datas =
new
ArrayList<>();
for
(
int
i =
0
; i <
40
; i++) {
datas.add(
"数据—"
+ i);
}
}
private
ViewGroup headView;
private
ListViewEx lv;
private
int
lastY;
public
int
mInitScrollY;
private
Scroller mScroller;
public
RefreshLayoutBase2(Context context) {
this
(context,
null
);
}
public
RefreshLayoutBase2(Context context, AttributeSet attrs) {
this
(context, attrs,
0
);
}
public
RefreshLayoutBase2(Context context, AttributeSet attrs,
int
defStyleAttr) {
super
(context, attrs, defStyleAttr);
mScroller =
new
Scroller(context);
setupHeadView(context);
setupContentView(context);
}
@Override
protected
void
onMeasure(
int
widthMeasureSpec,
int
heightMeasureSpec) {
int
widthSize = MeasureSpec.getSize(widthMeasureSpec);
int
widthMode = MeasureSpec.getMode(widthMeasureSpec);
int
height = MeasureSpec.getSize(heightMeasureSpec);
int
heightMode = MeasureSpec.getMode(heightMeasureSpec);
int
finalHeight =
0
;
for
(
int
i =
0
; i < getChildCount(); i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
finalHeight += child.getMeasuredHeight();
}
if
(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
widthSize = getChildAt(
0
).getMeasuredWidth();
setMeasuredDimension(widthSize, finalHeight);
}
else
if
(widthMode == MeasureSpec.AT_MOST) {
widthSize = getChildAt(
0
).getMeasuredWidth();
setMeasuredDimension(widthSize, height);
}
else
{
setMeasuredDimension(widthSize, finalHeight);
}
}
@Override
protected
void
onLayout(
boolean
changed,
int
l,
int
t,
int
r,
int
b) {
int
topOffset =
0
;
for
(
int
i =
0
; i < getChildCount(); i++) {
View child = getChildAt(i);
child.layout(getPaddingLeft(), getPaddingTop() + topOffset, r, getPaddingTop() + child.getMeasuredHeight() + topOffset);
topOffset += child.getMeasuredHeight();
}
mInitScrollY = headView.getMeasuredHeight() + getPaddingTop();
scrollTo(
0
, mInitScrollY);
}
/**
* 不拦截Down 其他一律拦截
* @param ev
* @return
*/
@Override
public
boolean
onInterceptTouchEvent(MotionEvent ev) {
if
(ev.getAction() == MotionEvent.ACTION_DOWN)
return
false
;
return
true
;
}
@Override
public
boolean
onTouchEvent(MotionEvent event) {
int
y = (
int
) event.getY();
switch
(event.getAction()) {
case
MotionEvent.ACTION_DOWN:
break
;
case
MotionEvent.ACTION_MOVE:
final
int
deltaY = y-lastY;
Log.i(TAG,
"onTouchEvent: deltaY: "
+deltaY);
if
(deltaY >=
0
&& lv.isTop() && getScrollY() - deltaY >=getPaddingTop()) {
scrollBy(
0
, -deltaY);
}
break
;
case
MotionEvent.ACTION_UP:
this
.postDelayed(
new
Runnable() {
@Override
public
void
run() {
mScroller.startScroll(
0
,getScrollY(),
0
,mInitScrollY-getScrollY());
invalidate();
}
},
2000
);
break
;
}
lastY = y ;
return
true
;
}
private
void
setupHeadView(Context context) {
headView = (ViewGroup) View.inflate(context, R.layout.fresh_head_view,
null
);
headView.setBackgroundColor(Color.RED);
ViewGroup.LayoutParams params =
new
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
300
);
addView(headView, params);
}
public
void
setupContentView(Context context) {
lv =
new
ListViewEx(context,
this
);
lv.setBackgroundColor(Color.BLUE);
ArrayAdapter<String> adapter =
new
ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, datas);
lv.setAdapter(adapter);
addView(lv,
new
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
@Override
public
void
computeScroll() {
if
(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
postInvalidate();
}
}
public
static
class
ListViewEx
extends
ListView {
private
RefreshLayoutBase2 outter;
public
ListViewEx(Context context, RefreshLayoutBase2 outter) {
super
(context);
this
.outter = outter;
}
public
ListViewEx(Context context, AttributeSet attrs) {
super
(context, attrs);
}
public
ListViewEx(Context context, AttributeSet attrs,
int
defStyleAttr) {
super
(context, attrs, defStyleAttr);
}
/**
* 使用 outter.requestDisallowInterceptTouchEvent();
* 来决定父控件是否对事件进行拦截
* @param ev
* @return
*/
@Override
public
boolean
dispatchTouchEvent(MotionEvent ev) {
switch
(ev.getAction()) {
case
MotionEvent.ACTION_DOWN:
outter.requestDisallowInterceptTouchEvent(
true
);
break
;
case
MotionEvent.ACTION_MOVE:
if
( isTop() && outter.getScrollY() <= outter.mInitScrollY) {
outter.requestDisallowInterceptTouchEvent(
false
);
}
break
;
}
return
super
.dispatchTouchEvent(ev);
}
public
boolean
isTop() {
return
getFirstVisiblePosition() ==
0
;
}
}
}
|
注:
1、转载原文地址:
http://blog.csdn.net/tgbus18990140382/article/details/53887982
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1117/574.html
http://www.jb51.net/article/104882.htm
2、
滑动速度跟踪类VelocityTracker介绍
VelocityTracker 是一个跟踪触摸事件滑动速度的帮助类,用于实现flinging以及其它类似的手势。它的原理是把触摸事件 MotionEvent 对象传递给VelocityTracker的addMovement(MotionEvent)方法,然后分析MotionEvent 对象在单位时间类发生的位移来计算速度。你可以使用getXVelocity() 或getXVelocity()获得横向和竖向的速率到速率时,但是使用它们之前请先调用computeCurrentVelocity(int)来初始化速率的单位 。
主要函数
Public Methods | |
---|---|
void |
addMovement(
MotionEvent event)
Add a user's movement to the tracker. |
void |
clear()
Reset the velocity tracker back to its initial state. |
void |
computeCurrentVelocity(int units, float maxVelocity)
Compute the current velocity based on the points that have been collected. intunitis表示速率的基本时间单位。unitis值为1的表示是,一毫秒时间单位内运动了多少个像素, unitis值为1000表示一秒(1000毫秒)时间单位内运动了多少个像素 floatVelocity表示速率的最大值 |
void |
computeCurrentVelocity(int units)
Equivalent to invoking computeCurrentVelocity(int, float)with a maximum velocity of Float.MAX_VALUE. |
abstract T | getNextPoolable() |
float |
getXVelocity()
Retrieve the last computed X velocity. |
float |
getXVelocity(int id)
Retrieve the last computed X velocity. |
float |
getYVelocity(int id)
Retrieve the last computed Y velocity. |
float |
getYVelocity()
Retrieve the last computed Y velocity. |
abstract boolean | isPooled() |
static VelocityTracker |
obtain()
Retrieve a new VelocityTracker object to watch the velocity of a motion. |
void |
recycle()
Return a VelocityTracker object back to be re-used by others. |
abstract void | setNextPoolable(T element) |
abstract void | setPooled(boolean isPooled) |
示例:
- private VelocityTracker mVelocityTracker;//生命变量
- //在onTouchEvent(MotionEvent ev)中
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();//获得VelocityTracker类实例
- }
- mVelocityTracker.addMovement(ev);//将事件加入到VelocityTracker类实例中
- //判断当ev事件是MotionEvent.ACTION_UP时:计算速率
- final VelocityTracker velocityTracker = mVelocityTracker;
- // 1000 provides pixels per second
- velocityTracker.computeCurrentVelocity(1, (float)0.01);//设置maxVelocity值为0.1时,速率大于0.01时,显示的速率都是0.01,速率小于0.01时,显示正常
- Log.i("test","velocityTraker"+velocityTracker.getXVelocity());
- velocityTracker.computeCurrentVelocity(1000); //设置units的值为1000,意思为一秒时间内运动了多少个像素
- Log.i("test","velocityTraker"+velocityTracker.getXVelocity());
大体的使用是这样的:
当你需要跟踪触摸屏事件的速度的时候,使用obtain()方法来获得VelocityTracker类的一个实例对象
在onTouchEvent回调函数中,使用addMovement(MotionEvent)函数将当前的移动事件传递给VelocityTracker对象
使用computeCurrentVelocity (int units)函数来计算当前的速度,使用getXVelocity ()、 getYVelocity ()函数来获得当前的速度
下面是一个简单Demo:
- package com.bxwu.demo.component.activity;
- import android.app.Activity;
- import android.graphics.Color;
- import android.os.Bundle;
- import android.view.MotionEvent;
- import android.view.VelocityTracker;
- import android.view.ViewConfiguration;
- import android.view.ViewGroup.LayoutParams;
- import android.widget.TextView;
- public class VelocityTrackerTest extends Activity {
- private TextView mInfo;
- private VelocityTracker mVelocityTracker;
- private int mMaxVelocity;
- private int mPointerId;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mVelocityTracker = VelocityTracker.obtain();
- mMaxVelocity = ViewConfiguration.get(this).getMaximumFlingVelocity();
- mInfo = new TextView(this);
- mInfo.setLines(4);
- mInfo.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
- mInfo.setTextColor(Color.WHITE);
- setContentView(mInfo);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- final int action = event.getAction();
- mVelocityTracker.addMovement(event);
- final VelocityTracker verTracker = mVelocityTracker;
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- //求第一个触点的id, 此时可能有多个触点,但至少一个
- mPointerId = event.getPointerId(0);
- break;
- case MotionEvent.ACTION_MOVE:
- //求伪瞬时速度
- verTracker.computeCurrentVelocity(1000, mMaxVelocity);
- final float velocityX = verTracker.getXVelocity(mPointerId);
- final float velocityY = verTracker.getYVelocity(mPointerId);
- recodeInfo(velocityX, velocityY);
- break;
- case MotionEvent.ACTION_UP:
- releaseVelocityTracker();
- break;
- case MotionEvent.ACTION_CANCEL:
- releaseVelocityTracker();
- break;
- default:
- break;
- }
- return super.onTouchEvent(event);
- }
- //释放VelocityTracker
- private void releaseVelocityTracker() {
- if(null != mVelocityTracker) {
- mVelocityTracker.clear();
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- }
- private static final String sFormatStr = "velocityX=%f\nvelocityY=%f";
- /**
- * 记录当前速度
- *
- * @param velocityX x轴速度
- * @param velocityY y轴速度
- */
- private void recodeInfo(final float velocityX, final float velocityY) {
- final String info = String.format(sFormatStr, velocityX, velocityY);
- mInfo.setText(info);
- }
- }
代码很简单,我们可以求出move过程中的伪瞬时速度, 这样在做很多控件的时候都是可以用到的,比如系统Launcher的分页,
ScrollView滑动等, 可根据此时的速度来计算ACTION_UP后的减速运动等。实现一些非常棒的效果。
3、
Android Scroller完全解析,关于Scroller你所需知道的一切
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/48719871
2016大家新年好!这是今年的第一篇文章,那么应CSDN工作人员的建议,为了能给大家带来更好的阅读体验,我也是将博客换成了宽屏版。另外,作为一个对新鲜事物从来后知后觉的人,我终于也在新的一年里改用MarkDown编辑器来写博客了,希望大家在我的博客里也能体验到新年新的气象。
我写博客的题材很多时候取决于平时大家问的问题,最近一段时间有不少朋友都问到ViewPager是怎么实现的。那ViewPager相信每个人都再熟悉不过了,因此它实在是太常用了,我们可以借助ViewPager来轻松完成页面之间的滑动切换效果,但是如果问到它是如何实现的话,我感觉大部分人还是比较陌生的, 为此我也是做了一番功课。其实说到ViewPager最基本的实现原理主要就是两部分内容,一个是事件分发,一个是Scroller,那么对于事件分发,其实我在很早之前就已经写过了相关的内容,感兴趣的朋友可以去阅读Android事件分发机制完全解析,带你从源码的角度彻底理解,但是对于Scroller我还从来没有讲过,因此本篇文章我们就先来学习一下Scroller的用法,并结合事件分发和Scroller来实现一个简易版的ViewPager。
Scroller是一个专门用于处理滚动效果的工具类,可能在大多数情况下,我们直接使用Scroller的场景并不多,但是很多大家所熟知的控件在内部都是使用Scroller来实现的,如ViewPager、ListView等。而如果能够把Scroller的用法熟练掌握的话,我们自己也可以轻松实现出类似于ViewPager这样的功能。那么首先新建一个ScrollerTest项目,今天就让我们通过例子来学习一下吧。
先撇开Scroller类不谈,其实任何一个控件都是可以滚动的,因为在View类当中有scrollTo()和scrollBy()这两个方法,如下图所示:
这两个方法都是用于对View进行滚动的,那么它们之间有什么区别呢?简单点讲,scrollBy()方法是让View相对于当前的位置滚动某段距离,而scrollTo()方法则是让View相对于初始的位置滚动某段距离。这样讲大家理解起来可能有点费劲,我们来通过例子实验一下就知道了。
修改activity_main.xml中的布局文件,代码如下所示:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
外层我们使用了一个LinearLayout,然后在里面包含了两个按钮,一个用于触发scrollTo逻辑,一个用于触发scrollBy逻辑。
接着修改MainActivity中的代码,如下所示:
- 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
没错,代码就是这么简单。当点击了scrollTo按钮时,我们调用了LinearLayout的scrollTo()方法,当点击了scrollBy按钮时,调用了LinearLayout的scrollBy()方法。那有的朋友可能会问了,为什么都是调用的LinearLayout中的scroll方法?这里一定要注意,不管是scrollTo()还是scrollBy()方法,滚动的都是该View内部的内容,而LinearLayout中的内容就是我们的两个Button,如果你直接调用button的scroll方法的话,那结果一定不是你想看到的。
另外还有一点需要注意,就是两个scroll方法中传入的参数,第一个参数x表示相对于当前位置横向移动的距离,正值向左移动,负值向右移动,单位是像素。第二个参数y表示相对于当前位置纵向移动的距离,正值向上移动,负值向下移动,单位是像素。
那说了这么多,scrollTo()和scrollBy()这两个方法到底有什么区别呢?其实运行一下代码我们就能立刻知道了:
可以看到,当我们点击scrollTo按钮时,两个按钮会一起向右下方滚动,因为我们传入的参数是-60和-100,因此向右下方移动是正确的。但是你会发现,之后再点击scrollTo按钮就没有任何作用了,界面不会再继续滚动,只有点击scrollBy按钮界面才会继续滚动,并且不停点击scrollBy按钮界面会一起滚动下去。
现在我们再来回头看一下这两个方法的区别,scrollTo()方法是让View相对于初始的位置滚动某段距离,由于View的初始位置是不变的,因此不管我们点击多少次scrollTo按钮滚动到的都将是同一个位置。而scrollBy()方法则是让View相对于当前的位置滚动某段距离,那每当我们点击一次scrollBy按钮,View的当前位置都进行了变动,因此不停点击会一直向右下方移动。
通过这个例子来理解,相信大家已经把scrollTo()和scrollBy()这两个方法的区别搞清楚了,但是现在还有一个问题,从上图中大家也能看得出来,目前使用这两个方法完成的滚动效果是跳跃式的,没有任何平滑滚动的效果。没错,只靠scrollTo()和scrollBy()这两个方法是很难完成ViewPager这样的效果的,因此我们还需要借助另外一个关键性的工具,也就我们今天的主角Scroller。
Scroller的基本用法其实还是比较简单的,主要可以分为以下几个步骤:
1. 创建Scroller的实例
2. 调用startScroll()方法来初始化滚动数据并刷新界面
3. 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
那么下面我们就按照上述的步骤,通过一个模仿ViewPager的简易例子来学习和理解一下Scroller的用法。
新建一个ScrollerLayout并让它继承自ViewGroup来作为我们的简易ViewPager布局,代码如下所示:
- 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
整个Scroller用法的代码都在这里了,代码并不长,一共才100多行,我们一点点来看。
首先在ScrollerLayout的构造函数里面我们进行了上述步骤中的第一步操作,即创建Scroller的实例,由于Scroller的实例只需创建一次,因此我们把它放到构造函数里面执行。另外在构建函数中我们还初始化的TouchSlop的值,这个值在后面将用于判断当前用户的操作是否是拖动。
接着重写onMeasure()方法和onLayout()方法,在onMeasure()方法中测量ScrollerLayout里的每一个子控件的大小,在onLayout()方法中为ScrollerLayout里的每一个子控件在水平方向上进行布局。如果有朋友对这两个方法的作用还不理解,可以参照我之前写的一篇文章 Android视图绘制流程完全解析,带你一步步深入了解View(二) 。
接着重写onInterceptTouchEvent()方法, 在这个方法中我们记录了用户手指按下时的X坐标位置,以及用户手指在屏幕上拖动时的X坐标位置,当两者之间的距离大于TouchSlop值时,就认为用户正在拖动布局,然后我们就将事件在这里拦截掉,阻止事件传递到子控件当中。
那么当我们把事件拦截掉之后,就会将事件交给ScrollerLayout的onTouchEvent()方法来处理。如果当前事件是ACTION_MOVE,说明用户正在拖动布局,那么我们就应该对布局内容进行滚动从而影响拖动事件,实现的方式就是使用我们刚刚所学的scrollBy()方法,用户拖动了多少这里就scrollBy多少。另外为了防止用户拖出边界这里还专门做了边界保护,当拖出边界时就调用scrollTo()方法来回到边界位置。
如果当前事件是ACTION_UP时,说明用户手指抬起来了,但是目前很有可能用户只是将布局拖动到了中间,我们不可能让布局就这么停留在中间的位置,因此接下来就需要借助Scroller来完成后续的滚动操作。首先这里我们先根据当前的滚动位置来计算布局应该继续滚动到哪一个子控件的页面,然后计算出距离该页面还需滚动多少距离。接下来我们就该进行上述步骤中的第二步操作,调用startScroll()方法来初始化滚动数据并刷新界面。startScroll()方法接收四个参数,第一个参数是滚动开始时X的坐标,第二个参数是滚动开始时Y的坐标,第三个参数是横向滚动的距离,正值表示向左滚动,第四个参数是纵向滚动的距离,正值表示向上滚动。紧接着调用invalidate()方法来刷新界面。
现在前两步都已经完成了,最后我们还需要进行第三步操作,即重写computeScroll()方法,并在其内部完成平滑滚动的逻辑 。在整个后续的平滑滚动过程中,computeScroll()方法是会一直被调用的,因此我们需要不断调用Scroller的computeScrollOffset()方法来进行判断滚动操作是否已经完成了,如果还没完成的话,那就继续调用scrollTo()方法,并把Scroller的curX和curY坐标传入,然后刷新界面从而完成平滑滚动的操作。
现在ScrollerLayout已经准备好了,接下来我们修改activity_main.xml布局中的内容,如下所示:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
可以看到,这里我们在ScrollerLayout中放置了三个按钮用来进行测试,其实这里不仅可以放置按钮,放置任何控件都是没问题的。
最后MainActivity当中删除掉之前测试的代码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
好的,所有代码都在这里了,现在我们可以运行一下程序来看一看效果了,如下图所示:
怎么样,是不是感觉有点像一个简易的ViewPager了?其实借助Scroller,很多漂亮的滚动效果都可以轻松完成,比如实现图片轮播之类的特效。当然就目前这一个例子来讲,我们只是借助它来学习了一下Scroller的基本用法,例子本身有很多的功能点都没有去实现,比如说ViewPager会根据用户手指滑动速度的快慢来决定是否要翻页,这个功能在我们的例子中并没有体现出来,不过大家也可以当成自我训练来尝试实现一下。
好的,那么本篇文章就到这里,相信通过这篇文章的学习,大家已经能够熟练掌握Scroller的使用方法了,当然ViewPager的内部实现要比这复杂得多,如果有朋友对ViewPager的源码感兴趣也可以尝试去读一下,不过一定需要非常扎实的基本功才行。