Android 中 View移动总结ViewDragHelper学习及用法详解.docx

上传人:b****7 文档编号:10564649 上传时间:2023-02-21 格式:DOCX 页数:20 大小:294.40KB
下载 相关 举报
Android 中 View移动总结ViewDragHelper学习及用法详解.docx_第1页
第1页 / 共20页
Android 中 View移动总结ViewDragHelper学习及用法详解.docx_第2页
第2页 / 共20页
Android 中 View移动总结ViewDragHelper学习及用法详解.docx_第3页
第3页 / 共20页
Android 中 View移动总结ViewDragHelper学习及用法详解.docx_第4页
第4页 / 共20页
Android 中 View移动总结ViewDragHelper学习及用法详解.docx_第5页
第5页 / 共20页
点击查看更多>>
下载资源
资源描述

Android 中 View移动总结ViewDragHelper学习及用法详解.docx

《Android 中 View移动总结ViewDragHelper学习及用法详解.docx》由会员分享,可在线阅读,更多相关《Android 中 View移动总结ViewDragHelper学习及用法详解.docx(20页珍藏版)》请在冰豆网上搜索。

Android 中 View移动总结ViewDragHelper学习及用法详解.docx

Android中View移动总结ViewDragHelper学习及用法详解

Android中View移动总结:

ViewDragHelper学习及用法详解

如上图简单呈现出两个方块后,提出一个需求:

 

1.拖动方块时,方块(即子View)可以跟随手指移动。

 

2.一个方块移动时,另一个方块可以跟随移动。

 

3.将方块移动到左边区域(右边区域)后放开(即手指离开屏幕),它会自动移动到左边界(右边界)。

 

4.移动的时候给方块加点动画(duang~duang~duang~)。

View移动的相关方法总结:

1.layout

在自定义控件中,View绘制的一个重写方法layout(),用来设置显示的位置。

所以,可以通过修改View的坐标值来改变view在父View的位置,以此可以达到移动的效果!

但是缺点是只能移动指定的View:

//通过layout方法来改变位置

view.layout(l,t,r,b);

2.offsetLeftAndRight()和offsetTopAndBottom()

非常方便的封装方法,只需提供水平、垂直方向上的偏移量,展示效果与layout()方法相同。

view.offsetLeftAndRight(offset);//同时改变left和right

view.offsetTopAndBottom(offset);//同时改变top和bottom

3.LayoutParams

此类保存了一个View的布局参数,可通过LayoutParams动态改变一个布局的位置参数,以此动态地修改布局,达到View位置移动的效果!

但是在获取getLayoutParams()时,要根据该子View对应的父View布局来决定自身的LayoutParams 。

所以一切的前提是:

必须要有一个父View,否则无法获取LayoutParams !

//必须获取父View的LayoutParams

LinearLayout.LayoutParamslayoutParams=(LinearLayout.LayoutParams)getLayoutParams();

layoutParams.leftMargin=getLeft()+dx;

layoutParams.topMargin=getTop()+dy;

setLayoutParams(layoutParams);

4.scrollTo和scrollBy

通过改变scrollX和scrollY来移动,但是可以移动所有的子View。

scrollTo(x,y)表示移动到一个具体的坐标点(x,y),而scrollBy(x,y)表示移动的增量为dx,dy。

scrollTo(x,y);

scrollBy(xOffset,yOffset);

注意:

这里使用scrollBy(xOffset,yOffset);,你会发现并没有效果,因为以上两个方法移动的是View的content。

若在ViewGroup中使用,移动的是所有子View;若在View中使用,移动的是View的内容(比如TextView)。

所以,不可在view中使用以上方法!

应该在View所在的ViewGroup中使用:

((View)getParent()).scrollBy(offsetX,offsetY);

【视图坐标系】:

 

可是即使这样,你会发现view移动的效果与设想方向相反!

这是Android试图移动原因,若参数为正值,content将向坐标轴负方向移动;参数为负值,content将向坐标轴正方向移动。

所以要实现随手指移动而滑动的效果,应将偏移量设置为负值即可:

((View)getParent()).scrollBy(-offsetX,-offsetY);

5.canvas

通过改变Canvas绘制的位置来移动View的内容,用的少:

canvas.drawBitmap(bitmap,left,top,paint)

总结

但是要完成最开始的提的需求,不管使用哪一种方法,都需要通过onTouchEvent方法来捕捉手势,自己手动计算移动距离,再改变子View的布局,不免有些麻烦,所以在这里引出正文,介绍一个强大的类来处理移动:

