Android从零开搞系列:自定义View(6)ScrollTo+ScrollBy+Scroller+NestedScrolling机制(上)

转载请注意:
http://blog.csdn.net/wjzj000/article/details/53874285
本菜开源的一个自己写的Demo,希望能给Androider们有所帮助,水平有限,见谅见谅…
https://github.com/zhiaixinyang/PersonalCollect (拆解GitHub上的优秀框架于一体,全部拆离不含任何额外的库导入)
https://github.com/zhiaixinyang/MyFirstApp(Retrofit+RxJava+MVP)


写在前面

在一切的开始之前,先看一个简单的效果:

这里写图片描述

实现这种效果有很多种方案,今天在这就主要借这个机会来记录一下scrollTo,scrollBy以及Scroller的用法。

让我们先了解一发:


关于scrollTo方法:

先看一下scrollTo()的源码解释:

    /**
     * Set the scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param x the x position to scroll to
     * @param y the y position to scroll to
     */
    //翻译过来:设置视图的滚动位置。 这将导致调用{@link #onScrollChanged(int,int,int,int)},并且视图将失效。

说实话不是很好理解。但是根据效果我们可以这么理解:scrollTo的效果是移向你传进的坐标。如果你再次调用这个方法,传值不变时!你会发现并没有任何的位置移动。(和scrollBy方法有区别)因为它已经到达了这个位置因此不会再移动,而scrollBy却不然。

此外此方法移动的是View内部的位置而不是View整体。如果View不是一个ViewGroup的话,例如TextView,那么移动的就是TextView的文字内容;如果是ViewGroup那么便是ViewGroup中的子元素。


关于scrollBy方法:

看一下scrollBy的源码:

    //注释基本和scrollTo相同
    /**
     * Move the scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param x the amount of pixels to scroll by horizontally
     * @param y the amount of pixels to scroll by vertically
     */
    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }

我们可以看出,scrollBy内部是调用的scrollTo,但是这里的传参导致了它们二者的不同。

scrollBy传入了mScrollX…这是个什么东西?我们在View中可以调用getScrollX()方法拿到这个值;其实这个值就是滑动的距离,对于X轴来说左滑动为正(增加),对于Y轴上滑动为正(增加)。

scrollBy的效果与scrollTo也截然相反。scrollBy就基于当前位置的移动,简单说它可以不断的移动。

只用文字去叙述有点单调,接下来上高清无码gif:

  • 这里我分别对俩这进行了多次点击,但是 传的值是固定的

这里写图片描述

!!!!这里有个需要注意的地方!!!!

在传值的时候,我们需要注意正负号问题:简单来说往左滑动时x为正,否则为负;上滑动时y为正,反之为负。

因为这里和mScrollX和mScrollY这俩个变量有关。这两个方法最终都会直接或间接的引用到这俩个变量。而它们俩的正负是这么判断的:如果View的左边缘在View内容区域(这俩个方法的移动都只是移动自己的内容区域)左边缘的右边为正,反之为负;如果View的上边缘在View内容区域的上边缘的下边mScrollY为正反之为负。

为什么是这样:当我们把内容区域的某个边缘当做参考点来理解就是这种情况。如果View的内容区域的左边缘为(0,…)那么View的左边缘在它的右边,理所应当为(+…,…)。同理mScrollY也是如此。


第三个我们来看一下Scroller

它其实就是一个辅助类:

    //简单重写了onTouchEvent
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                int x=-5;
                //在0.2内使View的x轴从0移到x
                //但是具体怎么移动需要我们自己去实现,解释在下面
                scroller.startScroll(0,0,x,0,200);
                invalidate();
                break;
        }
        return true;
    }
    /**
     * invalidate()最终会调用computeScroll()
     * 而View中的computeScroll()是一个空实现
     * 因此需要我们自己去重写这个方法,去实现对应的效果
     */
    @Override
    public void computeScroll() {
        super.computeScroll();
        //如果返回true,说明滑动还不到结束的时候,应当继续。
        if (scroller.computeScrollOffset()){
            //而促使它滑动的方式依旧是scrollBy或是scrollTo
            //注意此处x的位置,我们是使用的scroller.getCurrX()的返回值,至于它的作用往下来
            scrollBy(scroller.getCurrX(),0);
            //请求重新绘制View
            invalidate();
        }
    }

