android view 常用的6种 View 的滑动方法

御弟哥哥 等级 804 0 0

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,它会随着我们手指的滑动改变自己的位置。
          android view 常用的6种 View 的滑动方法
                 图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 所示。
  android view 常用的6种 View 的滑动方法
                      图2 初始情况
  画布上有3 个控件,即Button、EditText 和SwichButton。只有Button 在手机屏幕中显示,它的Android 坐标为(60,60) 。现在我们调用scrollBy(50,50),按照字面的意思,这个Button 应该会在屏幕右下侧,可是事实并非如此。如果我们调用scrollBy(50,50),里面的参数都是正值,我们的手机屏幕向X 轴正方向,也就是向右边平移50,然后手机屏幕向Y 轴正方向,也就是向下方平移50,平移后的效果如图3所示。
  android view 常用的6种 View 的滑动方法
                  图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()方法,这样我们通过不断地移动一个小的距离并连贯起来就实现了平滑移动的效果。

@Override
  public 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进阶之光》,点此链接可在博文视点官网查看此书。

收藏
评论区

相关推荐

android view 常用的6种 View 的滑动方法
View 的滑动是Android 实现自定义控件的基础,实现View 滑动有很多种方法,在这里主要讲解6 种滑动方法,分别是layout()、offsetLeftAndRight()与offsetTopAndBottom()、LayoutParams、动画、scollTo 与scollBy,以及Scroller。   View 的滑动是Android
Android-自定义view
要自定义view,都知道有3个方法需要重写:onMeasure、onLayout、onDraw。而且这三个方法的执行是按顺序的。 生命周期image.png 实际开发中,比较多的自定义都是具体实现一个view的子类,实现viewgroup的子类比较少,两者基本相似,区别就是view需要实现onMeasure、onLayout、onDraw三个方法,而vie
Android输入系统(一)输入事件传递流程和InputManagerService的诞生
Android框架层 Android输入系统 Android框架层本文首发于微信公众号「刘望舒」 前言很多同学可能会认为输入系统是不是和View的事件分发有些关联,确实是有些关联,只不过View事件分发只能算是输入系统事件传递的一部分。这个系列讲的输入系统主要是我们不常接触的,但还是需要去了解的那部分。 1. 输入事件传递流程的组成部分输入系统是外界与And
Android窗口管理框架:Android应用视图的管理者Window
Android窗口管理框架:Android应用视图的管理者Window文章目录 一 窗口类型 二 窗口参数 三 窗口模式 四 窗口回调 五 窗口实现从这篇文章开始,我们来分析和Window以及WindowManager相关的内容,Abstract base class for a toplevel window look and behavior polic
Android View转换成图片保存
package zhangphil.viewtoimage; import java.io.File; import java.io.FileOutputStream; import android.os.Bundle; import android.os.Environment; impo
Android自定义ViewGroup:onMeasure与onLayout(1)
**Android自定义ViewGroup:onMeasure与onLayout(1)** Android自定义一个ViewGroup,需要重写ViewGrouo里面的两个最重要的回调函数onMeasure()与onLayout()。如果开发者自己摆脱Android为我们做好的几套布局(如常见的线1性布局、相对布局、帧布局等等),往底层实现vi
2019年Android岗位BAT等大厂面试题,希望对新的一年的你有所帮助
2019年Android岗位BAT等大厂面试题知识点小结 ============================ 2019年了搜集了很多面试题,希望能对大家有所帮助 **1.View的绘制流程;自定义View如何考虑机型适配;自定义View的事件分发机制;View和ViewGroup分别有哪些事件分发相关的回调方法;自定义View如何提供获取View属
2019年Android岗位BAT等大厂面试题,希望对新的一年的你有所帮助
2019年Android岗位BAT等大厂面试题知识点小结 ============================ 2019年了搜集了很多面试题,希望能对大家有所帮助 **1.View的绘制流程;自定义View如何考虑机型适配;自定义View的事件分发机制;View和ViewGroup分别有哪些事件分发相关的回调方法;自定义View如何提供获取View属
Android webview获取html的内容
  Android用文本view加载HTML时,可以首先获取到html的内容,然后对html做自己想要的效果调整     [@Override](https://my.oschina.net/u/1162528)     protected void onCreate(Bundle savedInstanceState) {         supe
Android中不规则形状View的布局实现
在Android中不管是View还是ViewGroup,都是方的! 方的! 方的! 而对于非方形的,Android官方并没有给出非常好的解决方案.有的无非就是自定义View了. 然而自定义View非常麻烦,需要重写很多方法,而且稍微不注意可能就会丧失一些特性或者造成一些Bug. 而且即便是自定义View,其实那个自定义View还是方的!!!,自定义V
Android之自定义View学习(二)
Android学习系列 =========== [Android之Room学习](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fblog.csdn.net%2FLInthunder%2Farticle%2Fdetails%2F108280542) --------------------
Android学习笔记(二) 布局方式的介绍
Android应用的开发的一项内容就是用户界面开发了。Android 提供了大量功能丰富的UI组件。Android的界面是由布局和组件协同完成的。 ![](http://static.oschina.net/uploads/space/2014/0304/213204_usD8_865771.gif)  Android所有UI组件都继承了View
Android添加横线和竖线分割界面
竖线 <View       android:layout\_width="1dip"      android:layout\_height="match\_parent"     android:background="#66CCFF"     android:layout\_gravity="center\_horizontal"
Android的Surface的创建
`ViewRootImpl`管理着整个view tree。 对于`ViewRootImpl.setView()`,我们可以简单的把它当做一个`UI渲染操作`的入口。 [http://androidxref.com/6.0.1\_r10/xref/frameworks/base/core/java/android/view/WindowManagerImpl
GifView
GifView 是一个为了解决android中现在没有直接显示gif的view,只能通过mediaplay来显示这个问题的项目,其用法和 ImageView一样,支持gif图片 使用方法: 1-把GifView.jar加入你的项目。 2-在xml中配置GifView的基本属性,GifView继承自View类,和Button、ImageView一样是一个