自定義View構造函數,知多少?

構造函數

View有四個構造函數以下php

public View(Context context) {}
    public View(Context context, AttributeSet attrs) {}
    public View(Context context, AttributeSet attrs, int defStyleAttr) {}
    public View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
複製代碼

本文先以TextView爲例理論講解這四個構造函數如何使用,再用一個自定義View來進行實戰。java

一個參數的構造函數

一個參數的構造函數是在代碼中建立的,例如把一個TextView添加到Activity佈局中android

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 建立TextView對象,並設置屬性
        TextView textView = new TextView(this);
        textView.setText(R.string.app_name);
        textView.setTextSize(30);
        // 把TextView對象添加到佈局中
        setContentView(textView);
    }
}
複製代碼

從這個例子中能夠發現,使用一個參數的構造函數建立對象後,須要手動調用設置屬性的方法。canvas

兩個參數的構造函數

如今假設在一個佈局中聲明瞭一個TextView控件,代碼以下app

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
    
    <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:textSize="24sp" />

</RelativeLayout>
複製代碼

系統在解析這個XML佈局的時候,會使用TextView兩個參數的構造函數,代碼以下ide

public TextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.textViewStyle);
    }
複製代碼

第二個參數AttributeSet attrs就表明了在XML中爲TextView聲明的屬性集,咱們能夠利用它解析出聲明的屬性值,例如android:textandroid:textSize函數

三個參數的構造函數

咱們知道,能夠經過Theme全局控制控件的樣式,其中的原理就是使用三個參數的構造函數佈局

三個參數構造函數的使用方式有點特別,通常是二個參數的構造函數中傳入一個Theme中的屬性this

public TextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.textViewStyle);
    }

    public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }
複製代碼

能夠看到,TextView兩個參數的構造函數調用了三個參數的構造函數,而第三個參數使用的值就是Theme中的com.android.internal.R.attr.textViewStyle屬性值。spa

若是咱們想覆蓋Theme中的com.android.internal.R.attr.textViewStyle,就須要自定義這個屬性的值,代碼以下

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> <!--定義TextView使用的屬性--> <item name="android:textViewStyle">@style/MyTextViewStyle</item> </style>
    
    <!--自定義TextView的顏色-->
    <style name="MyTextViewStyle" parent="Widget.AppCompat.TextView"> <item name="android:textColor">@color/colorAccent</item> </style>
複製代碼

四個參數的構造函數

Theme是全局控制樣式的,可是時候咱們只想爲某幾個TextView單獨定義樣式,那就得使用四個參數的構造函數。

四個參數構造函數的使用方式,通常是在三個參數的構造函數中調用,並傳入自定義Style

首先,在styles.xml中聲明一個TextView使用的Style

<style name="CustomTextViewStyle" parent="Widget.AppCompat.TextView" > <item name="android:textColor">@color/colorPrimaryDark</item> </style>
複製代碼

這個Style只是簡單定義了TextView的文本顏色。

而後自定義一個繼承自TextView的控件

public class MyTextView extends TextView {
    public MyTextView(Context context) {
        this(context, null);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs) {
        // 注意,這裏的第三個參數爲0
        this(context, attrs, 0);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, R.style.CustomTextViewStyle);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
}
複製代碼

在兩個參數的構造函數中,調用了三個參數的構造函數,可是傳入的第三個參數的值爲0。至因而什麼緣由,後面會講到。

在三個參數的構造函數中,調用四個參數的構造函數,第四個參數須要傳入自定義的Style

屬性值的覆蓋規則

既然有這麼多地方能控制屬性值,那麼就有個有限順序。其實能夠從四個參數的obtainStyledAttributes()方法中看到這個規則

public final TypedArray obtainStyledAttributes( AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {}
複製代碼

第一個參數AttributeSet set指的是XML中聲明的屬性集。

第三個參數int defStyleAttr指的是Theme中的控制控件的屬性。

第四個參數int defStyleRes指的是自定義的Style

那麼最簡單屬性值獲取的優先規則就是第一個參數,第三個參數,第四個參數。

若是在XML給控件使用style屬性呢?它的優先級是介於第一個參數和第三個參數之間。

那麼最終的優先規則以下

  1. XML中屬性
  2. XML中style屬性
  3. Theme中屬性
  4. 自定義Style

自定義View

如今,經過一個自定義View演示四個構造函數如何使用。

自定義View名字叫SimpleView,在這個控件中,只簡單繪製一個圓,而且它有一個自定義屬性,能夠控制圓的顏色。

首先聲明SimpleView使用的自定義屬性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--SimpleView的自定義屬性,控制圓的顏色-->
    <declare-styleable name="SimpleView">
        <attr name="circleColor" format="color|reference" />
    </declare-styleable>
</resources>
複製代碼

而後,在兩個參數的構造函數中解析這個屬性顏色值,並使用這個顏色值繪製圓

public class SimpleView extends View {
    Paint mPaint = new Paint();
    int mColor = Color.BLACK;
    int mCenterX, mCenterY;
    int mRadius;

    public SimpleView(Context context) {
        this(context, null);
    }

    public SimpleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        // 獲取XML中聲明的屬性集,並解析屬性
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SimpleView);
        for (int i = 0; i < a.getIndexCount(); i++) {
            int attrIndex = a.getIndex(i);
            if (attrIndex == R.styleable.SimpleView_circleColor) {
                mColor = a.getColor(attrIndex, Color.BLACK);
            }
        }
        a.recycle();
        mPaint.setColor(mColor);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        mCenterX = w / 2;
        mCenterY = h / 2;
        mRadius = w < h ? w / 4 : h / 4;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint);
    }
}
複製代碼

