End

【Scroller】scrollTo scrollBy startScroll computeScroll 自定义ViewPage 简介 示例

简介

android.widget.Scroller是用于模拟scrolling行为,它是scrolling行为的一个帮助类。我们通常通过它的 startScroll 函数来设置一个 scrolling 行为模型,即在 duration 毫秒时间内从 int startX, int startY 这个点起向X和Y方向分别滚动 int dx 和 int dy 个像素;或者通过它的 fling 函数来设置一个 fling 行为模型(特殊的scroll),即在 int startX, int startY 这个点起向X和Y方向分别以 int velocityX 和  int velocityY 个像素的速度进行滚动。然后我们可以调用 computeScrollOffset 方法计算此时scroll到的位置,并调用 getCurrX 和 getCurrY 得到到此时在X和Y方向的位置。

构造函数

  • Scroller(Context context):Create a Scroller with the default duration and interpolator.
  • Scroller(Context context, Interpolator interpolator):interpolator参数只是在computeScrollOffset()中用于对我们的流逝的时间(通过timePassed()取得)这值进行重新解析。If the interpolator is null, the default (viscous) interpolator will be used. "Flywheel" behavior will be in effect for apps targeting Honeycomb or newer.
  • Scroller(Context context, Interpolator interpolator, boolean flywheel):Specify指定 whether or not to support progressive "flywheel" behavior in flinging.

公共函数

  • void abortAnimation()  停止scroll。Stops the animation.
  • boolean computeScrollOffset()计算scroll的情况,判断滚动操作是否已经完成了Call this when you want to know the new location. If it returns true,the animation is not yet finished.
  • void extendDuration(int extend):增加scroll的时间。Extend the scroll animation. This allows a running animation to scroll further and longer, when used with setFinalX(int) orsetFinalY(int).
  • void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)模拟fling形式的scroll行为。int startX, int startY 代表起点int velocityX, int velocityY代表初速度int minX, int maxX, int minY, int maxY代表终点的范围
  • final void forceFinished(boolean finished):强制设置为scroll状态。Force the finished field to a particular特定的 value.
  • float getCurrVelocity():得到当前速度。该值是X方向和Y方向的合成值。Returns the current velocity. Return the original velocity less the deceleration减速. Result may be negative负值.
  • final int getCurrX()得到当前的X坐标。Returns the current X offset in the scroll.
  • final int getDuration():得到设置的scroll行为的总时间值。Returns how long the scroll event will take, in milliseconds.
  • final int getFinalX():得到scroll行为终点的X值。Returns where the scroll will end.
  • void setFinalX(int newX):设置scroll行为的终点的X值。Sets the final position (X) for this scroller.
  • final int getStartX():得到scroll行为起点的X值。Returns the start X offset in the scroll.
  • final boolean isFinished():返回scroll行为是否结束。Returns whether the scroller has finished scrolling.True if the scroller has finished scrolling, false otherwise.
  • boolean isScrollingInDirection(float xvel, float yvel):这是一个public类型的hide方法
  • final void setFriction(float friction):设置摩擦力。The amount of friction applied to flings. The default value is ViewConfiguration的getScrollFriction.
  • void startScroll(int startX, int startY, int dx, int dy)设置一个scrolling行为模型,即在默认时间(250毫秒)内从int startX, int startY 这个点起向X和Y方向分别滚动 int dx 和 int dy 个像素。Start scrolling by providing a starting point and the distance距离 to travel.
  • void startScroll(int startX, int startY, int dx, int dy, int duration):指定时间
  • int timePassed()从scroll开始到现在已经过去的时间。其值为 AnimationUtils.currentAnimationTimeMillis() - mStartTime。Returns the time elapsed消逝、过去 since the beginning of the scrolling.

View与Scroller的关系

Scroller是一个封装位置和速度等信息的变量,startScroll()函数只是对它的一些成员变量做一些设置,这个设置的唯一效果就是导致mScroller.computeScrollOffset()返回true,而并不会导致View的滚动。

这里大家可能就会有一个疑问,既然startScroll()只是虚晃一枪,那scroll的动态效果到底是谁触发的呢?

其实是invalidate方法,或导致invalidate方法被调用的其他操作。
当View重绘时(即因故调用了其invalidate方法),若此时mScroller.startScroll方法被调用了,那么mScroller将会存在一些有效的设置。然后当整个View系统自上而下重新绘制过程中调用到drawChild时,也会使得View的computeScroll函数被触发,如果我们在computeScroll方法中对View进行了移动(调用其自身的scrollTo、scrollBy方法),将会导致系统不断的重绘,直到startScroll的mScroller.computeScrollOffset()返回false才停下来。

