Android 一步一步教你使用ViewDragHelper

怼烎@ 2022-08-19 12:12 397阅读 0赞

在自定义viewgroup的时候 要重写onInterceptTouchEvent和onTouchEvent 这2个方法 是非常麻烦的事情,好在谷歌后来

推出了ViewDragHelper这个类。可以极大方便我们自定义viewgroup.

先看一个简单效果 一个layout里有2个图片 其中有一个可以滑动 一个不能滑

183354ins4qbls3l4vp9cq.jpg

这个效果其实还蛮简单的

布局文件:

[XML] 纯文本查看 复制代码

?










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”
?>


 


<
LinearLayout
xmlns:android
=
http://schemas.android.com/apk/res/android


 


    
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
>

然后我们看一下自定义的layout 如何实现2个子view 一个可以滑动 一个不能滑动的

[Java] 纯文本查看 复制代码

?










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充满了屏幕 所以看上去是在屏幕内)

[Java] 纯文本查看 复制代码

?










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>

我们看下效果

183401ahcccqnsw2cqvsvk.jpg

然后我们可以再加上一个回弹的效果,就是你把babay拉倒一个位置 然后松手他会自动回弹到初始位置

其实思路很简单 就是你松手的时候 回到初始的坐标位置即可。

[Java] 纯文本查看 复制代码

?










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>

看下效果:

183406fy0b7nlybrr75gd7.jpg

到这里有人会发现 这样做的话imageview就无法响应点击事件了。继续修改这个代码让iv可以响应点击事件并且可以响应

滑动事件。

首先修改xml 把click属性设置为true 这个代码就不上了,然后修改我们的代码 其实就是增加2个函数

[Java] 纯文本查看 复制代码

?










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>

然后看下效果:

183437wkjcwg79a0khcj3g.jpg

这个地方 如果你学过android 事件传递的话很好理解,因为如果你子view可以响应点击事件的话,那说明你消费了这个事件。

如果你消费了这个事件话 就会先走dragger的 onInterceptTouchEvent这个方法。我们跟进去看看这个方法

[Java] 纯文本查看 复制代码

?










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即可。

代码非常简单 两行即可

再重写一个回调函数 然后加个监听

[Java] 纯文本查看 复制代码

?










1


2


3


4



@Override


         
public
void
onEdgeDragStarted(
int
edgeFlags,
int
pointerId) {


             
mDragger.captureChildView(iv1, pointerId);


         
}

[Java] 纯文本查看 复制代码

?










1



mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);

这个效果在模拟器上不知道为啥 鼠标拖不动,GIF图片我就不上了大家可以自己在手机里跑一下就可以。

上面的那些效果实际上都是DrawerLayout 等类似抽屉效果里经常用到的函数,有兴趣的同学可以

看下源码。

原文:http://www.cnblogs.com/punkisnotdead/p/4724825.html?utm\_source=tuicool

发表评论

表情:
评论列表 (有 0 条评论,397人围观)

还没有评论,来说两句吧...

相关阅读

    相关 安装openstack

    openstack,安装的门槛比较高,而且相当麻烦,很多的安装文档作者省了不少安装步骤。这对初学的人带来了很大的麻烦,也许作者轻易节省一步,就会创成后面的安装失败。而且初学者由