ViewDragHelper

ViewDragHelper介绍:

1.产生:

 ViewDragHelper在高版本的v4包(android4.4以上的v4)中,于Google在2013年开发者大会提出的

2.作用:

它主要用于处理ViewGroup中对子View的拖拽处理。

3.使用:

它主要封装了对View的触摸位置,触摸速度,移动距离等的检测和Scroller,通过接口回调的方式通知我们。

所以我们需要做的只是用接收来的数据指定这些子View是否需要移动,移动多少等。

4.本质:

是一个对触摸事件的解析类。

ViewDragHelper实现

1.ViewDragHelper实例创建

/**

*FactorymethodtocreateanewViewDragHelper.

*

*@paramforParentParentviewtomonitor

*@paramsensitivityMultiplierforhowsensitivethehelpershouldbeaboutdetecting

*thestartofadrag.Largervaluesaremoresensitive.1.0fisnormal.

*@paramcbCallbacktoprovideinformationandreceiveevents

*@returnanewViewDragHelperinstance

*/

viewDragHelper=ViewDragHelper.create(forParent,sensitivity,cb);

create()就是创建ViewDragHelper实例的方法,代码中的注释是create()中参数的解释,来查看:

 

(1)forParent:

“用来监视的父View”。

传入参数父View,即可监视该父View中的所有子View。

 

(2)sensitivity:

“检测时的敏感度;值越大越敏感,1是正常范围”。

比如说手指在滑动屏幕时速度特别快,敏感度越大时,此时速度快也可以检测到,反之亦然。

 

(3)Callback:

“提供信息和接受的事件”。

最重要的参数!

可以从这个回调提供的信息获取到View滑动的距离、速度等。

2.自定义View继承FrameLayout

这里来个小提示:

之前实现的布局中自定义DragLayout是继承于ViewGroup,并且实现重写了onMeasure()方法,如下:

DragLayout.java

publicclassDragLayoutextendsViewGroup{

...

@Override

protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){

super.onMeasure(widthMeasureSpec,heightMeasureSpec);

//方法一:

对子View的测量需求

/*获取子View的宽度100dp的两种方法:

intsize=(int)getResources().getDimension(R.dimen.width);

intsize=readView.getLayoutParams().width;*/

intmeasureSpec=MeasureSpec.makeMeasureSpec(redView.getLayoutParams().width,MeasureSpec.EXACTLY);//具体指定宽高,为精确模式

redView.measure(measureSpec,measureSpec);//当父控件测量完子控件,才可以填(0,0)

blueView.measure(measureSpec,measureSpec);

/*//方法二:

如果说没有特殊的对子View的测量需求,可用如下方法

measureChild(redView,widthMeasureSpec,heightMeasureSpec);

measureChild(blueView,widthMeasureSpec,heightMeasureSpec);*/

}

}

但是现在使DragLayout 类继承于FrameLayout即可!

publicclassDragLayoutextendsFrameLayout{

...

}

因为在自定义ViewGroup的时候,如果对子View的测量没有特殊的需求,那么可以继承系统已有的布局(比如FrameLayout、RelativeLayout),目的是为了让已有的布局帮我们实现onMeasure()。

所以在继承之后,我们无需实现onMeasure()方法,以上代码全部不需要(这里选择继承FrameLayout帧布局,原因是在Android源码中其实现最简单),所以重写继承的onLayout()方法其实是重写帧布局中的onLayout(),如果也注释的话,你会发现蓝色小方块覆盖红色,一起摆放在左上角(其实就是帧布局的摆放规则)

3.callback回调创建

privateViewDragHelper.Callbackcallback=newCallback(){

//必须要实现的方法

@Override

publicbooleantryCaptureView(Viewchild,intpointerId){

returnfalse;

}

};

4.触摸、拦截事件

以上部分ViewDragHelper的创建部分已完成,可是还没结束。

比如大家熟悉的一个类:

GestureDetector手势识别器,想要它生效,必须传一个触摸事件,这样GestureDetector类才可以解析当前手势。

道理相同,之前在介绍ViewDragHelper已提到,它只是一个对触摸事件的解析类,需要传一个触摸事件,才会生效。

//处理是否拦截

@Override

publicbooleanonInterceptTouchEvent(MotionEventev){

//由viewDragHelper来判断是否应该拦截此事件

booleanresult=viewDragHelper.shouldInterceptTouchEvent(ev);

returnresult;

}

@Override

publicbooleanonTouchEvent(MotionEventevent){

//将触摸事件传给viewDragHelper来解析处理

viewDragHelper.processTouchEvent(event);

//消费掉此事件,自己来处理

returntrue;

}

以上则viewDragHelper可以监视并解析我们的手势了,而且会把信息通过回调传递给callback。

5.处理computeScroll()

该方法是Scroller类的核心,系统在绘制View的时候在draw()中调用此方法,实际与scrollTo()相同。

@Override

publicvoidcomputeScroll(){

puteScroll();

if(puteScrollOffset()){

scrollTo(scroller.getCurrX(),scroller.getCurrY());

invalidate();

}

如上,Scroller类提供computeScrollOffset()方法来判断是否完成了整个滑动,同时getCurrX()和getCurrY()来获得当前滑动坐标。

重点是invalidate()方法,因为只能在computeScroll()方法中获取模拟过程中的scrollX 和 scrollY,但computeScroll()方法是不会自动调用的,只能通过invalidate()—>draw()—>computeScroll()来间接调用computeScroll()方法!

模拟过程结束,if判断中computeScrollOffset()方法返回false,中断循环,完成整个平滑移动过程!

但是!

我们并不采取以上方法,之前介绍过ViewDragHelper已经封装好了Scroller,用另外一种:

@Override

publicvoidcomputeScroll(){

puteScroll();

if(viewDragHelper.continueSettling(true)){

ViewCompat.postInvalidateOnAnimation(DragLayout.this);

}

}

}

continueSettling()方法判断是否结束,同Scroller的方法相似,主要是postInvalidateOnAnimation(),此方法不像Scroller的scrollTo,还需要传值,其实此方法体内已经封装好移动的方法,它会自动去测量当前位置进行移动,所以我们只需调用即可!

(在手指抬起时回调的方法中也会用到它,后面介绍)

6.实现callback回调中的方法

之前在创建callback时,默认只实现了tryCaptureView()方法,完成需求仅仅不够,还需要其它方法,依次介绍:

(1)tryCaptureView()

此方法用于判断是否捕获当前child的触摸事件,可以指定ViewDragHelper移动哪一个子View。

此例中,需要移动两个方块,则判断当前View是否是自己想移动的,返回boolean值。

/**用于判断是否捕获当前child的触摸事件

*@paramchild当前触摸的子View

*@returntrue:

捕获并解析false:

不处理

*/

@Override

publicbooleantryCaptureView(Viewchild,intpointerId){

returnchild==blueView||child==redView;

}

(2)onViewCaptured()

此方法在View被开始捕获和解析时回调,即当tryCaptureView()中的返回值为true的时候,此方法才会被调用。

例如tryCaptureView()方法中只捕获红色方块,当移动红方快时,该方法会回调,移动蓝色方块时则不会!

/**当View被开始捕获和解析的回调(用处不大)

*@paramcapturedChild当前被捕获的子View

*/

@Override

publicvoidonViewCaptured(ViewcapturedChild,intactivePointerId){

super.onViewCaptured(capturedChild,activePointerId);

Log.e("tag","onViewCaptures");

}

(3)clampViewPositionHorizontal()和clampViewPositionVertical()

这两个为具体滑动方法,分别对应水平和垂直方向上的移动。

要想子View移动,此方法必须重写实现!

而方法的返回值则是指定View在水平(left)或垂直(top)方向上变成的值,参数中的dx、dy则是代表相较于上一次位置的增量。

/**控制child在水平方向的移动

*@paramchild

*@paramleftViewDragHelper会将当前child的left值改变成返回的值

*@paramdx相较于上一次child在水平方向上移动的

*@return

*/

@Override

publicintclampViewPositionHorizontal(Viewchild,intleft,intdx){

returnleft;

}

/**控制child在垂直方向的移动

*@paramchild

*@paramtopViewDragHelper会将当前child的top值改变成返回的值

*@paramdy相较于上一次child在水平方向上移动的

*@return

*/

@Override

publicintclampViewPositionVertical(Viewchild,inttop,intdy){

returntop;

}

};

显示效果:

 

通过以上GIF动图和日志打印可以看出,仅将返回值设置成方法中的参数,方块就可以任意移动了。

也证实了方法中提供的参数而dx或dy是每一次移动的距离,left或top是指定View移动到的位置,这是计算好了的,相当于left=child.getLeft()+dx。

 

