Android View繪制流程


Android View繪制流程

img

如上圖,Activity的window組成,Activity內部有個Window成員,它的實例為PhoneWindow,PhoneWindow有個內部類是DecorView,這個DecorView就是存放布局文件的,里面有TitleActionBar和我們setContentView傳入進去的layout布局文件

Window類時一個抽象類,提供繪制窗口的API
PhoneWindow是繼承Window的一個具體的類,該類內部包含了一個DecorView對象,該DectorView對象是所有應用窗口(Activity界面)的根View
DecorView繼承FrameLayout,里面id=content的就是我們傳入的布局視圖
依據面向對象從抽象到具體我們可以類比上面關系就像如下:
Window是一塊電子屏,PhoneWindow是一塊手機電子屏,DecorView就是電子屏要顯示的內容,Activity就是手機電子屏安裝位置

View 繪制中主要流程分為measure,layout, draw 三個階段。

measure :根據父 view 傳遞的 MeasureSpec 進行計算大小。

layout :根據 measure 子 View 所得到的布局大小和布局參數,將子View放在合適的位置上。

draw :把 View 對象繪制到屏幕上。

一、Measure

1、MeasureSpec

MeasureSpec 封裝了從父級傳遞到子級的布局要求。每個 MeasureSpec 代表對寬度或高度的要求。MeasureSpec 由大小和模式組成。

MeasureSpec 一個32位二進制的整數型,前面2位代表的是mode,后面30位代表的是size。mode 主要分為3類,分別是

EXACTLY:父容器已經測量出子View的大小。對應是 View 的LayoutParams的match_parent 或者精確數值。

AT_MOST:父容器已經限制子view的大小,View 最終大小不可超過這個值。對應是 View 的LayoutParams的wrap_content

UNSPECIFIED:父容器不對View有任何限制,要多大給多大,這種情況一般用於系統內部,表示一種測量的狀態。(這種不怎么常用)
View 的 MeasureSpec 並不是父 View 獨自決定,它是根據父 view 的MeasureSpec加上子 View 的自己的 LayoutParams,通過相應的規則轉化。

2、流程

在measure 方法,核心就是調用onMeasure( ) 進行View的測量。在onMeasure( )里面,獲取到最小建議值,如果父類傳遞過來的模式是MeasureSpec.UNSPECIFIED,也就是父View大小未定的情況下,使用最小建議值,如果是AT_MOST或者EXACTLY模式,則設置父類傳遞過來的大小。
然后調用setMeasuredDimension 方法進行存儲大小。

二、Layout

確定 View 在父 View 的位置進行排版布局

三、draw

draw 過程中一共分成7步,其中兩步我們直接直接跳過不分析了。

image-20211116131514724

第一步:drawBackground(canvas): 作用就是繪制 View 的背景。

第三步:onDraw(canvas) :繪制 View 的內容。View 的內容是根據自己需求自己繪制的,所以方法是一個空方法,View的繼承類自己復寫實現繪制內容。

第三步:dispatchDraw(canvas):遍歷子View進行繪制內容。在 View 里面是一個空實現,ViewGroup 里面才會有實現。在自定義 ViewGroup 一般不用復寫這個方法,因為它在里面的實現幫我們實現了子 View 的繪制過程,基本滿足需求。

第四步:onDrawForeground(canvas):對前景色跟滾動條進行繪制。

第五步:drawDefaultFocusHighlight(canvas):繪制默認焦點高亮

四、自定義視圖類

1、步驟

1、自定義View的屬性

2、在View的構造方法中獲得我們自定義的屬性

3、重寫onMeasure

4、重寫onDraw

2、定義自定義屬性

自定義View的屬性,首先在res/values/ 下建立一個attrs.xml , 在里面定義我們的屬性和聲明我們的整個樣式。

如需在自定義視圖中啟用此行為,您必須:

  • <declare-styleable> 資源元素中定義視圖的自定義屬性
  • 在 XML 布局中指定屬性值
  • 在運行時檢索屬性值
  • 將檢索到的屬性值應用到視圖
<resources>
    <declare-styleable name="CustomView">
        <attr name="titleText" format="string"/>
        <attr name="titleTextColor" format="color"/>
        <attr name="titleTextSize" format="dimension"/>
    </declare-styleable>
</resources>

