当前位置:首页 > 嵌入式培训 > 嵌入式学习 > 讲师博文 > 什么是ViewDragLayout?

什么是ViewDragLayout? 时间:2018-09-26      来源:未知

什么是ViewDragLayout?

他是谷歌专门为我们制作自定义控件而做的一个工具类。他提供了一些很有用的方法,主要是提供拖动和重新定位子控件的操作功能。

好了那么怎么使用呢?

首先从类说明上看,应用场景应该是我们在重写一个ViewGroup,我们这里重写一个FrameLayout:

public class MyDragLayout extends FrameLayout{}

当然少不了我们还需要重写一下构造器:

public MyDragLayout(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

init() ;

}

这里我只把这个构造器的代码写出来,因为其他的构造器都会调用此构造器实现对象状态的初始化。

这里有个init方法,这个init方法中就是使用ViewDragLayout的第一步了:

private void init() {

//创建ViewDragHelper的对象

viewDragHelper = ViewDragHelper.create(this,1.0f, new MyDragCallback()) ;

}

这里我们需要调用ViewDragHelper的工程方法来实例化对象。

工厂方法的三个参数分别是:

1.View viewGroup : 这里我填的this,当然了,看了源码就明白了,这里要填的就是我们以后要拖动的子控件的父控件。

2.float slop : 这个就是手指触控的敏感度,没有特殊要求,就直接填1.0f(google官方推荐)

3.ViewDragHelper.Callback : 名字很赤果果,就是回调函数。我们在拖动子控件时的各种状态判定,都需要通过这个回调让我们参与进来。

好了现在我们需要重写一个回调函数出来:

class MyDragCallback extends ViewDragHelper.Callback{

@Override

public boolean tryCaptureView(View child, int pointerId) {

return super.tryCaptureView(child, pointerId);

}

}

上面的回调函数是默认创建出来的样子,这里有个重写的方法叫tryCaptureView()的方法,从名字看就是尝试获取View对象。实质上也是,不过呢,这里是提供一个比较,

比较当前ViewDragHelper他找到的这个View child是否是我们需要移动的这个控件,后面的pointerId是我们多触控时,返回的触控点id。当然根据我们的直觉,我们也应该了解这里不可能return super.tryCaptureView()。一般来说现在我们也没有特别要求,所以我们来返回true。代表获取到的控件,我们都认可为可以移动的控件。所以结果就是:

/**

* 用于判断指定的参数View child是否为我们当前需要操作的控件

* */

@Override

public boolean tryCaptureView(View child, int pointerId) {

return true;

}

好了,这里其实还没有完。不过呢,我们先放这里不管他。

好了我们这就定义好了一个自定义ViewGroup(继承自FrameLayout)。那么我们就来使用一下:

在Activity的布局文件中写入:

<?xml version="1.0" encoding="utf-8"?>

<com.example.administrator.viewdraghelperdemo.view.MyDragLayout xmlns:android="//schemas.android.com/apk/res/android" 

android:layout_width="match_parent"

android:layout_height="match_parent">

<TextView <>

android:id="@+id/tv_1"

android:layout_width="100dip"

android:layout_height="100dip"

android:background="@android:color/holo_red_light"

android:text="第一个TextView" />

<TextView <>

android:id="@+id/tv_2"

android:layout_width="100dip"

android:layout_height="100dip"

android:background="@android:color/holo_blue_light"

android:text="第二个TextView" />

<TextView <>

android:id="@+id/tv_3"

android:layout_width="100dip"

android:layout_height="100dip"

android:background="@android:color/holo_green_light"

android:text="第三个TextView" />

</com.example.administrator.viewdraghelperdemo.view.MyDragLayout>

然后我们启动这个Activity,我们会发现3个TextView并不会随着我们手指的滑动而移动。

当然我们写出上面的东西后,心里应该也会觉得空荡荡的少些代码,少什么呢?

我们在ViewGroup中仅仅是创建出了ViewDragHelper的对象,还没有使用过,那么怎么使用呢?

