如上图简单呈现出两个方块后,提出一个需求:
  
  1.拖动方块时,方块(即子View)可以跟随手指移动。
  
  2.一个方块移动时,另一个方块可以跟随移动。
  
  3.将方块移动到左边区域(右边区域)后放开(即手指离开屏幕),它会自动移动到左边界(右边界)。
  
  4.移动的时候给方块加点动画(duang~duang~duang~) 。
  
  View移动的相关方法总结:
  
  1. layout
  
  在自定义控件中,View绘制的一个重写方法layout(),用来设置显示的位置。所以,可以通过修改View的坐标值来改变view在父View的位置,以此可以达到移动的效果!但是缺点是只能移动指定的View:
  
  //通过layout方法来改变位置
  
  view.layout(l,t,r,b);
  
  1
  
  2
  
  1
  
  2
  
  2.offsetLeftAndRight() 和 offsetTopAndBottom()
  
  非常方便的封装方法,只需提供水平、垂直方向上的偏移量,展示效果与layout()方法相同。
  
  view.offsetLeftAndRight(offset);//同时改变left和right
  
  view.offsetTopAndBottom(offset);//同时改变top和bottom
  
  1
  
  2
  
  1
  
  2
  
  3. LayoutParams
  
  此类保存了一个View的布局参数,可通过LayoutParams动态改变一个布局的位置参数,以此动态地修改布局,达到View位置移动的效果!但是在获取getLayoutParams()时,要根据该子View对应的父View布局来决定自身的LayoutParams 。所以一切的前提是:必须要有一个父View,否则无法获取LayoutParams !
  
  //必须获取父View的LayoutParams
  
  LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)getLayoutParams();
  
  layoutParams.leftMargin = getLeft() + dx;
  
  layoutParams.topMargin = getTop() + dy;
  
  setLayoutParams(layoutParams);
  
  1
  
  2
  
  3
  
  4
  
  5
  
  1
  
  2
  
  3
  
  4
  
  5
  
  4. scrollTo 和 scrollBy
  
  通过改变scrollX和scrollY来移动,但是可以移动所有的子View。scrollTo(x,y)表示移动到一个具体的坐标点(x,y),而scrollBy(x,y)表示移动的增量为dx,dy。
  
  scrollTo(x,y);
  
  scrollBy(xOffset,yOffset);
  
  1
  
  2
  
  1
  
  2
  
  注意:这里使用scrollBy(xOffset,yOffset);,你会发现并没有效果,因为以上两个方法移动的是View的content。若在ViewGroup中使用,移动的是所有子View;若在View中使用,移动的是View的内容(比如TextView)。
  
  所以,不可在view中使用以上方法!应该在View所在的ViewGroup中使用:
  
  ((View)getParent()).www.yyzx66.cn/ scrollBy(offsetX, offsetY);
  
  1
  
  1
  
  【视图坐标系】:
  
  这里写图片描述
  
  可是即使这样,你会发现view移动的效果与设想方向相反!这是Android试图移动原因,若参数为正值,cwww.lxinyul.cc ontent将向坐标轴负方向移动;参数为负值,content将向坐标轴正方向移动。所以要实现随手指移动而滑动的效果,应将偏移量设置为负值即可:
  
  ((View)getParent()).scrollBy(-offsetX, -offsetY);
  
  1
  
  1
  
  5. canvas
  
  通过改变Canvas绘制的位置来移动View的内容,用的少:
  
  canvas.drawBitmap(bitmap, left, top, paint)
  
  1
  
  1
  
  总结
  
  但是要完成最开始的提的需求,不管使用哪一种方法,都需要通过onTouchEvent方法来捕捉手势,自己手动计算移动距离,再改变子View的布局,不免有些麻烦,所以在这里引出正文,介绍一个强大的类来处理移动:ViewDragHelper
  
  ViewDragHelper介绍:
  
  1. 产生: ViewDragHelper在高版本的v4包(androiwww.senta7.net d4.4以上的v4)中,于Google在2013年开发者大会提出的
  
  2. 作用:它主要用于处理ViewGroup中对子View的拖拽处理。
  
  3. 使用:它主要封装了对View的触摸位置,触摸速度,移动距离等的检测和Scroller,通过接口回调的方式通知我们。所以我们需要做的只是用接收来的数据指定这些子View是否需要移动,移动多少等。
  
  4. 本质:是一个对触摸事件的解析类。
  
  ViewDragHelper实现
  
  1. ViewDragHelper实例创建
  
  /**
  
  * Factory method to create a new ViewDragHelper.
  
  *
  
  * @param forParent Parent view to monitor
  
  * @param sensitivity Multiplier for www.yinbaovip.cn how sensitive the helper should be about detecting
  
  *the start of a drag. Larger values are more sensitive. 1.0f is normal.
  
  * @param cb Callback to provide information www.wuxinvip.cn and receive events
  
  * @return a new ViewDragHelper instance
  
  */
  
  viewDragHelper = ViewDragHelper.create(forParent, sensitivity, cb);
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  create()就是创建ViewDragHelper实例的方法,代码中的注释是create()中参数的解释,来查看:
  
  (1)forParent :“用来监视的父View”。传入参数父View,即可监视该父View中的所有子View。
  
  (2)sensitivity:“检测时的敏感度;值越大越敏感,1是正常范围”。比如说手指在滑动屏幕时速度特别快,敏感度越大时,此时速度快也可以检测到,反之亦然。
  
  (3)Callback :“提供信息和接受的事件”。最重要的参数!可以从这个回调提供的信息获取到View滑动的距离、速度等。
  
  2. 自定义View继承FrameLayout
  
  这里来个小提示:之前实现的布局中自定义DragLayout是继承于ViewGroup,并且实现重写了onMeasure()方法,如下:
  
  DragLayout.java
  
  public class DragLayout extends ViewGroup{
  
  ...
  
  @Override
  
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  
  //方法一:对子View的测量需求
  
  /*获取子View的宽度100dp 的两种方法:
  
  int size = (int) getResources().getDimension(R.dimen.width);
  
  int size = readView.getLayoutParams().width;*/
  
  int measureSpec = 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);*/
  
  }
  
  }
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  11
  
  12
  
  13
  
  14
  
  15
  
  16
  
  17
  
  18
  
  19
  
  20
  
  21
  
  22
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  11
  
  12
  
  13
  
  14
  
  15
  
  16
  
  17
  
  18
  
  19
  
  20
  
  21
  
  22
  
  但是现在使DragLayout 类继承于FrameLayout即可!
  
  public class DragLayout extends FrameLayout{
  
  ...
  
  }
  
  1
  
  2
  
  3
  
  1
  
  2
  
  3
  
  因为在自定义ViewGroup的时候,如果对子View的测量没有特殊的需求,那么可以继承系统已有的布局(比如FrameLayout、RelativeLayout),目的是为了让已有的布局帮我们实现onMeasure()。
  
  所以在继承之后,我们无需实现onMeasure()方法,以上代码全部不需要(这里选择继承FrameLayout帧布局,原因是在Android源码中其实现最简单),所以重写继承的onLayout()方法其实是重写帧布局中的onLayout(),如果也注释的话,你会发现蓝色小方块覆盖红色,一起摆放在左上角(其实就是帧布局的摆放规则)
  
  3. callback回调创建
  
  private ViewDragHelper.Callback callback = new Callback() {
  
  //必须要实现的方法
  
  @Override
  
  public boolean tryCaptureView(View child, int pointerId) {
  
  return false;
  
  }
  
  };
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  4. 触摸、拦截事件
  
  以上部分ViewDragHelper的创建部分已完成,可是还没结束。比如大家熟悉的一个类:GestureDetector手势识别器,想要它生效,必须传一个触摸事件,这样GestureDetector类才可以解析当前手势。道理相同,之前在介绍ViewDragHelper已提到,它只是一个对触摸事件的解析类,需要传一个触摸事件,才会生效。
  
  //处理是否拦截
  
  @Override
  
  public boolean onInterceptTouchEvent(MotionEvent ev) {
  
  //由viewDragHelper 来判断是否应该拦截此事件
  
  boolean result = viewDragHelper.shouldInterceptTouchEvent(ev);
  
  return result;
  
  }
  
  @Override
  
  public boolean onTouchEvent(MotionEvent event) {
  
  //将触摸事件传给viewDragHelper来解析处理
  
  viewDragHelper.processTouchEvent(event);
  
  //消费掉此事件,自己来处理
  
  return true;
  
  }
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  11
  
  12
  
  13
  
  14
  
  15
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  11
  
  12
  
  13
  
  14
  
  15
  
  以上则viewDragHelper可以监视并解析我们的手势了,而且会把信息通过回调传递给callback。
  
  5. 处理computeScroll()
  
  该方法是Scroller类的核心,系统在绘制View的时候在draw()中调用此方法,实际与scrollTo()相同。
  
  @Override
  
  public void computeScroll() {
  
  super.computeScroll();
  
  if(scroller.computeScrollOffset()){
  
  scrollTo(scroller.getCurrX(),scroller.getCurrY());
  
  invalidate();
  
  }
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  如上,Scroller类提供computeScrollOffset()方法来判断是否完成了整个滑动,同时getCurrX()和getCurrY()来获得当前滑动坐标。
  
  重点是invalidate()方法,因为只能在computeScroll()方法中获取模拟过程中的scrollX 和 scrollY,但computeScroll()方法是不会自动调用的,只能通过invalidate() —> draw() —>computeScroll()来间接调用computeScroll()方法!模拟过程结束,if判断中computeScrollOffset()方法返回false,中断循环,完成整个平滑移动过程!
  
  但是!!!我们并不采取以上方法,之前介绍过ViewDragHelper已经封装好了Scroller,用另外一种:
  
  @Override
  
  public void computeScroll() {
  
  super.computeScroll();
  
  if(viewDragHelper.continueSettling(true)){
  
  ViewCompat.postInvalidateOnAnimation(DragLayout.this);
  
  }
  
  }
  
  }
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  continueSettling()方法判断是否结束,同Scroller的方法相似,主要是postInvalidateOnAnimation(),此方法不像Scroller的scrollTo,还需要传值,其实此方法体内已经封装好移动的方法,它会自动去测量当前位置进行移动,所以我们只需调用即可!(在手指抬起时回调的方法中也会用到它,后面介绍)
  
  6. 实现callback回调中的方法
  
  之前在创建callback时,默认只实现了tryCaptureView()方法 ,完成需求仅仅不够,还需要其它方法,依次介绍:
  
  (1) tryCaptureView()
  
  此方法用于判断是否捕获当前child的触摸事件,可以指定ViewDragHelper移动哪一个子View。此例中,需要移动两个方块,则判断当前View是否是自己想移动的,返回boolean值。
  
  /**用于判断是否捕获当前child的触摸事件
  
  * @param child 当前触摸的子View
  
  * @return true:捕获并解析 false:不处理
  
  */
  
  @Override
  
  public boolean tryCaptureView(View child, int pointerId) {
  
  return child == blueView || child == redView;
  
  }
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  (2) onViewCaptured()
  
  此方法在View被开始捕获和解析时回调,即当tryCaptureView()中的返回值为true的时候,此方法才会被调用。
  
  例如tryCaptureView()方法中只捕获红色方块,当移动红方快时,该方法会回调,移动蓝色方块时则不会!
  
  /** 当View被开始捕获和解析的回调(用处不大)
  
  * @param capturedChild 当前被捕获的子View
  
  */
  
  @Override
  
  public void onViewCaptured(View capturedChild, int activePointerId) {
  
  super.onViewCaptured(capturedChild, activePointerId);
  
  Log.e("tag","onViewCaptures");
  
  }
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  (3) clampViewPositionHorizontal() 和 clampViewPositionVertical()
  
  这两个为具体滑动方法,分别对应水平和垂直方向上的移动。要想子View移动,此方法必须重写实现!
  
  而方法的返回值则是指定View在水平(left)或垂直(top)方向上变成的值,参数中的dx、dy则是代表相较于上一次位置的增量。
  
  /** 控制child在水平方向的移动
  
  * @param child
  
  * @param left ViewDragHelper会将当前child的left值改变成返回的值
  
  * @param dx 相较于上一次child在水平方向上移动的
  
  * @return
  
  */
  
  @Override
  
  public int clampViewPositionHorizontal(View child, int left, int dx) {
  
  return left;
  
  }
  
  /**控制child在垂直方向的移动
  
  * @param child
  
  * @param top ViewDragHelper会将当前child的top值改变成返回的值
  
  * @param dy 相较于上一次child在水平方向上移动的
  
  * @return
  
  */
  
  @Override
  
  public int clampViewPositionVertical(View child, int top, int dy) {
  
  return top;
  
  }
  
  };
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  11
  
  12
  
  13
  
  14
  
  15
  
  16
  
  17
  
  18
  
  19
  
  20
  
  21
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  11
  
  12
  
  13
  
  14
  
  15
  
  16
  
  17
  
  18
  
  19
  
  20
  
  21
  
  显示效果:
  
  这里写图片描述
  
  这里写图片描述
  
  通过以上GIF动图和日志打印可以看出,仅将返回值设置成方法中的参数,方块就可以任意移动了。也证实了方法中提供的参数而dx或dy是每一次移动的距离,left或top 是指定View移动到的位置,这是计算好了的,相当于left = child.getLeft() + dx。
  
  若想要它不移动,则:
  
  return left - dx;
  
  将它计算好后的距离减去相较于上次移动的距离即可,此时的View就不会移动。所以根据你的需求,可以任意改变此方法的返回值来移动View。
  
  (4) getViewHorizontalDragRange() 和 getViewVerticalDragRange()
  
  看到以上GIF动图,你会发现我在移动方块时,它可以超过边界,没有任何限制!有些不合理,想限制它的移动范围,这两个方法就可以获取View的拖拽范围,将它的返回值设为:父控件的宽/高 - 子控件的宽/高,即控件可以移动的范围。
  
  //获取View水平方向的拖拽范围
  
  @Override
  
  public int getViewHorizontalDragRange(View child) {
  
  return getMeasuredWidth() - child.getMeasuredWidth();
  
  }
  
  // 获取View垂直方向的拖拽范围
  
  @Override
  
  public int getViewVerticalDragRange(View child) {
  
  return getMeasuredHeight() - child.getMeasuredHeight();
  
  }
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  可是以上实现后,你会发现拖拽方块还是可以超出边界,此方法并没有起作用!是否代表此方法完全无用?这返回的值有何用?
  
  不是,它目前确实并不可以限制边界,但此方法返回的值会用在:比如说手指抬起时,View缓慢移动的动画时间的计算会用到此值,最好不要返回0(返回也不会错)!
  
  但是我们还想要达到限制View拖拽边界的效果,这时在第三点介绍的clampViewPositionHorizontal() 和 clampViewPositionVertical()发挥效果了,该方法是通过返回值来改变View移动的位置,这时可以在方法中加判断是否有越界:

Android 中 View移动总结:ViewDragHelper学习及用法详解的更多相关文章

  1. Android中Intent传值与Bundle传值的区别详解

    Android中Intent传值与Bundle传值的区别详解 举个例子我现在要从A界面跳转到B界面或者C界面   这样的话 我就需要写2个Intent如果你还要涉及的传值的话 你的Intent就要写两 ...

  2. Python中第三方库Requests库的高级用法详解

    Python中第三方库Requests库的高级用法详解 虽然Python的标准库中urllib2模块已经包含了平常我们使用的大多数功能,但是它的API使用起来让人实在感觉不好.它已经不适合现在的时代, ...

  3. JavaScript中return的用法和this的用法详解

    JavaScript中return的用法详解 最近,跟身边学前端的朋友了解,有很多人对this和函数中的return的用法和意思理解的比较模糊,这里写一篇博客跟大家一起探讨一下return和this的 ...

  4. Android中Activity运行时屏幕方向与显示方式详解

    现在我们的手机一般都内置有方向感应器,手机屏幕会根据所处位置自动进行横竖屏切换(前提是未锁定屏幕方向).但有时我们的应用程序仅限在横屏或者竖屏状态下才可以运行,此时我们需要锁定该程序Activity运 ...

  5. Android中利用Camera与Matrix实现3D效果详解

    本文行文目录: 一.Camera与Matrix初步认识 二.Camera与Matrix旋转效果拆分介绍 三.Camera与Matrix实现立体3D切换效果 [csdn地址:http://blog.cs ...

  6. Android中@id与@+id区别和sharedUserId属性详解

    Android中的组件需要用一个int类型的值来表示,这个值也就是组件标签中的id属性值. id属性只能接受资源类型的值,也就是必须以@开头的值,例如,@id/abc.@+id/xyz等. 如果在@后 ...

  7. Android中源码Launcher主屏幕程序排列详解【安卓Launcher进化一】

    最近研究Lancher,从短信Mms的框架中过度到Launcher的bug和需求修改中,下面对launcher最简单的主屏幕程序的程序的布局的详 解,给读者一个入门的感觉,android的主屏幕一共分 ...

  8. Android中的Service与进程间通信(IPC)详解

    Service 什么是Service 在后台长期运行的没有界面的组件.其他组件可以启动Service让他在后台运行,或者绑定Service与它进行交互,甚至实现进程间通信(IPC).例如,可以让服务在 ...

  9. Android中APK签名工具之jarsigner和apksigner详解

    一.工具介绍 jarsigner是JDK提供的针对jar包签名的通用工具, 位于JDK/bin/jarsigner.exe apksigner是Google官方提供的针对Android apk签名及验 ...

随机推荐

  1. bash的多行注释

    :<<EOF 注释的代码... EOF 单行是#

  2. poj 3628 (搜索or背包)

    好久没看背包题目了!!!生疏了!!!! 这题是背包题!!!不过对于这题,解决方法还是搜索省时!!! 题意:第一行给你一个N和VV,接下来N行,每行一个数,求得是任选N个数组合求和,求组合的和大于VV而 ...

  3. sql语句书写升降序

    1, desc:降序,明天今天昨天,zyx...cba,.....321 sql语句:select * from SC_BackDiggingInfo where WriteDate<='201 ...

  4. js 设置cookie

    function GetCookieVal(offset) // 获得Cookie解码后的值 { var endstr = document.cookie.indexOf(";", ...

  5. WDCP是什么 关于WDCP的详细介绍

    WDCP是WDlinux Control Panel的简称,是一套用PHP开发的Linux服务器管理系统以及虚拟主机管理系统,,旨在易于使用Linux系统做为我们的网站服务器,以及平时对Linux服务 ...

  6. 即时通讯(IM)

    即时通讯(IM)功能是APP的重要功能之一,而开发好移动IM却绝非易事.通常来说,IM技术选型至少要解决以下问题:1. 协议选型 2. IM服务器选型 3. 对协议和服务器做相应修改,通常来说直接拿个 ...

  7. Windows下Memcached在.Net程序中的实际运用(从Memcached客户端Enyim的库的编译到实际项目运用)

    1.一点基础概念 2.获取EnyimMemcached客户端的源代码并编译出动态库 3.Memcached的服务器安装(windows server) 4.在web项目中实战 一.基础概念 memca ...

  8. Android(java)学习笔记185:xml文件生成

    1.xml文件: 用元素描述数据,跨平台. 2.利用传统的方式创建xml文件,下面是一个案例: 设计思路:建立一个学生管理系统,创建xml文件保存学生信息: (1)首先是布局文件activity_ma ...

  9. 以非root权限安装nginx及运行

    本章主要讲如何在无root权限(包含无sudo权限)条件下于centos命令行中安装nginx以及在大于1024的端口(这里用8080)上运行. 1. 安装 两种方式,一是下载预编译好的rpm包安装, ...

  10. 使用Git操作GitHub代码入门教程

    GitHub除了网页操作外,还可以借助本地客户端git(或github for windows)来增删修改远程代码.使用Git操作来连接GitHub可以通过Https或SSH方式,使用SSH方式可以免 ...