定義了自定義屬性后,便可像內置屬性一樣在布局 XML 文件中使用它們。唯一的區別是自定義屬性屬於另一個命名空間。它們不屬於 http://schemas.android.com/apk/res/android 命名空間,而是屬於 http://schemas.android.com/apk/res/[your package name]

當然在我的Android Studio中我改用xmlns:custom="http://schemas.android.com/apk/res-auto",因為當前工程是做為lib使用,那么你如上所寫 ,會出現找不到自定義屬性的錯誤 。這時候你就可以寫成xmlns:custom="http://schemas.android.com/apk/res-auto"

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.example.uidesign.CustomView
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:layout_centerInParent="true"
        custom:titleText="liming"
        custom:titleTextColor="#ff0000"
        custom:titleTextSize="40sp"/>
</RelativeLayout>

3、應用自定義屬性

通過 XML 布局創建視圖時,XML 標記中的所有屬性都會從資源包讀取,並作為 AttributeSet 傳遞到視圖的構造函數中。雖然可以直接從 AttributeSet 讀取值,但這樣做有一些弊端:

  • 不解析屬性值中的資源引用
  • 不應用樣式

請改為將 AttributeSet 傳遞給 obtainStyledAttributes()。此方法會傳回一個 TypedArray 數組,其中包含已解除引用並設置了樣式的值。

Android 資源編譯器做了大量工作,以便您更輕松地調用 obtainStyledAttributes()。對於 res 目錄中的各個 <declare-styleable> 資源,生成的 R.java 定義一個由屬性 ID 組成的數組,同時定義一組常量,用於定義該數組中各屬性的索引。您可以使用預定義的常量從 TypedArray 讀取屬性。

public class CustomView extends View {
    private String mTitleText;
    private int mTitleTextColor;
    private int mTitleTextSize;
    private Rect rect;
    private Paint paint;
    public CustomView(Context context, AttributeSet attrs) {
        super(context,attrs);
        //為自定義View添加事件
        this.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mTitleText=randomText();
                postInvalidate();
            }
        });
        TypedArray a = context.getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.CustomView,
                0, 0);
        try {
            mTitleText = a.getString(R.styleable.CustomView_titleText);
            mTitleTextColor=a.getColor(R.styleable.CustomView_titleTextColor,Color.BLACK);
            mTitleTextSize=a.getDimensionPixelSize(R.styleable.CustomView_titleTextSize,(int) TypedValue.applyDimension(
                    TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
        } finally {
            a.recycle();
        }
    }
    //重寫onMeasure
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int width;
        int height ;
        if (widthMode == MeasureSpec.EXACTLY)
        {
            width = widthSize;
        } else
        {
            paint.setTextSize(mTitleTextSize);
            paint.getTextBounds(mTitleText, 0, mTitleText.length(), rect);
            float textWidth = rect.width();
            int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
            width = desired;
        }

        if (heightMode == MeasureSpec.EXACTLY)
        {
            height = heightSize;
        } else
        {
            paint.setTextSize(mTitleTextSize);
            paint.getTextBounds(mTitleText, 0, mTitleText.length(), rect);
            float textHeight = rect.height();
            int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
            height = desired;
        }
        setMeasuredDimension(width, height);
    }
    //重寫onDraw
    @Override
    protected void onDraw(Canvas canvas) {
        paint.setColor(Color.YELLOW);
        canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint);

        paint.setColor(mTitleTextColor);
        canvas.drawText(mTitleText, getWidth() / 2 - rect.width() / 2, getHeight() / 2 + rect.height() / 2, paint);
    }
}

TypedArray 對象是共享資源,必須在使用后回收。

由於系統幫我們測量的高度和寬度都是MATCH_PARNET,當我們設置明確的寬度和高度時,系統幫我們測量的結果就是我們設置的結果,當我們設置為WRAP_CONTENT,或者MATCH_PARENT系統幫我們測量的結果就是MATCH_PARENT的長度。

所以,當設置了WRAP_CONTENT時,我們需要自己進行測量,即重寫onMesure方法。重寫之前先了解MeasureSpec的specMode,一共三種類型:

EXACTLY:一般是設置了明確的值或者是MATCH_PARENT

AT_MOST:表示子布局限制在一個最大值內,一般為WARP_CONTENT

UNSPECIFIED:表示子布局想要多大就多大,很少使用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM