Android——View体系的进阶(一)View 的滑动

一、View 的简介

View是所有可视化控件的基类,我们平时接触的所有的控件,比如说TextView,Button 等等都继承于View。View是Android 在视觉上的呈现,是界面层控件的一种抽象,可以是单个控件也可以是一组控件。

二、坐标系

1、Android 坐标系

在Android中,屏幕的左上角定点被视为Android坐标系的原点,原点向右是X轴正方向,原点向下是Y轴正方向。在触控事件中使用 getRawX()getRawY() 来获取Android 坐标系的坐标,也可以称之为绝对坐标
Android 坐标系

2、视图(View)坐标系

视图坐标系是用来描述当前View 和父视图的位置关系的。这里是以父视图的左上角定点为视图坐标系的原点,原点向右是X轴正方向,原点向下是Y轴正方向。在触控事件中使用getX()getY() 来获取视图坐标系的坐标,也可以称之为相对坐标.
视图(View)坐标系

三、View滑动的实现

View 的滑动实现逻辑:当触摸事件传到View 时,记录下触摸点的坐标,手指在移动的时候记下移动后的触摸坐标并算出偏移量,然后通过偏移量来修改View 的坐标。

MotionEvent

我们要实现View的滑动要在 onTouchEvent(MotionEvent event)中进行触摸事件的监听,MotionEvent 是触控事件的封装,简短的介绍下MotionEvent中封装的事件常量:

public static final int ACTION_DOWN = 0; //单点触摸按下动作
public static final int ACTION_UP = 1; //单点触摸离开动作
public static final int ACTION_MOVE = 2; //单点触摸移动动作
public static final int ACTION_CANCEL = 3; //触摸动作取消
public staiic final int ACTION_OUTSIDE = 4; //触摸动作超出边界
public static final int ACTION_POINTER_DOWN = 5; //多点触摸按下动作
public static final int ACTION_POINTER_UP = 6; 多点离开动作

1、layout()方法

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

@Override
    public boolean onTouchEvent(MotionEvent event) {
        // 获取触摸点的x,y
        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 moveX = x - lastX;
                int moveY = y - lastY;
                //  更改位置
                layout(getLeft() + moveX, getTop() + moveY,
                        getRight() + moveX, getBottom() + moveY);
                break;
        }
        return true;
    }

2、offsetLeftAndRight()和offsetTopAndBottom

传入偏移量后,这两个方法就是对View左右、上下移动的操作。

@Override
    public boolean onTouchEvent(MotionEvent event) {
        // 获取触摸点的x,y
        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 moveX = x - lastX;
                int moveY = y - lastY;
                //  更改位置
                offsetLeftAndRight(moveX);
                offsetTopAndBottom(moveY);
                break;
        }
        return true;
    }

3、LayoutParams

LayoutParams保存了View的布局参数,所以我们可以改变LayoutParams来动态的改变布局的位置来达到滑动的效果。

@Override
    public boolean onTouchEvent(MotionEvent event) {
        // 获取触摸点的x,y
        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 moveX = x - lastX;
                int moveY = y - lastY;
                //  更改位置
                LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) getLayoutParams();
                params.leftMargin = getLeft() + moveX;
                params.topMargin = getTop() + moveY;
                setLayoutParams(params);
                break;
        }
        return true;
    }

其实LayoutParams 完全可以配合 xml 中的layout_widthlayout_marginLeft 等等这些前面带layout_ 的属性去理解,设置LayoutParams 各个参数的过程就是动态设置xml中带layout_ 各个属性的过程。关于LayoutParams 的详细分析,可以参照这位大神写的博客 自定义控件知识储备-LayoutParams的那些事,写的很透彻,完全被圈粉。

4、ScrollTo 和 scrollBy

scollTo(x,y)表示移动到一个具体的坐标点,而scollBy(dx,dy)则表示移动的增量为dx、dy。查看scollBy()源码最终也是要调用scollTo的,举个例子说明下scollBy()是怎样移动的:
这里写图片描述
这里写图片描述

我们都知道画布的大小是无限大的,而坐标系是基于屏幕的左上角为原点的,所以屏幕中的控件的Android 坐标是(40,50),当我们执行scrollBy(30,30) 的时候,按照字面意思,这个控件的位置应该会出现在当前红色屏幕的右下方,可是事实上却出现在了屏幕的左上方。这是因为当执行scrollBy()方法的时候,我们的屏幕会沿着X轴正方向平移30,向Y轴正方向平移30,也就是屏幕向右下方平移了,此时红色的位置就是当前屏幕的位置。当执行完ScrollBy 以后,坐标系就会继续基于屏幕的左上角为原点,所以此时的屏幕中的控件的Android坐标为(10,20)。所以当我们调用scrollBy 方法的时候,要设置值为负数才能得到自己想要的移动效果。
scollTo、scollBy移动的是View的内容,如果在ViewGroup中使用则是移动他所有的子View,这点要特别注意。

@Override
    public boolean onTouchEvent(MotionEvent event) {
        // 获取触摸点的x,y
        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 moveX = x - lastX;
                int moveY = y - lastY;
                //  更改位置
                ((View) getParent()).scrollBy(-moveX, -moveY);
                break;
        }
        return true;
    }

5、Scroller

我们用scollTo/scollBy方法来进行滑动时,这个过程是瞬间完成的,所以用户体验不大好。这里我们可以使用Scroller来实现有过度效果的滑动,这个过程不是瞬间完成的,而是在一定的时间间隔完成的。Scroller本身是不能实现View的滑动的,它需要配合View的computeScroll()方法才能弹性滑动的效果。Scroller的基本用法其实还是比较简单的,主要可以分为以下几个步骤:
1. 创建Scroller的实例
2. 调用startScroll()方法来初始化滚动数据并刷新界面
3. 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑

public class ScrollerView extends View {
    private Scroller mScroller;

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

    public ScrollerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 初始化Scroller
        mScroller = new Scroller(context);
    }

    public ScrollerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * 系统会在绘制View的时候在draw()方法中调用该方法,这个方法中我们调用父类的scrollTo()方法并通过Scroller
     * 来不断获取当前的滚动值,每滑动一小段距离我们就调用invalidate()方法不断的进行重绘,重绘就会调用
     * computeScroll()方法,这样我们就通过不断的移动一个小的距离并连贯起来就实现了平滑移动的效果
     */
    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()) {
            ((View) getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            //通过不断的重绘不断的调用computeScroll方法
            invalidate();
        }
    }

    /**
     * 提供2秒内平滑的滑动方法
     * @param destX
     * @param destY
     */
    public void smoothScrollTo(int destX, int destY) {
        int scrollX = getScrollX();
        int scrollY = getScrollY();
        int deltaX = destX - scrollX;
        int deltaY = destY - scrollY;
        // 在2秒内滑到destX destY
        mScroller.startScroll(scrollX, scrollY, deltaX, deltaY, 2000);
        invalidate();
    }
}

结尾:以上就是关于View 滑动的相关内容了,当然还有属性动画也能控制View 的滑动,我准备放到动画篇讲解。下面附上源码:https://github.com/smile-sxl/CustomView

猜你喜欢

转载自blog.csdn.net/javasxl/article/details/80635434