Scroller理解

ScrollTo和ScrollBy

首先需要理解View.ScrollTo和ScrollBy这两个方法。任何View都可以使用这两个方法进行平移。但是他的坐标系与笛卡尔坐标系是相反的。

并且很重要的一点ScrollTo和ScrollBy这两个方法移动的不是View而是View内部的组件!

getScrollX()和getScrollY()

getScrollX()、getScrollY()得到的是偏移量,是相对自己初始位置的滑动偏移距离,只有当有scroll事件发生时,这两个方法才能有值,否则getScrollX()、getScrollY()都是初始时的值0,而不管你这个滑动控件在哪里。所谓自己初始位置是指,控件在刚开始显示时、没有滑动前的位置。mScrollX为正代表着当前内容相对于初始位置向左偏移了mScrollX的距离,mScrollX为负表示当前内容相对于初始位置向右偏移了mScrollX的距离。

使用ScrollBy滑动例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package com.example.sun.test;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

/**
* Create by Sun on 2019/6/28
* Desc: 左滑
*/
public class MyPciturePageView extends ViewGroup {
private int oldx;

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

public MyPciturePageView(Context context, AttributeSet attrs) {
super(context, attrs);
}

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

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
for(int i = 0; i < count; i++){
View child = getChildAt(i);
child.layout(i * getWidth(), t, (i+1) * getWidth(), b);
}
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int count = getChildCount();
for(int i = 0; i < count; i++){

//子节点的宽高和父亲一样
View child = getChildAt(i);
child.measure(widthMeasureSpec,heightMeasureSpec);
}
}

@Override
public boolean onTouchEvent(MotionEvent event) {

switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
oldx= (int) event.getX();

break;
case MotionEvent.ACTION_MOVE:
//小于0向左滑
int d= (int) (event.getX()-oldx);

int hasScollX=this.getScrollX();
Log.d("onTouchEvent", "onTouchEvent: "+hasScollX);
if(d<0){
if((hasScollX)>=(getChildCount()-1)*getWidth()){
d=0;
}
}else {
if(hasScollX<=0){
//滑到最左边了
d=0;
}

}

this.scrollBy(-d,0);
oldx= (int) event.getX();
break;
case MotionEvent.ACTION_UP:

break;
}
return true;
}
}

Scroller滑动辅助类

Scroller本身不会去移动View,它只是一个移动计算辅助类,用于跟踪控件滑动的轨迹,只相当于一个滚动轨迹记录工具,最终还是通过View的scrollTo、scrollBy方法完成View的移动

  • startScroll(),开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达偏移坐标为(startX+dx , startY+dy)处。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
    mMode = SCROLL_MODE;
    mFinished = false;
    mDuration = duration;
    mStartTime = AnimationUtils.currentAnimationTimeMillis();
    mStartX = startX;
    mStartY = startY;
    mFinalX = startX + dx;
    mFinalY = startY + dy;
    mDeltaX = dx;
    mDeltaY = dy;
    mDurationReciprocal = 1.0f / (float) mDuration;
    }
  • ComputeScrollOffset(),滑动过程中,根据当前已经消逝的时间计算当前偏移的坐标点,保存在mCurrX和mCurrY值中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    public boolean computeScrollOffset() {
    if (mFinished) {
    return false;
    }

    int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);

    if (timePassed < mDuration) {
    switch (mMode) {
    case SCROLL_MODE:
    final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
    mCurrX = mStartX + Math.round(x * mDeltaX);
    mCurrY = mStartY + Math.round(x * mDeltaY);
    break;
    case FLING_MODE:
    final float t = (float) timePassed / mDuration;
    final int index = (int) (NB_SAMPLES * t);
    float distanceCoef = 1.f;
    float velocityCoef = 0.f;
    if (index < NB_SAMPLES) {
    final float t_inf = (float) index / NB_SAMPLES;
    final float t_sup = (float) (index + 1) / NB_SAMPLES;
    final float d_inf = SPLINE_POSITION[index];
    final float d_sup = SPLINE_POSITION[index + 1];
    velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
    distanceCoef = d_inf + (t - t_inf) * velocityCoef;
    }

    mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;

    mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
    // Pin to mMinX <= mCurrX <= mMaxX
    mCurrX = Math.min(mCurrX, mMaxX);
    mCurrX = Math.max(mCurrX, mMinX);

    mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
    // Pin to mMinY <= mCurrY <= mMaxY
    mCurrY = Math.min(mCurrY, mMaxY);
    mCurrY = Math.max(mCurrY, mMinY);

    if (mCurrX == mFinalX && mCurrY == mFinalY) {
    mFinished = true;
    }

    break;
    }
    }
    else {
    mCurrX = mFinalX;
    mCurrY = mFinalY;
    mFinished = true;
    }
    return true;
    }