1. 程式人生 > >自定義View中,四個引數的建構函式,其最後兩個引數的含義

自定義View中,四個引數的建構函式,其最後兩個引數的含義

先看兩個引數的建構函式:

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

四個引數的構造,省略了很多

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        this(context);

        final TypedArray a = context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);

        if (mDebugViewAttributes) {
            saveAttributeData(attrs, a);
        }

        Drawable background = null;

一個引數的構造:

 public View(Context context) {
        mContext = context;
        mResources = context != null ? context.getResources() : null;
        mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
        // Set some flags defaults
        mPrivateFlags2 =
                (LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) |
                (TEXT_DIRECTION_DEFAULT << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) |
                (PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT) |
                (TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) |
                (PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) |
                (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
        mUserPaddingStart = UNDEFINED_PADDING;
        mUserPaddingEnd = UNDEFINED_PADDING;
        mRenderNode = RenderNode.create(getClass().getName(), this);

        if (!sCompatibilityDone && context != null) {
            final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;

            // Older apps may need this compatibility hack for measurement.
            sUseBrokenMakeMeasureSpec = targetSdkVersion <= JELLY_BEAN_MR1;

            // Older apps expect onMeasure() to always be called on a layout pass, regardless
            // of whether a layout was requested on that View.
            sIgnoreMeasureCache = targetSdkVersion < KITKAT;

            Canvas.sCompatibilityRestore = targetSdkVersion < M;

            // In M and newer, our widgets can pass a "hint" value in the size
            // for UNSPECIFIED MeasureSpecs. This lets child views of scrolling containers
            // know what the expected parent size is going to be, so e.g. list items can size
            // themselves at 1/3 the size of their container. It breaks older apps though,
            // specifically apps that use some popular open source libraries.
            sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < M;

            // Old versions of the platform would give different results from
            // LinearLayout measurement passes using EXACTLY and non-EXACTLY
            // modes, so we always need to run an additional EXACTLY pass.
            sAlwaysRemeasureExactly = targetSdkVersion <= M;

            // Prior to N, layout params could change without requiring a
            // subsequent call to setLayoutParams() and they would usually
            // work. Partial layout breaks this assumption.
            sLayoutParamsAlwaysChanged = targetSdkVersion <= M;

            // Prior to N, TextureView would silently ignore calls to setBackground/setForeground.
            // On N+, we throw, but that breaks compatibility with apps that use these methods.
            sTextureViewIgnoresDrawableSetters = targetSdkVersion <= M;

            // Prior to N, we would drop margins in LayoutParam conversions. The fix triggers bugs
            // in apps so we target check it to avoid breaking existing apps.
            sPreserveMarginParamsInLayoutParamConversion = targetSdkVersion >= N;

            sCascadedDragDrop = targetSdkVersion < N;

            sCompatibilityDone = true;
        }
    }

預設情況下,最終會呼叫四個引數的建構函式,其他兩個引數的值都是0,最終第四個建構函式又呼叫了第一個。

我們在自定義view時,一般會重寫一個引數的構造(在activity中直接new 時使用),兩個引數的構造(xml中宣告時)。其他兩個引數的構造都是手動執行的,比如自定義view的時候,需要傳入一個預設的style,或是系統的控制元件比如Button:

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

    public Button(Context context, AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.buttonStyle);
    }

    public Button(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public Button(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

可以看出,在Button的四個建構函式中,無論我們從程式碼中new一個buttong還是從xml中聲明瞭一button,其最終都會執行四個構造的方法,而在第二個建構函式中,呼叫了三個引數的建構函式,並傳入了一個attrStyle,這個是系統為button定義的style,也就是說如果我們沒有在xml中為這個button指定屬性(也就是第二個引數attrs沒有傳入值),那麼系統預設會去使用預設的風格來裝飾這個button,前提是這個buttonStyle屬性在app或是activity的Theme中被引用了。
一句話就是說,如果我們的attrs沒有設定(比如直接code中new一個buttong,或是xml中沒有設定其他風格),那麼會去取預設的風格,而如果預設的風格被構造的時候傳入了0,或是APP theme中沒有找到對應的值,那麼第四個引數傳入的值才起了作用。
也就是說第三個引數,是跟app theme關聯的。也就是為什麼我們在給app或是activity設定不同的風格的時候,button的樣式也會跟著改變的原因了。

最後附上一篇詳細講解這幾個屬性的文章,需要多讀幾遍然後實踐一下才能理解了。

另外,清理下資源下的attr標籤和style標籤以及theme標籤,都在values資料夾中定義(檔案的命名並沒有具體的規定,只要是資原始檔,頭標籤是<resources>就行:

1、attrs.xml

比如,有如下定義:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="Customize">
        <attr name="attr_one" format="string"/>
        <attr name="attr_two" format="string"/>
        <attr name="attr_three" format="string"/>
        <attr name="attr_four" format="string"/>
    </declare-styleable>

    <attr name="CustomizeStyle" format="reference"/>
</resources>

上面定義的所有attr都會在R.attr這個類中被賦值:

public static final class attr {
    public static final int CustomizeStyle=0x7f010004;
    public static final int attr_one=0x7f010000;
    public static final int attr_two=0x7f010001;
    public static final int attr_three=0x7f010002;
    public static final int attr_four=0x7f010003;
}

而<declare-styleable>標籤下的會在R.styleable類中生成相關的陣列。

我的理解是,declare標籤下的是每種控制元件特有的屬性,而直接attr標籤則是所有控制元件都共有的屬性吧。


2、styles.xml

相當於是為這些定義的屬性賦值,也就是集合起來,一個組合就有一種風格

比如button:

<style name="Widget.Button">
        <item name="android:background">@android:drawable/btn_default</item>
        <item name="android:focusable">true</item>
        <item name="android:clickable">true</item>
        <item name="android:textAppearance">?android:attr/textAppearanceSmallInverse</item>
        <item name="android:textColor">@android:color/primary_text_light</item>
        <item name="android:gravity">center_vertical|center_horizontal</item>
    </style>

每個item裡面的name是在attrs.xml中定義好的屬性,而後面跟著的就是該屬性的具體值了,該值可以是個具體的值,也可以是另一個style或其他資源的引用(在其定義的時候其格式就是format=“reference”

在控制元件的xml中,我們可以為控制元件指定一個個的attr屬性值,也可以使用一個style來設定一組attr值,比如:

<TextView
        android:id ="@+id/tv1"
        android:layout_width ="wrap_content"
        android:layout_height ="wrap_content"
        android:textColor ="#FFFFFF" />

可以用如下代替:

<style name= "Sample1">
       
        < item name= "android:layout_width" >wrap_content </ item>
        < item name= "android:layout_height" >wrap_content </ item>
        < item name= "android:textColor" >#00FF00 </item >
       
    </style >
<TextView
        android:id ="@+id/tv1"
        style= "@style/Sample1"
        android:text ="@string/sample1_tv1" />

組成一個style被引用,其實可以起到複用的作用。

3、theme標籤,該標籤只能應用於整個app或是activity,android:theme="@style/mystyle"。其引用的是一個style集合,一旦聲明瞭一個theme,該app或是activity下的所有控制元件都預設使用這種風格,但是如果控制元件自己制定了自己的風格(比如說在xml中指定自己的屬性)那麼將會優先選擇控制元件自己制定的風格。