View的滚动以及Scroller探究
View的滚动以及Scroller探究
飞越围墙 发表于1年前
View的滚动以及Scroller探究
  • 发表于 1年前
  • 阅读 8
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

前言

说来惭愧,Android开发好几年,对于滚动仍然一无所知,一般自定义view如果要滚动就是直接把ScrollView也打包进去,也能完成既定需求.在公司项目中有一个环形菜单控件一直点卡卡的,看代码是响应触摸事件然后改变LayoutParams,再重绘实现的,当时想这个也相当于"滑动"呀,是不是可以用Scroller来实现(后来发现太天真).

Scroller

Scroller是一个滚动的辅助类,它负责告诉你某一时刻滚动到了那个位置以及滚动有没有完成. 下面是几个主要的方法

  • void startScroll(int startX, int startY, int dx, int dy, int duration) 开始滚动,Scroller纪录开始的位置,要滚动的距离,滚动时间,以及滚动开始的时刻
  • boolean computeScrollOffset() 计算滚动距离,Scroller计算调用这个方法的时刻需要滚动到的位置,顺便返回是否已经滚动到位了(即结束了)
  • int getCurrX() 方法调用时刻scroller的X位置
  • int getCurrY() 方法调用时刻scroller的Y位置

假如:我自定义View的内容要移动水平移动50,垂直移动30,是怎样进行的呢

  1. 先startScroll,纪录下要从0,0,X滚动50,Y滚动30,滚动时间250ms
  2. 调用computeScrollOffset()计算一下从startScroll开始到现在过了多少时间,按速度应该到哪个位置了.
  3. 用getCurrX()和getCurrY()拿到计算出来的位置,把View的内容移到那个位置
  4. 要是第2步的computeScrollOffset()返回true,则再调用第2步,循环往复,直到返回false,说明移动到位了,滚动结束

View的滚动

View的滚动就是按上面的原理进行的, 首先我们通过事件(滑动,点击,代码)触发滚动,调用startScroll,然后调用postInvalidate()重绘View, 重绘会调用View.computeScroll(),这个方法里调用上面说的第2-3步(这里最后移动内容是调用的View.scrollTo(x,y)), 而scrollTo又会触发View的重绘,循环往复,直到滚动完成.

真正移动view的内容的动作是View.scrollTo(x,y),通过分析源代码发现是通过移动canvas来实现的.

代码比文字简单100倍,

测试代码片段:http://git.oschina.net/sun141421/laj4tp78vcr12usw36ghd69.code.git

以下是对View的源代码不严谨分析(基于Android-24)

View.scrollTo(x,y)

  /**
     * 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
     */
    public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }

可以看出它只是赋值了mScrollX mScrollY,并调用postInvalidateOnAnimation()引起View树的重绘, 父控件通过调用viewGroup.drawChild()重绘子控件

    /**
     * Draw one child of this View Group. This method is responsible for getting
     * the canvas in the right state. This includes clipping, translating so
     * that the child's scrolled origin is at 0, 0, and applying any animation
     * transformations.
     *
     * @param canvas The canvas on which to draw the child
     * @param child Who to draw
     * @param drawingTime The time at which draw is occurring
     * @return True if an invalidate() was issued
     */
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

子控件的 child.draw(canvas, this, drawingTime)中调用了updateDisplayListIfDirty();

     /**
     * This method is called by ViewGroup.drawChild() to have each child view draw itself.
     *
     * This is where the View specializes rendering behavior based on layer type,
     * and hardware acceleration.
     */
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        ....
        renderNode = updateDisplayListIfDirty();
        ....            
    }

在updateDisplayListIfDirty()中有关键的几行,

    /**
     * Gets the RenderNode for the view, and updates its DisplayList (if needed and supported)
     * @hide
     */
    @NonNull
    public RenderNode updateDisplayListIfDirty() {
        ....
         computeScroll();

         canvas.translate(-mScrollX, -mScrollY);
        ....  
       draw(canvas);
        ....
            
    }

它先调用 computeScroll()计算好mScrollX,mScrollY然后 canvas.translate,最后draw

共有 人打赏支持
粉丝 2
博文 2
码字总数 909
×
飞越围墙
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: