View 的滑动是Android 实现自定义控件的基础,实现View 滑动有很多种方法,在这里主要讲解6 种滑动方法,分别是layout()、offsetLeftAndRight()与offsetTopAndBottom()、LayoutParams、动画、scollTo 与scollBy,以及Scroller。
  View 的滑动是Android 实现自定义控件的基础,同时在开发中我们也难免会遇到View 的滑动处理。其实不管是哪种滑动方式,其基本思想都是类似的:当点击事件传到View 时,系统记下触摸点的坐标,手指移动时系统记下移动后触摸的坐标并算出偏移量,并通过偏移量来修改View 的坐标。实现View 滑动有很多种方法,在这里主要讲解6 种滑动方法,分别是layout()、offsetLeftAndRight()与offsetTopAndBottom()、LayoutParams、动画、scollTo 与scollBy,以及Scroller。

1 layout()方法

  View 进行绘制的时候会调用onLayout()方法来设置显示的位置,因此我们同样也可以通过修改View 的left、top、right、bottom 这4 种属性来控制View 的坐标。首先我们要自定义一个View,在onTouchEvent()方法中获取触摸点的坐标,代码如下所示:

public boolean onTouchEvent(MotionEvent event) {//获取手指触摸点的横坐标和纵坐标int x = (int) event.getX();int y = (int) event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:lastX = x;lastY = y;break;

  接下来我们在ACTION_MOVE 事件中计算偏移量,再调用layout()方法重新放置这个自定义View 的位置即可。

case MotionEvent.ACTION_MOVE://计算移动的距离int offsetX = x - lastX;int offsetY = y - lastY;//调用layout 方法来重新放置它的位置layout(getLeft()+offsetX, getTop()+offsetY,getRight()+offsetX , getBottom()+offsetY);break;

  在每次移动时都会调用layout()方法对屏幕重新布局,从而达到移动View 的效果。自定义View,CustomView 的全部代码如下所示:

public class CustomView extends View {private int lastX;private int lastY;public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}public CustomView(Context context, AttributeSet attrs) {super(context, attrs);}public CustomView(Context context) {super(context);}public boolean onTouchEvent(MotionEvent event) {//获取手指触摸点的横坐标和纵坐标int x = (int) event.getX();int y = (int) event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:lastX = x;lastY = y;break;case MotionEvent.ACTION_MOVE://计算移动的距离int offsetX = x - lastX;int offsetY = y - lastY;//调用layout 方法来重新放置它的位置layout(getLeft()+offsetX, getTop()+offsetY,getRight()+offsetX , getBottom()+offsetY);break;}return true;}
}

  随后,我们在布局中引用自定义View 就可以了。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><com.example.liuwangshu.moonviewslide.CustomView
    android:id="@+id/customview"android:layout_width="80dp"android:layout_height="80dp"android:layout_margin="50dp"android:background="@android:color/holo_red_light" />
</LinearLayout>

  运行程序,效果如图1 所示。图1 中的方块就是我们自定义的CustomView,它会随着我们手指的滑动改变自己的位置。
          
                 图1 View 的滑动

2 offsetLeftAndRight()与offsetTopAndBottom()

  这两种方法和layout()方法的效果差不多,其使用方式也差不多。我们将ACTION_MOVE中的代码替换成如下代码:

case MotionEvent.ACTION_MOVE://计算移动的距离int offsetX = x - lastX;int offsetY = y - lastY;//对left 和right 进行偏移offsetLeftAndRight(offsetX);//对top 和bottom 进行偏移offsetTopAndBottom(offsetY);break;

3 LayoutParams(改变布局参数)

  LayoutParams 主要保存了一个View 的布局参数,因此我们可以通过LayoutParams 来改变View 的布局参数从而达到改变View 位置的效果。同样,我们将ACTION_MOVE 中的代码替换成如下代码:

LinearLayout.LayoutParams layoutParams= (LinearLayout.LayoutParams)getLayoutParams();layoutParams.leftMargin = getLeft() + offsetX;layoutParams.topMargin = getTop() + offsetY;setLayoutParams(layoutParams);

  因为父控件是LinearLayout,所以我们用了LinearLayout.LayoutParams。如果父控件是RelativeLayout,则要使用RelativeLayout.LayoutParams。除了使用布局的LayoutParams 外,我们还可以用ViewGroup.MarginLayoutParams 来实现:

ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)
getLayoutParams();layoutParams.leftMargin = getLeft() + offsetX;layoutParams.topMargin = getTop() + offsetY;setLayoutParams(layoutParams);

4 动画

  可以采用View 动画来移动,在res 目录新建anim 文件夹并创建translate.xml:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"><translate
    android:duration="1000"android:fromXDelta="0"android:toXDelta="300" />
</set>

  接下来在Java 代码中调用就好了,代码如下所示:

mCustomView.setAnimation(AnimationUtils.loadAnimation(this, R.anim.translate));

  运行程序,我们设置的小方块会向右平移300 像素,然后又会回到原来的位置。为了解决这个问题,我们需要在translate.xml 中加上fillAfter=”true”,代码如下所示。运行代码后会发现,方块向右平移300 像素后就停留在当前位置了。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" android:fillAfter="true"><translate
    android:duration="1000"android:fromXDelta="0"android:toXDelta="300" />