Scroller这个类本身不具备任何移动View的作用。它的startScroll方法,我们进源码就会发现仅仅是一些简单的赋值。真正起到移动效果的是invalidate()方法,通过这个方法使得View重绘,因此就会调用computeScroll(),所以我们重写computeScroll()。

可能有朋友在这里会由衷的赞叹一句:尼玛SB吗?饶了这么一大圈不还是scrollTo/scrollBy么!

不不不,它有一个最重要的作用。先让我们注意一下我们在传参的时候,传了一个时间参数(200)。
这个值在computeScrollOffset()中体现它的作用:

        //根据代码中的变量名,我们也能猜出:这里通过时间的流逝来计算移动进行的比例
        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
        //如果时间流逝小于应该传入的值,那么就继续执行
        if (timePassed < mDuration) {
            switch (mMode) {
            //如果是滚动状态
            case SCROLL_MODE:
                //通过类插值器的效果来计算x的变化量,使其随时间进行均匀变化。
                final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
                mCurrX = mStartX + Math.round(x * mDeltaX);
                mCurrY = mStartY + Math.round(x * mDeltaY);
                break;
         //省略部分代码
         }

这里用于计算移动的比例,因此Scroller最大的效果就是移动可以随时间的变化而变化,简单说可以做一些弹性的效果。让滑动不在单点生硬。


其实理解这些方法的使用,最开始的那个效果真的很简单,所以接下来就是简单贴一下代码:

相关代码

onTouchEvent相关:
@Override
    public boolean onTouchEvent(MotionEvent event) {
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (!scroller.isFinished())
                    scroller.abortAnimation();
                //记录手指按下时的坐标
                lastY = y;
                downY=event.getY();
                //消费点击事件
                return true;
            case MotionEvent.ACTION_MOVE:
                float dy = y - lastY;
                scrollBy(0, (int) -dy);
                lastY = y;
                break;
        }
        return super.onTouchEvent(event);
    }

scrollTo相关:

    //topViewHeight就是我们需要滑动的View的高度
    @Override
    public void scrollTo(int x, int y) {
        if (y < 0) {
            y = 0;
        }
        if (y > topViewHeight) {
            y = topViewHeight;
        }
        if (y != getScrollY()) {
            super.scrollTo(x, y);
        }
    }

onFinishInflate相关:

    //此方法在布局加载完成后回调,因此在此获得View的引用
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        topView=getChildAt(0);
    }

onSizeChanged相关:

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //获取topView的高度
        topViewHeight = topView.getMeasuredHeight();
        //画三角形所需的路线
        path=new Path();
        path.moveTo(avatarLeft-25,topViewHeight);
        path.lineTo(avatarLeft+25,topViewHeight);
        path.lineTo(avatarLeft,topViewHeight-25);
        path.close();
    }
    //画三角形
    @Override
    protected void dispatchDraw(Canvas canvas) {
        canvas.drawPath(path,paint);
        super.dispatchDraw(canvas);
    }

如果对基本的自定义View中的Canvas的一些Api不了解的可以看一下我的另一篇博客:
http://blog.csdn.net/wjzj000/article/details/53589024


PS:相关源码基本都存放于我的这个开源项目之中:
https://github.com/zhiaixinyang/PersonalCollect


尾声

OK,到此关于scrollTo和scrollBy以及Scroller的用法就结束了,接下来就是关于NestedScrolling机制的分析,让我们下一次博客见。

最后希望各位看官可以star我的GitHub,三叩九拜,满地打滚求star:
https://github.com/zhiaixinyang/PersonalCollect
https://github.com/zhiaixinyang/MyFirstApp

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值