Scroller的使用,让View随心所欲的移动

Scroller的使用,让View随心所欲的移动吧

效果图:





Scroller能控制View自由的移动,相对于其他让View移动的api,例如ViewDragHelper,
我个人倾向于使用Scroller,因为他使用起来简单,感觉更灵活一些。

值得注意的是,在一个View使用Scroller是不能让这个View移动的,
需要在ViewGroup或者他的子类里使用Scroller,才能让ViewGroup里的所有子View一起移动起来。

下面说Scroller让ViewGroup的所有子类移动的方法:



1,首先,在ViewGroup中创建Scroller对象。

    public class ScrollViewGroup extends FrameLayout {
        private static final String TAG = ScrollViewGroup.class.getSimpleName();
        private Scroller mScroller;
        private int mHeight;
        private int mWidth;
        public ScrollViewGroup(Context context) {
        super(context);
        init();
    }
    public ScrollViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    public ScrollViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    private void init() {
        mScroller = new Scroller(getContext());
    }



2,其次,复写View的 computeScroll() 方法,代码为模版代码,复制使用即可。

        @Override
        public void computeScroll() {
            if (mScroller.computeScrollOffset()) {
                scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
                postInvalidate();
            }
        }



3,然后,使用Scroller,让ViewGroup的子View移动起来。

    public void smoothScrollTo(int startX, int startY, int dx, int dy, int duration) {
        mScroller.startScroll(startX, startY, dx, dy, duration);
        invalidate();
    }



就这三步骤,就能让ViewGroup里的所有子View移动了。



下面说怎么玩Scroller



看这行代码:

        mScroller.startScroll(startX, startY, dx, dy, duration);
        invalidate();


startX是这个ViewGroup开始的X坐标,就是ViewGroup左上角的X坐标。而startY对应的是Y坐标。意思是,你想让ViewGroup初始点在哪里。
dx是X坐标的偏移量,你想让这个ViewGroup里的子View在X轴方向移动多少距离。而dy是Y坐标的偏移量。
duration是完成这一次移动所需要的时间。



如图:


例如,让ViewGroup里的子View,向下移动整个ViewGroup的高度,也就是上面动态图的向下移动down


1,首先,获取到ViewGroup的高度。

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mHeight = getMeasuredHeight();
        mWidth = getMeasuredWidth();
        Log.i(TAG, "height:" + mHeight + " ,width:" + mWidth);
    }


2,其次,设置参数,使用Scroller完成移动。

    public void down() {
        int startX = 0;
        int startY = 0;
        int dx = 0;
        int dy = -mHeight;
        int duration = 2000;
        smoothScrollTo(startX, startY, dx, dy, duration);
    }


值得注意的是,开始点0,0,也就是手机屏幕左上角那一点,移动Y方向的偏移量为 -mHeight,没错,是负的ViewGroup的高度。

这样,就可以让ViewGroup里的所有子View向下移动了。

接下来,来分析Scroller是如何完成移动的工作的。


看这两行代码:

        mScroller.startScroll(startX, startY, dx, dy, duration);
        invalidate();


CTRL+B,进入到startScroll方法里:

    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;
    }


可以看到,方法里把传递进来的参数赋值了一下,开始坐标,偏移量,使用的时间,还有移动后的坐标mFinalX,Y


再看这个方法:

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }


模版代码,一直可以这样写。进入到computeScrollOffset里:

    public boolean computeScrollOffset() {
        if (mFinished) {
            return false;
        }


可以看到,当mFinished为true时返回false,也就是不会继续走computeScroll里的代码了,在继续看:

    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;


可以看出,mCurrX ,Y 的值是有开始坐标不断的增加的。增加的与x有关,注意这里的timePassed ,可以看出,这是一个不断接近duration的值,
mStartTime 在之前:

 mStartTime = AnimationUtils.currentAnimationTimeMillis();


为一个时间戳,开始的时间。
timePassed 是一个随着时间流逝的值,根据这个差值进行计算,累加到mCurrX ,Y,达到一个在一段时间内,不断累加的效果,设计得挺妙的。



然后:

            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();


再然后:

    public final int getCurrX() {
        return mCurrX;
    }


可以看到使用了增加之后的mCurrX ,Y,调用scrollTo来完成ViewGroup的子View移动的效果。

scrollTo是一下子移动到某一点,并没有动态,循序渐进的效果。那为什么会出现动态的效果呢?


答案就是这个方法:

postInvalidate


重新绘制View,也就是调用View的draw方法进行重新绘制视图。

查看View源码找到draw方法:

 boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {


发现里面有调用:

        if (!drawingWithRenderNode) {
            computeScroll();
            sx = mScrollX;
            sy = mScrollY;
        }


说明, postInvalidate调用了draw再调用了computeScroll,然而computeScroll里又调用了scrollTo,移动的偏移量又是一点一点增大的值。

噢,我明白了,原来是通过不断的重绘来调用scrollTo让ViewGroup子View一点一点的移动,最终达到动态的效果,就像帧动画一样。


然而,什么时候结束移动呢?

看这个方法里:

    public boolean computeScrollOffset() {
        if (mFinished) {
            return false;
        }


发现了:

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


让mCurrX ,Y一点一点增加,增加到终点坐标之后,mFinished等于true,return false,就不会再绘制,就不再scrollTo了,移动停止了。

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }


Scroller的移动,如何实现,使用,分析一下原理,也就到这里了。总体感觉设计移动的增量使用时间戳的方式的确很精妙,可以借鉴学习,通过重绘与scrollTo的调用来完成动态效果,控制移动结束也很精妙,学习了。


要到两点了,就到这里吧。Demo下载:我的Github


2016年6月20日 1:53:48

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值