这里其实没有使用Google提供的工具,自己去实现过子控件移动的朋友应该清楚,在我们想要移动某个控件前,我们需要去了获取当前的触控事件,并且在合理的情况下,被我们拦截使用。所以在这个ViewGroup中我们还应该实现两个与触控事件处理紧密相关的方法:onTouchEvent()和onInterceptTouchEvent()。然后我们就可以在这两个方法中处理传递来的触控事件,并且决定是否交给子控件处理。而这里我们就可以利用ViewDragHelper来处理这些操作。

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

boolean shouldIntercet = viewDragHelper.shouldInterceptTouchEvent(ev) ;

return shouldIntercet;

}

当然首先是onInterceptTouchEvent传递给子控件的拦截触控事件。这里我们可以调用shouldInterceptTouchEvent(ev) ;来判断是否拦截次事件,当然我们要注意和Button这样的流氓一起使用时,这里没有办法返回true,因为Button会吃下整个触控事件,我们的ViewGroup只能得到一次ACTION_DOWN的事件,后续的ACTION_MOVE这些没法得到,而只有一次触控事件,这里的shouldInterceptTouchEvent是无法判断出来的。(这里推荐大家看看源码,源码实现也不复杂)

@Override

public boolean onTouchEvent(MotionEvent event) {

viewDragHelper.processTouchEvent(event);

return true;

}

好了,如果我们拦截下了触控事件要处理,那么我们就需要在onTouchEvent中来处理,首先返回值设为true(因为很容易忘掉...),保证我们来处理此事件。然后如果查看了上面shouldInterceptTouchEvent()的话,应该已经知道了,这里应该要调用一个叫processTouchEvent的方法,来处理我们整个拖动控件的操作。

好了,这样我们就将触控事件接受到,并处理了。当然现在如果去运行程序,你还是会发现无法操作。因为我们还没有告知子空间应当如果被拖动。

以现在我们对ViewDragHelper的了解,我们可以知道,被拖动的动作处理是在processTouchEvent方法来处理的。所以我们可以查看他的源码,在processTouchEvent中的ACTION_MOVE中我们能轻松发现一个方法dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy),看名字就知道是这个方法再操作我们的子控件移动:

private void dragTo(int left, int top, int dx, int dy) {

int clampedX = left;

int clampedY = top;

final int oldLeft = mCapturedView.getLeft();

final int oldTop = mCapturedView.getTop();

if (dx != 0) {

clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx);

mCapturedView.offsetLeftAndRight(clampedX - oldLeft);

}

if (dy != 0) {

clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);

mCapturedView.offsetTopAndBottom(clampedY - oldTop);

}

if (dx != 0 || dy != 0) {

final int clampedDx = clampedX - oldLeft;

final int clampedDy = clampedY - oldTop;

mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY,

clampedDx, clampedDy);

}

}

可以明确从代码中看到2个关键方法:

mCapturedView.offsetLeftAndRight(clampedX - oldLeft);

mCapturedView.offsetTopAndBottom(clampedY - oldTop);

这两个方法就是用于移动控件的位置的方法。

而这两句代码附近很明确调用了Callback的3个回调:

clampViewPositionHorizontal() : 用于获取水平移动子控件的移动后位置

clampViewPositionVertical() : 用于获取竖直移动子空间的移动后位置

onViewPositionChanged() : 用于告知父控件子控件的位移情况

知道这些之后我们就可以在MyDragCallback(我们实现的Callback实现子类)中填写上这3个回调。

/**

*横向拖动时,控件左上角x坐标结果返回。这里给出了3个参数

* View child 被操作的子控件

* int left 子控件当前坐标+手指在x方向的位移,其实就是跟随手指移动后的新坐标

* int dx 手指位移距离

* */

@Override

public int clampViewPositionHorizontal(View child, int left, int dx) {

return left ;

}

/**

*横向拖动时,控件左上角y坐标结果返回。这里给出了3个参数

* View child 被操作的子控件

* int top 子控件当前坐标+手指在y方向的位移,其实就是跟随手指移动后的新坐标

* int dy 手指位移距离

* */

@Override

public int clampViewPositionVertical(View child, int top, int dy) {

return top ;

}

/**

* 上面两个方法的结合体

* 但是并不会与上面两个方法冲突

* 上面两个方法主要是计算结果

* 这个方法更多是通知的效果,因为移动已经完成。

* */

@Override

public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {

super.onViewPositionChanged(changedView, left, top, dx, dy);

}

现在我们运行程序,我们的控件就可以移动了。

现在梳理下,我们的学习过程我们会发现,这个类的使用非常方便:

1,通过工厂方法获取实例对象

2,通过shouldInterceptTouchEvent(ev) 拦截触控事件,并交给processTouchEvent(event);处理触控事件。

3.在回调中,确认要移动的控件对象是否正确, 计算x坐标和y坐标两个方法子控件位移的距离和新的位置。

TIPS:补充

在子控件为Button这样会吃掉我们触控事件的控件时,shouldInterceptTouchEvent(ev) 无法判断出是否需要拦截。除了暴力的在onInterceptTouchEvent中返回true而外,我们还可以借用ViewDragHelper的边界拖动帮我们搞定这个事情。

实现边界拖动的过程:

1.在创建出ViewDragHelper的对象后,调用viewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);

tips:这个函数的参数是:EDGE_ALL所有边界,EDGE_LEFT左边界,EDGE_TOP上边界,EDGE_RIGHT右边界,EDGE_BOTTOM下边界。

2.在回调中实现几个方法:

/**边缘拖动**/

//先会通知我们触控的哪条边界

// edgeFlags = EDGE_LEFT|EDGE_TOP|EDGE_RIGHT|EDGE_BOTTOM 上下左右4边

@Override

public void onEdgeTouched(int edgeFlags, int pointerId) {

switch(edgeFlags){

case EDGE_ALL:

break ;

case EDGE_LEFT:

break ;

case EDGE_TOP:

break ;

case EDGE_RIGHT:

break ;

case EDGE_BOTTOM:

break ;

}

}

/**

* 可以用来阻止某条边不能实现边缘拖动功能

* 反回false就是可以使用,返回true就是不可以用

* */

@Override

public boolean onEdgeLock(int edgeFlags) {

return false;

}

/**

* 具体哪个控件可以被边缘拖动,那么我们就要在这里取指定了

* */

@Override

public void onEdgeDragStarted(int edgeFlags, int pointerId) {

super.onEdgeDragStarted(edgeFlags, pointerId);

viewDragHelper.captureChildView(getChildAt(1),pointerId);

}

加入上面的代码,我们就可以从边缘开始实现拖动。

后的彩蛋:

ViewDragHelper还提供了一个动画移动子控件的效果:settleCapturedViewAt(x, y),这个方法内部由Scroller实现,所以还需要在ViewGroup中实现computeScroll方法。

通常这个功能是实现松手后,控件回到原位。我们可以在Callback中找到一个onViewReleased方法来配合使用:

1,在Callback的实现类中重写方法:

/**

* 用户操作完毕时,调用,我们可以在其中使用回到指定位置的方法,让我们拖动的控件,回到某个指定坐标

* */

@Override

public void onViewReleased(View releasedChild, float xvel, float yvel) {

super.onViewReleased(releasedChild, xvel, yvel);

//当前被移动的控件,使用移动动画,移动到0,0点

if(viewDragHelper.settleCapturedViewAt(0, 0)){

//使用Scroller时,要调用invalidate,因为computScroll是在View.draw()中调用。要重绘才会启动。

invalidate();

}

}

2.实现computScroll,在我们的ViewGroup(自定义控件)中实现:

@Override

public void computeScroll() {

if(viewDragHelper.continueSettling(true)){

invalidate();

}

}

这样实现以后,我们就会拖动控件后,控件自动恢复到0,0坐标去。

上一篇:Qt5 信号与槽机制的使用

下一篇:进程的产生

热点文章推荐
华清学员就业榜单
高薪学员经验分享
热点新闻推荐
前台专线:010-82525158 企业培训洽谈专线:010-82525379 院校合作洽谈专线:010-82525379 Copyright © 2004-2022 北京华清远见科技集团有限公司 版权所有 ,京ICP备16055225号-5京公海网安备11010802025203号

回到顶部