</set>

  需要注意的是,View 动画并不能改变View 的位置参数。如果对一个Button 进行如上的平移动画操作,当Button 平移300 像素停留在当前位置时,我们点击这个Button 并不会触发点击事件,但在我们点击这个Button 的原始位置时却触发了点击事件。对于系统来说这个Button 并没有改变原有的位置,所以我们点击其他位置当然不会触发这个Button 的点击事件。在Android3.0 时出现的属性动画解决了上述问题,因为它不仅可以执行动画,还能够改变View 的位置参数。当然,这里使用属性动画移动那就更简单了,我们让CustomView 在1000ms 内沿着X 轴向右平移300 像素,代码如下所示。

ObjectAnimator.ofFloat(mCustomView,"translationX",0,300).setDuration(100
0).start();

5 scrollTo 与scollBy

  scrollTo(x,y)表示移动到一个具体的坐标点,而scrollBy(dx,dy)则表示移动的增量为dx、dy。其中,scollBy 最终也是要调用scollTo 的。View.java 的scollBy 和scollTo 的源码如下所示:

public void scrollTo(int x, int y) {if (mScrollX != x || mScrollY != y) {int oldX = mScrollX;int oldY = mScrollY;mScrollX = x;mScrollY = y;invalidateParentCaches();onScrollChanged(mScrollX, mScrollY, oldX, oldY);if (!awakenScrollBars()) {postInvalidateOnAnimation();}}
}public void scrollBy(int x, int y) {scrollTo(mScrollX + x, mScrollY + y);
}

scollTo、scollBy 移动的是View 的内容,如果在ViewGroup 中使用,则是移动其所有的子View。我们将ACTION_MOVE 中的代码替换成如下代码:

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

  这里若要实现CustomView 随手指移动的效果,就需要将偏移量设置为负值。为什么要设置为负值呢?下面具体讲解一下。假设我们正用放大镜来看报纸,放大镜用来显示字的内容。同样我们可以把放大镜看作我们的手机屏幕,它们都是负责显示内容的;而报纸则可以被看作屏幕下的画布,它们都是用来提供内容的。放大镜外的内容,也就是报纸的内容不会随着放大镜的移动而消失,它一直存在。同样,我们的手机屏幕看不到的视图并不代表其不存在,如图2 所示。
  
                      图2 初始情况
  画布上有3 个控件,即Button、EditText 和SwichButton。只有Button 在手机屏幕中显示,它的Android 坐标为(60,60) 。现在我们调用scrollBy(50,50),按照字面的意思,这个Button 应该会在屏幕右下侧,可是事实并非如此。如果我们调用scrollBy(50,50),里面的参数都是正值,我们的手机屏幕向X 轴正方向,也就是向右边平移50,然后手机屏幕向Y 轴正方向,也就是向下方平移50,平移后的效果如图3所示。
  
                  图3 调用scrollBy(50,50)后

  虽然我们设置的数值是正数并且在X 轴和Y 轴的正方向移动,但Button 却向相反方向移动了,这是参考对象不同导致的差异。所以我们用scrollBy 方法的时候要设置负数才会达到自己想要的效果。

6 Scroller

  我们在用scollTo/scollBy 方法进行滑动时,这个过程是瞬间完成的,所以用户体验不大好。这里我们可以使用Scroller 来实现有过渡效果的滑动,这个过程不是瞬间完成的,而是在一定的时间间隔内完成的。Scroller 本身是不能实现View 的滑动的,它需要与View 的computeScroll()
方法配合才能实现弹性滑动的效果。在这里我们实现CustomView 平滑地向右移动。首先我们要初始化Scroller,代码如下所示:

public CustomView(Context context, AttributeSet attrs) {super(context, attrs);mScroller = new Scroller(context);
}

  接下来重写computeScroll()方法,系统会在绘制View 的时候在draw()方法中调用该方法。在这个方法中,我们调用父类的scrollTo()方法并通过Scroller 来不断获取当前的滚动值,每滑动一小段距离我们就调用invalidate()方法不断地进行重绘,重绘就会调用computeScroll()方法,这样我们通过不断地移动一个小的距离并连贯起来就实现了平滑移动的效果。

@Overridepublic void computeScroll() {super.computeScroll();if(mScroller.computeScrollOffset()){((View) getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());invalidate();}}

  我们在CustomView 中写一个smoothScrollTo 方法,调用Scroller 的startScroll()方法,在2000ms 内沿X 轴平移delta 像素,代码如下所示:

public void smoothScrollTo(int destX,int destY){int scrollX=getScrollX();int delta=destX-scrollX;mScroller.startScroll(scrollX,0,delta,0,2000);invalidate();
}

  最后我们在ViewSlideActivity.java 中调用CustomView 的smoothScrollTo()方法。这里我们设定CustomView 沿着X 轴向右平移400 像素。

mCustomView.smoothScrollTo(-400,0);

 
  本文选自《Android进阶之光》,点此链接可在博文视点官网查看此书。
  想及时获得更多精彩文章,可在微信中搜索“博文视点”或者扫描下方二维码并关注。
                  

解析6种常用View 的滑动方法相关推荐

  1. 几种常用的像素混合方法

    前两天为大家介绍了处理透明光影效果的 Alpha-Blending 技术,今次我将再为大家介绍其它几种常用的像素混合方法,这些方法一般在游戏中被用来处理光影效果. 『Alpha-Blending』 前 ...

  2. 10种常用的网络营销方法

    网络营销产生于20世纪90年代,发展至今已演变出越来越多的营销方法,在国内随着互联网影响的进一步扩大,人们对网络营销认知的进一步加深,网络营销方法手段也是各种推陈出新,下面就介绍如今网络营销最常用的1 ...

  3. **10种常用的网络营销方法**

    **10种常用的网络营销方法** 网络营销产生于20世纪90年代,发展至今已演变出越来越多的营销方法,在国内随着互联网影响的进一步扩大,人们对网络营销认知的进一步加深,网络营销方法手段也是各种推陈出新 ...

  4. 计算机中十二种常用密码的破解方法(转)

    计算机中十二种常用密码的破解方法(转)[@more@] 在日常操作中,我们经常要输入各种各样的密码,例如开机时要输入密码,QQ时也要先输入密码,假如你忘记了这些密码,就有可能用不了机器.打不开文件.不 ...

  5. 特征工程:8种常用类别型数据处理方法

    8种常用类别型数据处理方法

  6. 几种常用时钟分频实现方法

    在我们学习中,常常需要对时钟进行分频处理,本文将介绍几种常用分频方法. 一.2的整数次幂分频 这种分频很简单,只需要设置一个计数器,对计数器进行计数,计数器的第i位则对应的2的i-1次幂分频.此方法适 ...

  7. 产品经理懂点技术:几种常用的系统开发方法

    常用的系统开发方法有: 结构化系统开发方法 原型法 面向对象方法 计算机辅助软件工程方法 下面一一为大家详细介绍: 1 结构化系统开发方法 结构化系统开发方法(Structured System An ...

  8. 几种常用的数组遍历方法

    整理一下常用的几种遍历方法,for和while循环就不再说了,说一下其他几种非常好用的方法 1.for in  for in可定是接触比较早的遍历数组的方法,其实它也可遍历对象(遍历字符串也是可以的! ...

  9. 几种常用的文件加密方法

    下文是中软通用产品研发中心陈尚义根据http://blog.sina.com.cn/s/blog_611c36a80100gch7.html?tj=1编译. 每个人的电脑硬盘中都会有一些个人隐私或秘密 ...

  10. 总结几种常用的单片机加密方法

    总结前人的经验,摸索着写一点实用的方法,供大家参考:在这里不讲加密算法,只讨论结合软硬件的加密方法,总结了一下大该有以下几种: 一.加密方法 1.烧断数据总线.这个方法我想应不错,但应有损坏的风险,听 ...

最新文章

  1. 人脸标记检测:ICCV2019论文解析
  2. 计算机技能测试小学老师,小学信息技术教师专业技能测习题-20210726171728.docx-原创力文档...
  3. 类数组arguments
  4. 【图像处理】——Python+opencv实现二值图像的轮廓边界跟踪以及轮廓面积周长的求解(findcontours函数和contourArea函数)
  5. 如何下载python安装包的所有依赖_如何将包含所有依赖项的python包安装到Docker镜像中?...
  6. node js、npm、homebrew、cocoapod、git、hexo
  7. Unity制作的照片墙效果
  8. linux分区挂载到内存,ubuntu下SD卡分区与挂载
  9. 用阿里云建站模版套餐云速成美站有没有可能不容易被seo抓取排名?
  10. 手风琴 html 左边,Layui手风琴左边的符号如何修改
  11. 洛谷 P1873 [COCI 2011/2012 #5] EKO / 砍树
  12. Git暂存区有什么用
  13. java自动发图文微博_使用node搭建自动发图文微博机器人的方法
  14. zabbix安装(详细)本文没有截图看我网盘文档有图
  15. 照片转化成二维图片怎么做?图片内容如何生成二维码?
  16. facebook 邀请好友
  17. 上传图片,MultipartHttpServletRequest 2020-09-07
  18. isnan java_从 Number.isNaN 与 isNaN 的区别说起 例子
  19. python神经网络训练完了怎么输入得到结果_python神经网络读书笔记
  20. 老乡鸡上市获反馈:毛利率持续下降,多次被罚,食品安全隐患凸显

热门文章

  1. SEO是什么?SEO的作用?SEO的推广特点?
  2. JavaScript 函数 对象 数组
  3. magento Service Temporarily Unavailable
  4. 修改Imdict做自己的分词器
  5. linux vi 编辑命令
  6. 使用libevhtp搭建HTTPS SERVER(双向验证身份)
  7. 安装adt-bundle-windows-x86-20130917时遇到的问题及解决方法
  8. 数理统计基本原理复习
  9. Docker国内Yum源和国内镜像仓库
  10. 【BZOJ4503】两个串(FFT)