Android开发学习之Scroll分析

Posted 哈喽喔德

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android开发学习之Scroll分析相关的知识,希望对你有一定的参考价值。

(学习参考书:android群英传)

一、滑动效果的产生

滑动一个View本质上就是一移动一个View,改变当前其当前所处的位置。因要实现View的滑动,就必须监听用户的触摸事件,并根据事件传入的坐标,动态且不断的改变View的坐标,从而实现View跟随用户触摸的滑动而滑动。Android中的窗口坐标体系和触控事件MotionEvent

二、Android坐标系

在Android中,将屏幕的左上角的顶点作为Android坐标系的原点,从该点向左为X轴正方向,向下为Y轴正方向。系统提供了getLocationOnScreen(intlocation[])这样的方法来获取Android坐标系中点的位置,即该视图左上角在Android坐标系中的坐标,另外在触控事件中使用getRawX()、getRawY()方法所获得的坐标同样是Android坐标系中的坐标。

三、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 static final int ACTION_OUTSIDE = 4; 触摸动作超出边界
public static final int ACTION_POINTER_DOWN = 5; 多点触摸按下动作
public static final int ACTION_POINTER_UP = 6; 多点离开动作

通常情况下,会在onTouchEvent(MotionEvent event)方法中通过event.getAction()方法来获取触控事件的类型,并使用switch-case方法来进行筛选。该代码模式基本固定:

@Override
public boolean onTouchEvent(MotionEvent event) {
    //获取当前输入点的XY坐标(视图坐标)
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
            //处理输入的按下事件
            break;
        case MotionEvent.ACTION_MOVE:
            //处理输入的移动事件
            break;
        case MotionEvent.ACTION_UP:
            //处理输入的离开事件
            break; 
    }
    return true;
}

三、常用系统API

系统提供了很多方法来获取坐标值和相对距离,可以分为两个类别:
View提供的获取坐标方法:

getTop() 获取到的是View自身的顶边到父布局边界的距离
getLeft() 获取到的是View自身的左边到父布局边界的距离
getRight() 获取到的是View自身的右边到父布局边界的距离
getBottom() 获取到的是View自身的底边到父布局边界的距离

MotionEvent提供的方法:

getX() 获取点击事件距离控件左边的距离,即视图坐标
getY() 获取点击事件距离控件顶边边的距离,即视图坐标
getRawX() 获取点击事件距离控件左边的距离,即绝对坐标
getRawY() 获取点击事件距离控件顶边的距离,即绝对坐标

四、实现滑动的其中方法

实现滑动效果即修改View的坐标,其基本思想是当触摸View时,系统记下当前触摸点的坐标;当手指移动时,系统记下移动后的触摸点坐标,从而获取到相对于前一次坐标点的偏移量,并通过偏移量来修改View的坐标,这样不断重复,从而实现滑动过程。

(1)layout方法

View进行绘制时,会调用onLayout()方法来设置显示的位置。同样也可以通过修改View的left、top、right、bottom四个属性来控制View的坐标。与前面提供的代码模板一样,每次回调onTouchEvent的时候,都获取一下触摸点的坐标。
除了使用视图坐标,还可以使用绝对坐标来计算偏移量。

@Override
public boolean onTouchEvent(MotionEvent event) {
    //获取当前输入点的XY坐标(视图坐标)
    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(getLeft()+ offsetX,getTop()+offsetY,getRight()+offsetX,getBottom()+offsetY);
            break;
        case MotionEvent.ACTION_UP:
            //处理输入的离开事件
            break;
    }
    return true;
}

(2)offsetLeftAndRight( )与offsetTopAndBottom( )

这个方法相当于系统提供的一个对左右、上下移动的API的封装。当计算出偏移量后,只需要使用如下代码就可以完成View的重新布局,效果与layout方法一样:

offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);

(3)LayoutParams