若想要它不移动,则:

 

returnleft-dx; 

将它计算好后的距离减去相较于上次移动的距离即可,此时的View就不会移动。

所以根据你的需求,可以任意改变此方法的返回值来移动View。

(4)getViewHorizontalDragRange()和getViewVerticalDragRange()

看到以上GIF动图,你会发现我在移动方块时,它可以超过边界,没有任何限制!

有些不合理,想限制它的移动范围,这两个方法就可以获取View的拖拽范围,将它的返回值设为:

父控件的宽/高-子控件的宽/高,即控件可以移动的范围。

//获取View水平方向的拖拽范围

@Override

publicintgetViewHorizontalDragRange(Viewchild){

returngetMeasuredWidth()-child.getMeasuredWidth();

}

//获取View垂直方向的拖拽范围

@Override

publicintgetViewVerticalDragRange(Viewchild){

returngetMeasuredHeight()-child.getMeasuredHeight();

}

可是以上实现后,你会发现拖拽方块还是可以超出边界,此方法并没有起作用!

是否代表此方法完全无用?

这返回的值有何用?

不是,它目前确实并不可以限制边界,但此方法返回的值会用在:

比如说手指抬起时,View缓慢移动的动画时间的计算会用到此值,最好不要返回0(返回也不会错)!

但是我们还想要达到限制View拖拽边界的效果,这时在第三点介绍的clampViewPositionHorizontal()和clampViewPositionVertical()发挥效果了,该方法是通过返回值来改变View移动的位置,这时可以在方法中加判断是否有越界:

@Override

publicintclampViewPositionHorizontal(Viewchild,intleft,intdx){

if(left<0){

//限制左边界

left=0;

}elseif(left>(getMeasuredWidth()-child.getMeasuredWidth())){

//限制右边界

left=getMeasuredWidth()-child.getMeasuredWidth();

}

returnleft;

}

显示效果:

 

(5)onViewPositionChanged()

目前为止,需求已经完成可以任意拖拽View了,接下来完成拖拽View时,另一块跟随移动。

这时介绍一个新的方法:

onViewPositionChanged(),该方法在child(需要捕捉的View)位置改变时执行,参数left(top)跟之前介绍方法中含义相同,为child最新的left(top)位置,而dx(dy)是child相较于上一次移动时水平(垂直)方向上改变的距离。

了解之后,就知道这个方法很强大了,在方法体中判断具体View,再根据方法提供的参数设置另一View的位置,如下:

/**当child位置改变时执行

*@paramchangedView位置改变的子View

*@paramleftchild最新的left位置

*@paramtopchild最新的top位置

*@paramdx相较于上一次水平移动的距离

*@paramdy相较于上一次垂直移动的距离

*/

@Override

publicvoidonViewPositionChanged(ViewchangedView,intleft,inttop,intdx,intdy){

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

if(changedView==blueView){

//拖动蓝色方块时,红色也跟随移动

redView.layout(redView.getLeft()+dx,redView.getTop()+dy,

redView.getRight()+dx,redView.getBottom()+dy);

}elseif(changedView==redView){

//拖动红色方块时,蓝色也跟随移动

blueView.layout(blueView.getLeft()+dx,blueView.getTop()+dy,

blueView.getRight()+dx,blueView.getBottom()+dy);

}

}

显示效果:

 

(6)onViewReleased()

完成目前需求第三个:

手指在左边(右边)区域离开屏幕后,方块自动移动到左边界(右边界)。

接下来介绍最后一个方法onViewReleased(),手指抬起的时候执行该方法。

这里有两个新参数:

 

xvel:

 x方向移动的速度,若是正值,则代表向右移动,若是负值则向左移动; 

yvel:

 y方向移动的速度,若是正值则向下移动,若是负值则向上移动。

/**手指抬起的时候执行该方法

*@paramreleasedChild当前抬起的View

*@paramxvelx方向移动的速度:

正值:

向右移动负值:

向左移动

*@paramyvely方向移动的速度:

正值:

向下移动负值:

向上移动

*/

@Override

publicvoidonViewReleased(ViewreleasedChild,floatxvel,floatyvel){

super.onViewReleased(releasedChild,xvel,yvel);

intcenterLeft=getMeasuredWidth()/2-releasedChild.getMeasuredWidth()/2;

if(releasedChild.getLeft(

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 哲学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1