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() 一個方法。

相關文章
相關標籤/搜索