安卓开发博客Android自定义View

View 的滑动学习

2018-07-24  本文已影响172人  Eren丶耶格尔

目的

这篇文章主要对 View 的滑动进行学习,主要包括滑动的实现方式和弹性滑动提高用户体验


(一)View 的滑动

滑动的方式基本思想都是类似的:
当触摸事件传到View时,系统记下触摸点的坐标,手指移动时系统记下移动后的触摸的坐标并算出偏移量,并通过偏移量来修改View的坐标。

View 实现滑动的几种方式:

1. layout() 方法

View 进行绘制的时候会调用 onLayout() 方法来设置显示的位置,因此我们同样也可以通过修改 View 的属性来控制 View 的坐标

public class RedView extends View {

    int lastX, lastY;

    public RedView(Context context) {
        super(context);
    }

    public RedView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public RedView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    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(offsetX + getLeft(), offsetY + getTop(), offsetX + getRight(), offsetY + getBottom());
                break;
        }
        return true;
    }
}
2. offsetLeftAndRight() 与 offsetTopAndBottom()

将 ACTION_MOVE 中的代码替换

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

LayoutParams 主要保存了一个 View 的布局参数,因此我们可以通过改变参数从而达到改变 View 位置的效果

    case MotionEvent.ACTION_MOVE:
        //计算偏移量
        int offsetX = x - lastX;
        int offsetY = y - lastY;
        
        LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
        layoutParams.leftMargin = getLeft() + offsetX;
        layoutParams.topMargin = getTop() + offsetY;
        setLayoutParams(layoutParams);
        break;

需要注意的是 LinearLayout 是 View 的父容器,如果父容器是 RelativeLayout,则要使用 RelativeLayout.LayoutParams
当然我们可以使用 ViewGroup.MarginLayoutParams 来实现,一劳永逸

    case MotionEvent.ACTION_MOVE:
        //计算偏移量
        int offsetX = x - lastX;
        int offsetY = y - lastY;
        
        ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
        layoutParams.leftMargin = getLeft() + offsetX;
        layoutParams.topMargin = getTop() + offsetY;
        setLayoutParams(layoutParams);
        break;
4. 动画

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

<?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>
mView.setAnimation(AnimationUtils.loadAnimation(this, R.anim.translate));

在 Java 代码中执行动画,这里所用的是补间动画,虽然 View 位置发生改变,但是对系统来说,View 并没有发生改变,点击事件并不会发生。Android3.0 之后,可以通过属性动画来解决这个问题

ObjectAnimator.ofFloat(mView, "translationX", 0, 300).setDuration(1000).start();
5. scrollTo 与 scrollBy

scollTo(x, y) 表示移动到一个具体的坐标点,而 scollBy(dx, dy) 则表示移动的增量为 dx、dy。
两个方法区别: scrollBy() 相对移动,scrollTo() 绝对移动

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

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

这里要实现 mView 随着我们手指移动的效果的话,我们就需要将偏移量设置为负值。

在View内部有两个属性 mScrllX 和 mScrollY,分别可以通过 getScrollX() 和 getScrollY() 方法得到

在滑动过程中,mScrollX 总是等于 View 左边缘和 View 内容左边缘在水平方方向的距离;mScrollY 总是等于 View 上边缘和 View 中内容上边缘在竖直方向的距离。View 边缘是指 View 的位置也就是 View 的四个顶点到父容器的距离,View 内边缘是内容距离 View 四边的距离。

无论是 scrollTo() 还是 scrollBy() 都只能改变 View 内容的位置而不能改变 View 在布局中的位置

mScrollX/Y 单位为像素 px。当 View 左边缘在 View 内容左边缘右边时,mScrollX 为正值,反之为负值;同理,当 View 上边缘在 View 内容上边缘下边时,mScrollX 为正值,反之为负值。也就是说,View 从左向右滑动,mScrollX 为负值,反之为正值;从上往下滑动,mScrollY 为负值,反之为正值


mScrollX 和 mScrollY 变换规律
各种滑动方式的对比

(二)弹性滑动

在我们进行滑动时,这个过程是瞬间完成的,所以用户体验不大好,这里我们可以添加有过渡的滑动

View 弹性滑动的几种方式

这几种的方式有一个共同的思想:将一次大的滑动分成若干次小的滑动并在一个时间段内完成

1. Scroller

Scroller 本身是不能实现 View 的滑动的,它需要与 View 的 computeScroll() 方法配合才能实现弹性滑动的效果

  1. 初始化Scroller对象
  2. 重写View的computeScroll()方法
  3. 调用mScroller.startScroll()方法
    public RedView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mScroller = new ScrollView(context);
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()) {  //判断Scroller是否执行完毕
            ((View) getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }

    //缓慢的滑到指定位置
    public void smoothScrollTo(int destX, int destY) {
        int deltaX = destX - getScrollX();  
        int deltaY = destY - getScrollY();  
        //2000ms 内滑到向(deltaY,deltaY)的位置,效果就是慢慢滑动
        mScroller.startScroll(0, 0, deltaX, deltaY, 2000);
        invalidate();
    }

在Activity中调用:

mView.smoothScrollTo(-400, -400);

Scroller 本身并不能实现 View 的滑动,它需要配合 View 的 computeScroll 方法才能完成弹性滑动的效果,它通过不断地让 View 重绘,而每一次重绘距滑动起始时间间隔,通过这个时间间隔 Scroller 就可以得出 View 当前的滑动位置,知道了滑动位置就可以通过 scrollTo 方法来完成 View 的滑动。就这样,View 的每一次重绘都会导致 View 进行小幅度的滑动(弹性滑动的核心思想:将一次大的滑动分成若干次小的滑动并在一个时间段内完成),而多次的小幅度滑动就组成了弹性滑动,这就是 Scroller 的工作机制。

2. 通过动画

动画本身就是一种渐进的过程,因此通过它来实现的滑动天然就是具有弹性效果:

ObjectAnimator.ofFloat(mView, "translationX", 0, 100).setDuration(1000).start();

总结

滑动做为 View 最基本的行为,一切的华丽的、绚丽的 UI,归根结底都是建立在其基础上

下一章,将对 View 的分发机制进行学习

上一篇下一篇

猜你喜欢

热点阅读