博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
View学习(四)-View的绘制(draw)过程
阅读量:6217 次
发布时间:2019-06-21

本文共 4760 字,大约阅读时间需要 15 分钟。

View的draw过程相比之于measrue过程,也是比较简单的。并且在我们自定义View时,也经常需要重写onDraw方法,来绘制出我们要实现的效果。

如之前的文章所说,绘制的流程也是起始于ViewRootImpl#perfomTraversalsViewRootImpl#performDraw() 方法调用了ViewRootImpl#draw(boolean fullRedrawNeeded) ,其中这个boolean类型的形参,作用是判断是否需要重新绘制全部视图。最后调用的DecorView.draw(canvas)方法,自此开始了正式的绘制流程。

ViewGroup当中并没有实现draw(Canvas canvas)onDraw(Canvas canvas)方法,所以所有View都是调用View#draw方法, 其源码如下:

/**     * Manually render this view (and all of its children) to the given Canvas.     * The view must have already done a full layout before this function is     * called.  When implementing a view, implement     * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.     * If you do need to override this method, call the superclass version.     *     * @param canvas The Canvas to which the View is rendered.     */    @CallSuper    public void draw(Canvas canvas) {        final int privateFlags = mPrivateFlags;                //@author www.yaoxiaowen.com        // dirtyOpaque 标志位,判断该view是否透明,如果透明,它就可以省略一些步骤        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;        /*         * Draw traversal performs several drawing steps which must be executed         * in the appropriate order:         *         *      1. Draw the background         *      2. If necessary, save the canvas' layers to prepare for fading         *      3. Draw view's content         *      4. Draw children         *      5. If necessary, draw the fading edges and restore layers         *      6. Draw decorations (scrollbars for instance)         */        // Step 1, draw the background, if needed        int saveCount;        if (!dirtyOpaque) {            drawBackground(canvas);        }        // skip step 2 & 5 if possible (common case)        final int viewFlags = mViewFlags;        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;        if (!verticalEdges && !horizontalEdges) {            // Step 3, draw the content            if (!dirtyOpaque) onDraw(canvas);            // Step 4, draw the children            dispatchDraw(canvas);            // Overlay is part of the content and draws beneath Foreground            if (mOverlay != null && !mOverlay.isEmpty()) {                mOverlay.getOverlayView().dispatchDraw(canvas);            }            // Step 6, draw decorations (foreground, scrollbars)            onDrawForeground(canvas);            // we're done...            return;        }       //...    }

源码还是比较复杂的,不过注释倒是很清晰的。所以结合注释看源码,倒也算是一种正确的姿势。

注释当中清晰的说明了绘制流程的6个步骤。

  1. 绘制View背景。drawBackground
  2. 保存当前图层信息。(可跳过)
  3. 绘制View内容。onDraw(canvas)
  4. 绘制子View。(如果有的话)。dispatchDraw(canvas)
  5. 绘制View的褪色边缘,类似于阴影效果。(可跳过)
  6. 绘制View的装饰。(比如滚动条)。onDrawForeground(canvas)

Skip 1 : 绘制背景

这里调用的是 View#drawBackground方法,源码如下:

private void drawBackground(Canvas canvas) {    //mBackground是该View的背景参数,比如背景颜色    final Drawable background = mBackground;    if (background == null) {        return;    }    //根据View四个布局参数来确定背景的边界    setBackgroundBounds();    ...    //获取当前View的mScrollX和mScrollY值    final int scrollX = mScrollX;    final int scrollY = mScrollY;    if ((scrollX | scrollY) == 0) {        background.draw(canvas);    } else {        //如果scrollX和scrollY有值,则对canvas的坐标进行偏移,再绘制背景        canvas.translate(scrollX, scrollY);        background.draw(canvas);        canvas.translate(-scrollX, -scrollY);    }}

Skip 3 : 绘制内容

View#onDraw(Canvas)方法是空实现,这是因为不同View有不同的内容,所以具体各个子View都有不同的绘制内容。而我们在自定义View的时候,也一定要重写该方法。

Skip 4 : 绘制子View

如果当前View是ViewGroup容器类,那么它就要去循环遍历绘制它的子View了。而View#dispatchDraw是空实现,只有ViewGroup#dispatchDraw才有具体实现。

ViewGroup#dispatchDraw方法的主要内容是 遍历所有子View,对每个子View调用ViewGroup#drawChild方法,该方法源码如下:

//ViewGroup#drawChildprotected boolean drawChild(Canvas canvas, View child, long drawingTime) {     return child.draw(canvas, this, drawingTime);}

这里调用的是 View#draw(Canvas canvas, ViewGroup parent, long drawingTime)方法。这其实是View#draw(Canvas canvas)的重载方法.

而三个参数的draw方法,则会先判断是否有缓存,如果有缓存,则利用缓存显示。如果没有,则 调用draw(canvas)方法,开始正常绘制流程(就是那6个步骤)。

Skip 6 : 绘制装饰

所谓绘制装饰,就是指View除了背景,内容,子View的其余部分。(比如滚动条)。

调用的是 View#onDrawForeground方法,该过程和一般的绘制流程比较相似,都是先设定绘制区域,然后再利用 canvas进行绘制。

自此,view的draw流程就已经分析完了。

其实在这篇博客中,自己思考分析原创部分比较少,而参考别人的内容比较多。

而其实 对于我们自定义控件而言,如果是自定义ViewGroup,则不用重写onDraw方法,它默认就会遍历所有子View进行绘制。

而如果自定义View的话,那么需要重写onDraw方法,然后结合
Paint来使用canvas.drawRect, canvas#drawText等方法来绘制出我们所需要的内容,所以这里要对相关的API比较熟悉。

我觉的这就像之前写的那篇 一样,虽然知道相关流程,但是对相关API不熟悉,其实也无力去实现很多功能。

因此未来如果有时间的话,希望能结合具体的自定义控件,和相关的常用API来就相关过程,进行更详细的讨论吧。


参考内容:

  • 任玉刚 《Anroid开发艺术探索》

作者:

github:

欢迎对于本人的博客内容批评指点,如果问题,可评论或邮件(yaowen369@gmail.com)联系

欢迎转载,转载请注明出处.谢谢

你可能感兴趣的文章
Android 在安装完成界面,点击打开应用程序。在应用程序点击home键,再从桌面打开程序导致产生多个实例或者说程序被重复打开...
查看>>
《软件工程课程总结》
查看>>
js+cookie 购物车
查看>>
Luogu P5296 [北京省选集训2019]生成树计数
查看>>
ActiveMq-拦截创建消息队列
查看>>
【6】使用nginx
查看>>
怎么在html页面和js里判断是否是IE浏览器
查看>>
WPF: 在MVVM中使用Navigtaion
查看>>
非RootLayer的隐式动画
查看>>
机器学习问题方法总结
查看>>
application,session,cookie三者之间的区别和联系
查看>>
模拟,找次品硬币,Counterfeit Dollar(POJ 1013)
查看>>
广搜最短路(最短时间到达目的地),POJ(3669)
查看>>
setNeedsDisplay setNeedDisplayInRect
查看>>
从零开始学架构三 高性能
查看>>
@ConfigurationProperties和@EnableConfigurationProperties配合使用
查看>>
Linux设备模型(3)_Uevent【转】
查看>>
linux-3.2.36内核启动2-setup_arch中的内存初始化1(arm平台 分析高端内存和初始化memblock)【转】...
查看>>
linux 路由表设置 之 route 指令详解【转】
查看>>
Linux-0.00运行环境搭建【转】
查看>>