LayoutParams保存了一个View的布局参数。因此可以在程序中,通过改变LayoutParams来动态修改一个布局的位置参数,从而达到改变View位置的效果。可以在程序中很方便的使用getLayoutParams()来获取一个View的LayoutParams。获取到偏移量后,就可以通过setLayoutParams来改变其LayoutParams:

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

(4)scrollTo和scrollBy

scrollTo(x,y)表示移动到某个具体的坐标点,scrollBy(x,y)表示移动的增量。但是这两个方法移动的是View的内容,如文本、图片;如果在ViewGroup中使用这两个方法,移动的将是其所有子View。

scrollBy(offsetX,offsetY); //移动内容
((View) getParent()).scrollBy(-offsetX,-offsetY); //移动子View

(5)Scroll类

Scroller类与scrollTo和scrollBy方法非常相似。但是Scroller类的移动实现了平滑移动的动画,而不是上述两种方法的瞬间移动。

  1. 创建一个Scroller对象实例,传入参数Context
  2. 重写computeScroll()方法,实现模拟滑动;Scroller提供了computeScrollOffset判断是否完成滑动,也提供了getCurrX()getCurrY()获取当前坐标。模板如下
@Override
public void computeScroll() {
    super.computeScroll();
    if(scroller.computeScrollOffset()){
        ((View) getParent()).scrollTo(scroller.getCurrX(),scroller.getCurrY());
        invalidate();
    }
}

  1. startScroll开启滑动过程:
View viewGroup = (View) getParent();
scroller.startScroll(viewGroup.getScrollX(),viewGroup.getScrollY(),-viewGroup.getScrollX(),-viewGroup.getScrollY());
invalidate();
//获取View移动的距离——getScrollX()、getscrollY()
//并将偏移量设置为相反数

(6)ViewDragHelper

ViewDragHelper基本可以实现各种不同的滑动、拖放需求、因此这个方法也是各种滑动解决方法中的终极绝招。

  1. 初始化ViewDragHelper:通常定义在一个ViewGroup的内部,并使用其静态方法进行初始化:ViewDragHelper.create(this,callback);第一个参数是要监听的View,通常是需要一个ViewGroup;第二个参数是一个Callback回调,这个回调就是整个ViewDragHelper的逻辑核心。
  2. 拦截事件:重写事件拦截方法,将事件传递给ViewDragHelper进行处理。在onTouchEvent()方法中调用ViewDragHelper的processTouchEvent (event)方法
  3. 处理computeScroll():因为ViewDragHelper内部也是通过Scroll来实现平滑移动的,因此也需要重写computeScroll()方法,模板如下:
public void computeScroll() {
    if(helper.continueSettling(true)){
        ViewCompat.postInvalidateOnAnimation(this);
    }
}

  1. 处理回调Callback:最关键的Callback实现,通过下面的代码来创建一个ViewDragHelper.Callback。并通过tryCaptureView()方法指定在创建ViewDragHelper时,参数parentView中的哪一个子View可以被移动
private ViewDragHelper.Callback callback  = new ViewDragHelper.Callback() {
    @Override
    public boolean tryCaptureView(@NonNull View child, int pointerId) {
        return targetVeiw==child;
    }
};

  1. 如果需要实现基本滑动效果,必须要在回调接口中重写clampViewPositionHorizontal和clampViewPositionVertical方法。如垂直方法中的top代表垂直方向上child移动的距离,dy表示比较前一次的增量,水平方法中参数同理。
@Override
public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
    return left;
}                                                             

@Override
public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
    return top;
}

除了此对ViewDragHelper简单的应用外,ViewDragHelper.Callback中,系统定义了大量的监听事件来帮助处理各种事件:

onViewCaptured() 该事件在用户触摸到View后回调
onViewDragStateChanged() 该事件在拖拽状态改变时回调
onViewPositionChanged() 该事件在位置改变时回调

以上是关于Android开发学习之Scroll分析的主要内容,如果未能解决你的问题,请参考以下文章

Android开发学习之通知

Android开发学习之Material Design

Android开发学习之Material Design

Android开发学习之位置服务

Android开发学习之探究服务

Android开发学习之网络技术