在自定义viewgroup的时候 要重写onInterceptTouchEvent和onTouchEvent 这2个方法 是非常麻烦的事情,好在谷歌后来
推出了ViewDragHelper这个类。可以极大方便我们自定义viewgroup.
先看一个简单效果 一个layout里有2个图片 其中有一个可以滑动 一个不能滑
这个效果其实还蛮简单的
布局文件:
01
02
03
04
05
06
07
08
09
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
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
android:orientation
=
"vertical"
>
<
com.example.administrator.viewdragertestapp.DragLayout
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
android:orientation
=
"vertical"
>
<
ImageView
android:id
=
"@+id/iv1"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_gravity
=
"center_horizontal"
android:src
=
"@drawable/a1"
></
ImageView
>
<
ImageView
android:id
=
"@+id/iv2"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_gravity
=
"center_horizontal"
android:src
=
"@drawable/a2"
></
ImageView
>
</
com.example.administrator.viewdragertestapp.DragLayout
>
</
LinearLayout
>
|
01
02
03
04
05
06
07
08
09
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
|
package
com.example.administrator.viewdragertestapp;
import
android.content.Context;
import
android.support.v4.widget.ViewDragHelper;
import
android.util.AttributeSet;
import
android.view.MotionEvent;
import
android.view.View;
import
android.widget.ImageView;
import
android.widget.LinearLayout;
import
android.widget.TextView;
/**
* Created by Administrator on 2015/8/12.
*/
public
class
DragLayout
extends
LinearLayout {
private
ViewDragHelper mDragger;
private
ViewDragHelper.Callback callback;
private
ImageView iv1;
private
ImageView iv2;
@Override
protected
void
onFinishInflate() {
iv1 = (ImageView)
this
.findViewById(R.id.iv1);
iv2 = (ImageView)
this
.findViewById(R.id.iv2);
super
.onFinishInflate();
}
public
DragLayout(Context context) {
super
(context);
}
public
DragLayout(Context context, AttributeSet attrs) {
super
(context, attrs);
callback =
new
DraggerCallBack();
//第二个参数就是滑动灵敏度的意思 可以随意设置
mDragger = ViewDragHelper.create(
this
,
1
.0f, callback);
}
class
DraggerCallBack
extends
ViewDragHelper.Callback {
//这个地方实际上函数返回值为true就代表可以滑动 为false 则不能滑动
@Override
public
boolean
tryCaptureView(View child,
int
pointerId) {
if
(child == iv2) {
return
false
;
}
return
true
;
}
@Override
public
int
clampViewPositionHorizontal(View child,
int
left,
int
dx) {
return
left;
}
@Override
public
int
clampViewPositionVertical(View child,
int
top,
int
dy) {
return
top;
}
}
@Override
public
boolean
onInterceptTouchEvent(MotionEvent ev) {
//决定是否拦截当前事件
return
mDragger.shouldInterceptTouchEvent(ev);
}
@Override
public
boolean
onTouchEvent(MotionEvent event) {
//处理事件
mDragger.processTouchEvent(event);
return
true
;
}
}
|
然后再完善一下这个layout,刚才滑动的时候我们的view 出了屏幕的边界很不美观 现在我们修改2个函数 让滑动的范围
在这个屏幕之内(准确的说是在这个layout之内,因为我们的布局文件layout充满了屏幕 所以看上去是在屏幕内)
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<font style=
"color:rgb(51, 51, 51)"
><font style=
"background-color:rgb(255, 255, 255)"
><font face=
"Verdana, Arial, Helvetica, sans-serif"
>
//这个地方实际上left就代表 你将要移动到的位置的坐标。返回值就是最终确定的移动的位置。
// 我们要让view滑动的范围在我们的layout之内
//实际上就是判断如果这个坐标在layout之内 那我们就返回这个坐标值。
//如果这个坐标在layout的边界处 那我们就只能返回边界的坐标给他。不能让他超出这个范围
//除此之外就是如果你的layout设置了padding的话,也可以让子view的活动范围在padding之内的.
@Override
public
int
clampViewPositionHorizontal(View child,
int
left,
int
dx) {
//取得左边界的坐标
final
int
leftBound = getPaddingLeft();
//取得右边界的坐标
final
int
rightBound = getWidth() - child.getWidth() - leftBound;
//这个地方的含义就是 如果left的值 在leftbound和rightBound之间 那么就返回left
//如果left的值 比 leftbound还要小 那么就说明 超过了左边界 那我们只能返回给他左边界的值
//如果left的值 比rightbound还要大 那么就说明 超过了右边界,那我们只能返回给他右边界的值
return
Math.min(Math.max(left, leftBound), rightBound);
}
//纵向的注释就不写了 自己体会
@Override
public
int
clampViewPositionVertical(View child,
int
top,
int
dy) {
final
int
topBound = getPaddingTop();
final
int
bottomBound = getHeight() - child.getHeight() - topBound;
return
Math.min(Math.max(top, topBound), bottomBound);
}</font></font></font>
|
我们看下效果
然后我们可以再加上一个回弹的效果,就是你把babay拉倒一个位置 然后松手他会自动回弹到初始位置
其实思路很简单 就是你松手的时候 回到初始的坐标位置即可。
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
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
|
<font style=
"color:rgb(51, 51, 51)"
><font style=
"background-color:rgb(255, 255, 255)"
><font face=
"Verdana, Arial, Helvetica, sans-serif"
>
package
com.example.administrator.viewdragertestapp;
import
android.content.Context;
import
android.graphics.Point;
import
android.support.v4.widget.ViewDragHelper;
import
android.util.AttributeSet;
import
android.view.MotionEvent;
import
android.view.View;
import
android.widget.ImageView;
import
android.widget.LinearLayout;
import
android.widget.TextView;
/**
* Created by Administrator on 2015/8/12.
*/
public
class
DragLayout
extends
LinearLayout {
private
ViewDragHelper mDragger;
private
ViewDragHelper.Callback callback;
private
ImageView iv1;
private
ImageView iv2;
private
Point initPointPosition =
new
Point();
@Override
protected
void
onFinishInflate() {
iv1 = (ImageView)
this
.findViewById(R.id.iv1);
iv2 = (ImageView)
this
.findViewById(R.id.iv2);
super
.onFinishInflate();
}
public
DragLayout(Context context) {
super
(context);
}
public
DragLayout(Context context, AttributeSet attrs) {
super
(context, attrs);
callback =
new
DraggerCallBack();
//第二个参数就是滑动灵敏度的意思 可以随意设置
mDragger = ViewDragHelper.create(
this
,
1
.0f, callback);
}
class
DraggerCallBack
extends
ViewDragHelper.Callback {
//这个地方实际上函数返回值为true就代表可以滑动 为false 则不能滑动
@Override
public
boolean
tryCaptureView(View child,
int
pointerId) {
if
(child == iv2) {
return
false
;
}
return
true
;
}
//这个地方实际上left就代表 你将要移动到的位置的坐标。返回值就是最终确定的移动的位置。
// 我们要让view滑动的范围在我们的layout之内
//实际上就是判断如果这个坐标在layout之内 那我们就返回这个坐标值。
//如果这个坐标在layout的边界处 那我们就只能返回边界的坐标给他。不能让他超出这个范围
//除此之外就是如果你的layout设置了padding的话,也可以让子view的活动范围在padding之内的.
@Override
public
int
clampViewPositionHorizontal(View child,
int
left,
int
dx) {
//取得左边界的坐标
final
int
leftBound = getPaddingLeft();
//取得右边界的坐标
final
int
rightBound = getWidth() - child.getWidth() - leftBound;
//这个地方的含义就是 如果left的值 在leftbound和rightBound之间 那么就返回left
//如果left的值 比 leftbound还要小 那么就说明 超过了左边界 那我们只能返回给他左边界的值
//如果left的值 比rightbound还要大 那么就说明 超过了右边界,那我们只能返回给他右边界的值
return
Math.min(Math.max(left, leftBound), rightBound);
}
//纵向的注释就不写了 自己体会
@Override
public
int
clampViewPositionVertical(View child,
int
top,
int
dy) {
final
int
topBound = getPaddingTop();
final
int
bottomBound = getHeight() - child.getHeight() - topBound;
return
Math.min(Math.max(top, topBound), bottomBound);
}
@Override
public
void
onViewReleased(View releasedChild,
float
xvel,
float
yvel) {
//松手的时候 判断如果是这个view 就让他回到起始位置
if
(releasedChild == iv1) {
//这边代码你跟进去去看会发现最终调用的是startScroll这个方法 所以我们就明白还要在computeScroll方法里刷新
mDragger.settleCapturedViewAt(initPointPosition.x, initPointPosition.y);
invalidate();
}
}
}
@Override
public
void
computeScroll() {
if
(mDragger.continueSettling(
true
)) {
invalidate();
}
}
@Override
protected
void
onLayout(
boolean
changed,
int
l,
int
t,
int
r,
int
b) {
super
.onLayout(changed, l, t, r, b);
//布局完成的时候就记录一下位置
initPointPosition.x = iv1.getLeft();
initPointPosition.y = iv1.getTop();
}
@Override
public
boolean
onInterceptTouchEvent(MotionEvent ev) {
//决定是否拦截当前事件
return
mDragger.shouldInterceptTouchEvent(ev);
}
@Override
public
boolean
onTouchEvent(MotionEvent event) {
//处理事件
mDragger.processTouchEvent(event);
return
true
;
}
}</font></font></font>
|
看下效果:
到这里有人会发现 这样做的话imageview就无法响应点击事件了。继续修改这个代码让iv可以响应点击事件并且可以响应
滑动事件。
首先修改xml 把click属性设置为true 这个代码就不上了,然后修改我们的代码 其实就是增加2个函数
1
2
3
4
5
6
7
8
9
|
<font style=
"color:rgb(51, 51, 51)"
><font style=
"background-color:rgb(255, 255, 255)"
><font face=
"Verdana, Arial, Helvetica, sans-serif"
>
@Override
public
int
getViewHorizontalDragRange(View child) {
return
getMeasuredWidth() - child.getMeasuredWidth();
}
@Override
public
int
getViewVerticalDragRange(View child) {
return
getMeasuredHeight()-child.getMeasuredHeight();
}</font></font></font>
|
然后看下效果:
这个地方 如果你学过android 事件传递的话很好理解,因为如果你子view可以响应点击事件的话,那说明你消费了这个事件。
如果你消费了这个事件话 就会先走dragger的 onInterceptTouchEvent这个方法。我们跟进去看看这个方法
01
02
03
04
05
06
07
08
09
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
|
<font style=
"color:rgb(51, 51, 51)"
><font style=
"background-color:rgb(255, 255, 255)"
><font face=
"Verdana, Arial, Helvetica, sans-serif"
>
case
MotionEvent.ACTION_MOVE: {
if
(mInitialMotionX ==
null
|| mInitialMotionY ==
null
)
break
;
// First to cross a touch slop over a draggable view wins. Also report edge drags.
final
int
pointerCount = MotionEventCompat.getPointerCount(ev);
for
(
int
i =
0
; i < pointerCount; i++) {
final
int
pointerId = MotionEventCompat.getPointerId(ev, i);
final
float
x = MotionEventCompat.getX(ev, i);
final
float
y = MotionEventCompat.getY(ev, i);
final
float
dx = x - mInitialMotionX[pointerId];
final
float
dy = y - mInitialMotionY[pointerId];
final
View toCapture = findTopChildUnder((
int
) x, (
int
) y);
final
boolean
pastSlop = toCapture !=
null
&& checkTouchSlop(toCapture, dx, dy);
if
(pastSlop) {
// check the callback's
// getView[Horizontal|Vertical]DragRange methods to know
// if you can move at all along an axis, then see if it
// would clamp to the same value. If you can't move at
// all in every dimension with a nonzero range, bail.
final
int
oldLeft = toCapture.getLeft();
final
int
targetLeft = oldLeft + (
int
) dx;
final
int
newLeft = mCallback.clampViewPositionHorizontal(toCapture,
targetLeft, (
int
) dx);
final
int
oldTop = toCapture.getTop();
final
int
targetTop = oldTop + (
int
) dy;
final
int
newTop = mCallback.clampViewPositionVertical(toCapture, targetTop,
(
int
) dy);
final
int
horizontalDragRange = mCallback.getViewHorizontalDragRange(
toCapture);
final
int
verticalDragRange = mCallback.getViewVerticalDragRange(toCapture);
if
((horizontalDragRange ==
0
|| horizontalDragRange >
0
&& newLeft == oldLeft) && (verticalDragRange ==
0
|| verticalDragRange >
0
&& newTop == oldTop)) {
break
;
}
}
reportNewEdgeDrags(dx, dy, pointerId);
if
(mDragState == STATE_DRAGGING) {
// Callback might have started an edge drag
break
;
}
if
(pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
break
;
}
}
saveLastMotion(ev);
break
;
}</font></font></font>
|
注意看29行到末尾 你会发现 只有当
horizontalDragRange 和verticalDragRange大于0的时候 对应的move事件才会捕获。否则就是丢弃直接丢给子view自己处理了
另外还有一个效果就是 假如我们的 baby被拉倒了边界处,
我们的手指不需要拖动baby这个iv,手指直接在边界的其他地方拖动此时也能把这个iv拖走。
这个效果其实也可以实现,无非就是捕捉你手指在边界处的动作 然后传给你要拖动的view即可。
代码非常简单 两行即可
再重写一个回调函数 然后加个监听
1
2
3
4
|
@Override
public
void
onEdgeDragStarted(
int
edgeFlags,
int
pointerId) {
mDragger.captureChildView(iv1, pointerId);
}
|
1
|
mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);
|
这个效果在模拟器上不知道为啥 鼠标拖不动,GIF图片我就不上了大家可以自己在手机里跑一下就可以。
上面的那些效果实际上都是DrawerLayout 等类似抽屉效果里经常用到的函数,有兴趣的同学可以
看下源码。
原文:http://www.cnblogs.com/punkisnotdead/p/4724825.html?utm_source=tuicool