Android 自定义view系列 —— 认识绘制顺序

  • 1、简介

Android 的绘制是按顺序绘制的,先绘制的内容会被后绘制的内容所覆盖,而且Android 里面关于绘制的方法是比较多的,onDraw()、dispatchDraw()、onDrawForeground()、draw()方法在绘制顺序里面也起到关键的做用,本文主要在于介绍这些方法在绘制顺序中起到的一些做用android

  • 2、关键的方法

  • onDraw()

一、 onDraw() 方法介绍:

是绘制过程当中最为经常使用的重写方法,经过重写onDraw()方法能够绘制出不少不一样形状的图形,文字,例子以下canvas

override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        var drawable = drawable
        if (BuildConfig.DEBUG && drawable!=null){
            canvas?.save()
            canvas?.concat(imageMatrix)
            var bounds = drawable.bounds
            canvas?.drawText(resources.getString(R.string.image_size,bounds.width(),bounds.height()),20f,40f,paint)
            canvas?.restore()
        }
    }
复制代码

二、咱们draw的一些代码是要放在super.onDraw()前仍是后?

放在 super.onDraw()以前 :

因为绘制的代码都在原控件的绘制以前调用,因此绘制的内容会被原控件的绘制内容所遮挡。
这里例子展现了一个带背景颜色的TextView,因为在super.onDraw(),就已经绘制了颜色,后面写的一些文字的内容都会有带背景颜色,eg:bash

override fun onDraw(canvas: Canvas?) {
        var layout = layout
        bounds.left = layout.getLineLeft(0)
        bounds.right = layout.getLineRight(0)
        bounds.top = layout.getLineTop(0).toFloat()
        bounds.bottom = layout.getLineBottom(0).toFloat()
        canvas?.drawRect(bounds,paint)
        super.onDraw(canvas)
    }
复制代码

放在 super.onDraw()以后 :

因为绘制代码会在原有内容绘制结束以后才执行,因此绘制内容就会覆盖控件原来的内容。
这里例子展现了一个在debug状况下会在ImageView控件中输出图片尺寸的一个demo,eg:ide

//draw() 是绘制过程的总调度方法。一个 View 的整个绘制过程都发生在 draw() 方法里。前面讲到的背景、主体、子 View 、滑动相关以及前景的绘制,它们其实都是在 draw() 方法里的。
    override fun draw(canvas: Canvas?) {
        super.draw(canvas)
        // 在 super.draw() 的下方插入绘制代码,让绘制内容盖住其余全部
        paint.color = Color.parseColor("#f44336")
        canvas?.drawRect(0f, 40f, 200f, 120f, paint)
        paint.color = Color.WHITE
        canvas?.drawText("New", 20f, 100f, paint)

    }
复制代码

  • dispatchDraw()

一、dispatchDraw() 方法介绍:

在子view绘制完以后才调用的方法,能够经过重写这个方法让你绘制的内容覆盖在子view上面,若没有经过这个方法,父控件绘制的内容会被子控件所覆盖优化

override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        // 在 super.onDraw() 的下方插入绘制代码,绘制一些点
        drawPoint(canvas = canvas)
    }
复制代码
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
>
    <com.zy.draworder.AfterDispatchDrawView
            android:layout_width="match_parent"
            android:layout_height="match_parent">

    <ImageView
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:src="@drawable/logo"
    />
    </com.zy.draworder.AfterDispatchDrawView>
</FrameLayout>
复制代码

二、咱们draw的一些代码是要放在super.dispatchDraw()前仍是后?

放在 super.dispatchDraw()以前 :

把绘制代码写在 super.dispatchDraw() 的上面,这段绘制就会在子view的 onDraw() 以后、 super.dispatchDraw() 以前发生,也就是绘制内容会出如今主体内容和子 View 之间。ui

//把绘制代码写在 super.dispatchDraw() 的上面,这段绘制就会在 onDraw() 以后、 super.dispatchDraw() 以前发生,也就是绘制内容会出如今主体内容和子 View 之间。
    override fun dispatchDraw(canvas: Canvas?) {
        drawPoint(canvas = canvas)
        super.dispatchDraw(canvas)
    }
    
    fun drawPoint(canvas: Canvas?){
        paint.color = Color.GREEN
        canvas?.drawCircle(width * 0.1f,height * 0.1f,55f,paint)
        canvas?.drawCircle(width * 0.2f,height * 0.2f,15f,paint)
        canvas?.drawCircle(width * 0.3f,height * 0.3f,15f,paint)
        canvas?.drawCircle(width * 0.4f,height * 0.4f,15f,paint)
        canvas?.drawCircle(width * 0.5f,height * 0.5f,15f,paint)

        paint.color = Color.RED
        canvas?.drawCircle(width * 0.9f,height * 0.1f,55f,paint)
        canvas?.drawCircle(width * 0.8f,height * 0.2f,15f,paint)
        canvas?.drawCircle(width * 0.7f,height * 0.3f,15f,paint)
        canvas?.drawCircle(width * 0.6f,height * 0.4f,15f,paint)

        paint.shader = RadialGradient(width * 0.1f,height * 0.1f,55f,Color.parseColor("#E91E63"),
            Color.parseColor("#2196F3"), Shader.TileMode.CLAMP)
        canvas?.drawCircle(width * 0.5f,height * 0.5f,55f,paint)
    }
复制代码

放在 super.dispatchDraw()以后 :

把绘制代码写在 super.dispatchDraw() 的下面,这段绘制就会在子view的 onDraw() 以后、 super.dispatchDraw() 以后发生,也就是绘制内容会出如今主体内容和子 View 内容的后面,从而让绘制内容盖住子 View 了。spa

//只要重写 dispatchDraw(),并在 super.dispatchDraw() 的下面写上你的绘制代码,这段绘制代码就会发生在子 View 的绘制以后,从而让绘制内容盖住子 View 了。
    override fun dispatchDraw(canvas: Canvas?) {
        super.dispatchDraw(canvas)
        drawPoint(canvas = canvas)
    }
    
    fun drawPoint(canvas: Canvas?){
        paint.color = Color.GREEN
        canvas?.drawCircle(width * 0.1f,height * 0.1f,55f,paint)
        canvas?.drawCircle(width * 0.2f,height * 0.2f,15f,paint)
        canvas?.drawCircle(width * 0.3f,height * 0.3f,15f,paint)
        canvas?.drawCircle(width * 0.4f,height * 0.4f,15f,paint)
        canvas?.drawCircle(width * 0.5f,height * 0.5f,15f,paint)

        paint.color = Color.RED
        canvas?.drawCircle(width * 0.9f,height * 0.1f,55f,paint)
        canvas?.drawCircle(width * 0.8f,height * 0.2f,15f,paint)
        canvas?.drawCircle(width * 0.7f,height * 0.3f,15f,paint)
        canvas?.drawCircle(width * 0.6f,height * 0.4f,15f,paint)

        paint.shader = RadialGradient(width * 0.1f,height * 0.1f,55f,Color.parseColor("#E91E63"),
            Color.parseColor("#2196F3"), Shader.TileMode.CLAMP)
        canvas?.drawCircle(width * 0.5f,height * 0.5f,55f,paint)
    }
复制代码

  • onDrawForeground()

一、 onDrawForeground() 方法介绍:

这个方法是 API 23 才引入的,因此在重写这个方法的时候要确认你的 minSdk 达到了 23,否则低版本的手机装上你的软件会没有效果。
在 onDrawForeground() 中,会依次绘制滑动边缘渐变、滑动条和前景。因此若是你重写 onDrawForeground()debug

二、咱们draw的一些代码是要放在super.onDrawForeground()前仍是后?

放在 super.onDrawForeground()以前 :

若是你把绘制代码写在了 super.onDrawForeground() 的上面,绘制内容就会在 dispatchDraw() 和 super.onDrawForeground() 之间执行,那么绘制内容会盖住子 View,但被滑动边缘渐变、滑动条以及前景盖住:3d

override fun onDrawForeground(canvas: Canvas?) {
        paint.color = Color.parseColor("#f44336")
        canvas?.drawRect(0f, 40f, 200f, 120f, paint)
        paint.color = Color.WHITE
        canvas?.drawText("Foreground", 20f, 100f, paint)
        super.onDrawForeground(canvas)
    }
复制代码

放在 super.onDrawForeground()以后 :

若是你把绘制代码写在了 super.onDrawForeground() 的下面,绘制代码会在滑动边缘渐变、滑动条和前景以后被执行,那么绘制内容将会盖住滑动边缘渐变、滑动条和前景。rest

override fun onDrawForeground(canvas: Canvas?) {
        super.onDrawForeground(canvas)
        //在super.onDrawForeground() 的下方插入绘制代码,让绘制内容盖住前景
        paint.color = Color.parseColor("#f44336")
        canvas?.drawRect(0f, 40f, 200f, 120f, paint)
        paint.color = Color.WHITE
        canvas?.drawText("Foreground", 20f, 100f, paint)
    }
复制代码

  • draw()

一、 draw() 方法介绍:

draw() 是绘制过程的总调度方法。一个 View 的整个绘制过程都发生在 draw() 方法里。前面讲到的背景、主体、子 View 、滑动相关以及前景的绘制,它们其实都是在 draw() 方法里的。

public void draw(Canvas canvas) {
    ...
    
    drawBackground(Canvas); // 绘制背景(不能重写)
    onDraw(Canvas); // 绘制主体
    dispatchDraw(Canvas); // 绘制子 View
    onDrawForeground(Canvas); // 绘制滑动相关和前景
    
    ...
}
复制代码

二、咱们draw的一些代码是要放在super.draw()前仍是后?

放在 super.draw()以前 :

因为 draw() 是总调度方法,因此若是把绘制代码写在 super.draw() 的上面,那么这段代码会在其余全部绘制以前被执行,因此这部分绘制内容会被其余全部的内容盖住,包括背景。是的,背景也会盖住它。

override fun draw(canvas: Canvas?) {
        //于 draw() 是总调度方法,因此若是把绘制代码写在 super.draw() 的上面,那么这段代码会在其余全部绘制以前被执行,因此这部分绘制内容会被其余全部的内容盖住,包括背景。是的,背景也会盖住它。
        canvas?.drawColor(Color.parseColor("#00BB6A")); // 涂上绿色
        super.draw(canvas)
    }
复制代码

放在 super.draw()以后 :

因为 draw() 是总调度方法,因此若是把绘制代码写在 super.draw() 的下面,那么这段代码会在其余全部绘制完成以后再执行,也就是说,它的绘制内容会盖住其余的全部绘制内容。

它的效果和重写 onDrawForeground(),并把绘制代码写在 super.onDrawForeground() 下面时的效果是同样的:都会盖住其余的全部内容。

override fun draw(canvas: Canvas?) {
        super.draw(canvas)
        // 在 super.draw() 的下方插入绘制代码,让绘制内容盖住其余全部
        paint.color = Color.parseColor("#f44336")
        canvas?.drawRect(0f, 40f, 200f, 120f, paint)
        paint.color = Color.WHITE
        canvas?.drawText("New", 20f, 100f, paint)
    }
复制代码

  • 总结:

1. 掌握绘制顺序的一些方法很是的重要,特别在自定义view 有子view ,及要在前景背景作一些操做的时候,要注意后他跟super.xx()调用先后的关系

2. 出于效率的考虑,ViewGroup 默认会绕过 draw() 方法,换而直接执行 dispatchDraw(),以此来简化绘制流程。因此若是你自定义了某个 ViewGroup 的子类(好比 LinearLayout)而且须要在它的除 dispatchDraw() 之外的任何一个绘制方法内绘制内容,你可能会须要调用 View.setWillNotDraw(false) 这行代码来切换到完整的绘制流程(是「可能」而不是「必须」的缘由是,有些 ViewGroup 是已经调用过 setWillNotDraw(false) 了的,例如 ScrollView)。

3. 有的时候,一段绘制代码写在不一样的绘制方法中效果是同样的,这时你能够选一个本身喜欢或者习惯的绘制方法来重写。但有一个例外:若是绘制代码既能够写在 onDraw() 里,也能够写在其余绘制方法里,那么优先写在 onDraw() ,由于 Android 有相关的优化,能够在不须要重绘的时候自动跳过 onDraw() 的重复执行,以提高开发效率。享受这种优化的只有 onDraw() 一个方法。

相关文章
相关标签/搜索