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

来源:互联网 发布:分销商城系统源码下载 编辑:程序博客网 时间:2024/06/11 12:44

转载请注意:
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
原创粉丝点击