所以,我们可以在父容器重画自己孩子过程中,在调用孩子的computScroll方法时,在这个方法里去获取事先设置好的成员变量mScroller中的位置、时间、加速度等信息,用这些参数来做我们想做的事情。

我们再看下View中computeScroll方法的默认实现:
/**
* Called by a parent to request that a child update its values for mScrollX and mScrollY if necessary.
* This will typically通常 be done if the child is animating a scroll在滚动 using a Scroller object.
*/
public void computeScroll() {
}
可以看到,View的computeScroll是一个空函数,很明显我们需要去实现它,至于做什么,就由我们自己来决定了。

示例代码 

/**
 * 自定义模仿ViewPage,只实现其最基本的滑动平滑切换item的功能
 * @author 白乾涛
 */
public class ScrollerLayout extends ViewGroup {
    private Scroller mScroller = new Scroller(getContext()new AccelerateInterpolator());//可以指定加速器
    private int mTouchSlop = 20;//判定为拖动的最小移动像素数
    private float mXDown;//手指按下时的屏幕坐标
    private float mLastX;//【上次】触发ACTION_MOVE事件时的屏幕坐标
    private int leftBorder;//界面可滚动的左边界
    private int rightBorder;//界面可滚动的右边界
    public ScrollerLayout(Context context) {
        this(context, null);
    }
    public ScrollerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);// 为ScrollerLayout中的每一个子控件测量大小
        }
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (changed) {
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                int left = i * childView.getMeasuredWidth();
                int right = (i + 1) * childView.getMeasuredWidth();
                childView.layout(left, 0, right, childView.getMeasuredHeight());// 为ScrollerLayout中的每一个子控件在水平方向上进行布局
            }
            // 初始化左右边界值
            leftBorder = getChildAt(0).getLeft();
            rightBorder = getChildAt(getChildCount() - 1).getRight();
        }
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        Log.i("bqt""onInterceptTouchEvent" + "--" + mLastX);
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mXDown = event.getRawX();
            break;
        case MotionEvent.ACTION_MOVE:
            // 当手指拖动值大于TouchSlop时,认为应该进行滚动,拦截子控件的事件,之后就会将事件交给自己的onTouchEvent()方法来处理
            if (Math.abs(event.getRawX() - mXDown) > mTouchSlop) {
                mLastX = event.getRawX();//记录拦截事件之前、即调用自己的onTouchEvent()方法之前的触摸点的位置
                return true;
            }
            break;
        }
        return super.onInterceptTouchEvent(event);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("bqt""onTouchEvent" + "--" + mLastX);
        switch (event.getAction()) {
        //1、在拖动过程中,使用View自身的scrollTo和scrollBy方法滚动自身的【内容】
        //使用scrollTo和scrollBy滑动的过程本身并非是平滑的,但是由于ACTION_MOVE事件比较密集,所以看起来像是平滑的一样
        case MotionEvent.ACTION_MOVE:
            float scrolledX = mLastX - event.getRawX();//上一次回调与此次回调期间,用户拖动了多少距离
            if (getScrollX() + scrolledX < leftBorder) scrollTo(leftBorder, 0);//scrollTo()是让View相对于初始的位置滚动某段距离,【左上】滚动为正
            else if (getScrollX() + getWidth() + scrolledX > rightBorder) scrollTo(rightBorder - getWidth(), 0);
            else scrollBy((int) scrolledX, 0);//scrollBy()是让View相对于当前的位置滚动某段距离。用户拖动了多少这里就scrollBy多少
            mLastX = event.getRawX();
            break;
        //2、当手指抬起时,使用Scroller的startScroll方法【初始化滚动数据】。注意调用Scroller的startScroll方法本身并不会导致View的滑动!
        case MotionEvent.ACTION_UP:
            int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();
            int dx = targetIndex * getWidth() - getScrollX();
            mScroller.startScroll(getScrollX(), 0, dx, 0, 100);//参数:滚动开始时X/Y坐标,横/纵向滚动的距离,滚动时间,【左上】滚动为正
            invalidate();//一定要在startScroll后刷新才可以
            break;
        }
        return super.onTouchEvent(event);
    }
    @Override
    //3、重写View的computeScroll()方法,并在其内部完成平滑滚动的逻辑。在整个后续的平滑滚动过程中,此方法是会一直被调用的
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {//判断滚动操作是否已经完成了
            scrollTo(mScroller.getCurrX()mScroller.getCurrY());//把Scroller的currX和currY坐标传入。由此可以看出,Scroller只是提供数据用的
            invalidate();
        }
    }
}

posted @ 2017-03-11 16:51  白乾涛  阅读(868)  评论(0编辑  收藏  举报