SimpleView一個參數的構造函數調用了兩個參數的構造函數,在兩個參數的構造函數中解析了自定義的屬性circleColor

那麼如今,在XML中使用SimpleView,而後設置circleColor屬性,就能夠繪製你想要的顏色的圓

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto">

    <com.umx.viewconstructortest.SimpleView app:circleColor="@color/colorAccent" android:layout_width="wrap_content" android:layout_height="wrap_content" />

</RelativeLayout>
複製代碼

經過Theme控制樣式

如今,咱們想經過Theme的屬性控制SimpleView樣式。按照以前所說,那就須要一個屬性並在Theme中聲明,而後經過三個參數的構造函數來完成Theme的控制。

首先定義Theme使用的屬性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--SimpleView的自定義屬性-->
    <declare-styleable name="SimpleView">
        <attr name="circleColor" format="color|reference" />
    </declare-styleable>

    <!--在Theme中控制SimpleView樣式的屬性-->
    <attr name="SimpleViewStyle" format="reference" />
</resources>
複製代碼

SimpleViewStyle就是SimpleView要使用的Theme的屬性。

如今,咱們在Theme中加入這個屬性

<resources>
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> <!--SimpleViewStyle控制SimpleView樣式--> <item name="SimpleViewStyle">@style/MySimpleViewStyle</item> </style>

    <style name="MySimpleViewStyle"> <!--統一控制SimpleView使用的顏色爲黑色--> <item name="circleColor">#000000</item> </style>
</resources>
複製代碼

這一切準備就緒後,就來實現三個參數的構造函數

public class SimpleView extends View {

    public SimpleView(Context context) {
        this(context, null);
    }

    public SimpleView(Context context, @Nullable AttributeSet attrs) {
        // 調用三個參數的構造函數,傳入的第三個參數爲Theme中聲明的SimpleViewStyle屬性
        this(context, attrs, R.attr.SimpleViewStyle);
    }

    public SimpleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 注意,這裏使用的obtainStyledAttributes方法有四個參數
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SimpleView, defStyleAttr, 0);
        for (int i = 0; i < a.getIndexCount(); i++) {
            int attrIndex = a.getIndex(i);
            if (attrIndex == R.styleable.SimpleView_circleColor) {
                mColor = a.getColor(attrIndex, Color.BLACK);
            }
        }
        a.recycle();
        mPaint.setColor(mColor);
    }
}
複製代碼

在兩個參數的構造函數中調用了三個參數的構造函數,而且第三個參數傳入的就是剛纔自定義且在Theme中聲明的屬性。而後在三個參數的構造函數中,爲了使用剛剛傳入的Theme屬性,必須使用有四個參數的obtainStyledAttributes()方法,這裏必定要注意。

而後在三個參數的構造函數中完成了自定義屬性的解析,取代兩個參數的構造函數的工做。

自定義Style控制樣式

若是你想自定義一個擁有特殊樣式的SimpleView,按照前面的分析,你須要使用四個參數的構造函數,而且須要繼承SimpleView

首先,須要在SimpleView中加入四個參數的構造函數

public class SimpleView extends View {
    public SimpleView(Context context) {
        this(context, null);
    }

    public SimpleView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, R.attr.SimpleViewStyle);
    }

    public SimpleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public SimpleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        // 注意,這裏也是使用四個參數的obtainStyledAttributes()方法
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SimpleView, defStyleAttr, defStyleRes);
        for (int i = 0; i < a.getIndexCount(); i++) {
            int attrIndex = a.getIndex(i);
            if (attrIndex == R.styleable.SimpleView_circleColor) {
                mColor = a.getColor(attrIndex, Color.BLACK);
            }
        }
        a.recycle();
        mPaint.setColor(mColor);
    }
}    
複製代碼

實現四個參數的構造函數很是簡單,只須要在三個參數的構造函數中調用四個參數的構造函數,而後把第四個參數傳入0便可。同時我把屬性的解析移到了四個參數的構造函數中。

如今,定義一個繼承子SimpleView的類CustomSimpleView,而且傳入自定義的樣式

public class CustomSimpleView extends SimpleView {
    
    public CustomSimpleView(Context context) {
        this(context, null);
    }

    public CustomSimpleView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomSimpleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, R.style.CustomSimpleViewStyle);
    }

    public CustomSimpleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
}
複製代碼

CustomSimpleView的兩個參數的構造函數中,調用了三個參數的構造函數,然而第三個參數傳入的0,也就是說CustomSimpleView不想使用Theme控制它的樣式。

CustomSimpleView的三個參數的構造函數中,調用了四個參數的構造函數,而且第四個參數傳入了自定義的樣式R.style.CustomSimpleViewStyle

CustomSimpleViewStyle自定義樣式以下

<resources>
    <style name="CustomSimpleViewStyle"> <item name="circleColor">#ff0000</item> </style>
</resources>
複製代碼

如此一來,CusstomSimpleView就不能經過Theme控制樣式,而使用的是自定義的Style控制樣式。

結束

日常咱們可能用不到這些知識,可是在寫系統控件的時候,這個就不能忽視,尤爲在咱們自定義系統的Theme的時候,就顯得尤其重要。

相關文章
相關標籤/搜索