先看兩個參數的構造函數:
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的樣式也會跟着改變的原因了。
最後附上一篇詳細講解這幾個屬性的文章,需要多讀幾遍然後實踐一下才能了解了。
Android中自定義樣式與View的構造函數中的第三個參數defStyle的意義
另外,清理下資源下的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中指定自己的屬性)那麼将會優先選擇控件自己制定的風格。
參考:Android中Styles、Themes、attrs介紹