常見構造方式:
在Android 中要使用一個view,通常會有兩種方式,1、xml中 2、通過構造函數new出一個指定的view對象。
/**
* 關于view構造參數的詳解
*/
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout container = (LinearLayout) findViewById(R.id.activity_main);
/**
* 一個參數的構造函數
*/
Button buttonOne = new Button(this);
buttonOne.setText("(Context)");
/**
* 三個參數的構造函數
*/
Button buttonThree = new Button(this, null, 0);
buttonThree.setText("(Context,AttributeSet,0)");
container.addView(buttonOne);
container.addView(buttonThree);
}
}
顯然使用三個構造函數生成的button,樣式不正確而且無法完成點擊
View的構造函數:
通用構造函數展示如下:
public View(Context context);
public View(Context context, AttributeSet attrs);
public View(Context context, AttributeSet attrs, int defStyle);
關于button的構造函數展示如下:
public class Button extends TextView {
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 defStyle) {
super(context, attrs, defStyle);
}
}
Button 繼承自 TextView,顯然由構造函數我們可知,二者的區分主要來自com.android.internal.R.attr.buttonStyle這第三個參數。
View構造方法中的第三個參數:
- 用來給View提供一個基本的style,如果我們沒有對view設置某些屬性,就使用這個style的屬性。
通過三個參數的構造函數,進入TextView中查看構造方法,當中有一句關鍵代碼如下,:
TypedArray a = theme.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
obtainStyledAttributes方法介紹:
public TypedArray obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
- set:在XML中明確寫出的屬性集合。(比如:android:layout_width 等)
- attrs:需要我們在上面的set集合中查詢哪些內容,如果是自定義view,一般我們會把自定義的屬性寫在declare-styleable中,代表我們想要查詢這些自定義屬性。
- defStyleAttr:這是一個定義在attrs.xml文件中的attribute 這個值起作用需要兩個條件:1、值不為0 2、在Theme中出現過(出現即可)
- defStyleRes:這是在styles.xml文件中定義的一個style。只有當defStyleAttr沒有起作用,才會使用到這個值。
顯然,一個屬性最終的取值,是有一個順序的問題,這個順序的優先級從高到低依次是:
1、直接中在XML文件中定義。
2、在XML文件中通過style這個屬性定義。
3、通過defStyleAttr定義。
4、通過defStyleRes定義。
5、直接在當前工程的theme主題下定義。
關于com.android.internal.R.attr.buttonStyle 這個位于,frameworks\base\core\res\res\values\attrs.xml 中:
<attr name="buttonStyle" format="reference" />
只是定義了一個引用,在theme.xml文件下,有這樣一個style:
<style name="Theme">
...
<item name="buttonStyle">@android:style/Widget.Button</item>
...
</style>
在這里使用到了buttonStyle屬性,它指向另外一個style,這個style在styles.xml文件下:
<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>
這些屬性都是用來配置Button的,如果在XML文件中沒有給Button配置背景、內容的位置等屬性們就會默認的使用這里的屬性。當前這是在使用了defStyleAttr的情況下才會出現的。
上面的themes.xml中的那個style的名稱為Theme,而在我們自己的工程中,在配置menifest文件的時候,給application或者activity設置的主題android:theme一般都是這個style的子類,所以也就這樣使用到了defStyleAttr定義的屬性了
還有一個defStyleRes參數,我們可以發現在TextView、ImageView等控件中,這個值傳的都是0,也就是不使用它。它的作用就像是一個替補,當defStyleAttr不起作用的時候它就上場,因為它也是一個style,這個參數是怎么起作用的在下面的實例中有提到。
public class DefineView extends View {
static final String LOG_TAG = "DefineView";
public DefineView(Context context) {
this(context, null);
}
public DefineView(Context context, AttributeSet attrs) {
/**
* 在style中,theme style中建立對應的attrs和style之間的對應關系
*/
this(context, attrs, R.attr.defineViewStyle);
}
public DefineView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
/**
* 打印各個屬性的值
*/
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DefineView, defStyleAttr, 0);
Log.d(LOG_TAG, "attr1 => " + array.getString(R.styleable.DefineView_attr1));
Log.d(LOG_TAG, "attr2 => " + array.getString(R.styleable.DefineView_attr2));
Log.d(LOG_TAG, "attr3 => " + array.getString(R.styleable.DefineView_attr3));
Log.d(LOG_TAG, "attr4 => " + array.getString(R.styleable.DefineView_attr4));
Log.d(LOG_TAG, "attr5 => " + array.getString(R.styleable.DefineView_attr5));
Log.d(LOG_TAG, "attr6 => " + array.getString(R.styleable.DefineView_attr6));
Log.d(LOG_TAG, "attr6 => " + array.getString(R.styleable.DefineView_attr7));
}
}
<com.example.leiiiooo92.defineview.DefineView
style="@style/xml_style"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:attr1="attr1 from xml"
app:attr2="attr2 from xml" />
<resources>
<!--定義自定義view的屬性-->
<declare-styleable name="DefineView">
<attr name="attr1" format="string" />
<attr name="attr2" format="string" />
<attr name="attr3" format="string" />
<attr name="attr4" format="string" />
<attr name="attr5" format="string" />
<attr name="attr6" format="string" />
<attr name="attr7" format="string" />
</declare-styleable>
<attr name="defineViewStyle" format="reference" />
</resources>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<!--綁定attrs和style中對應項的關系-->
<item name="defineViewStyle">@style/define_view_style</item>
</style>
<!-- 首先定義我們的defStyleAttr屬性(在本項目中是defineViewStyle屬性)需要用到的style(位于styles.xml文件中) -->
<style name="define_view_style">
<item name="attr3">attr3 from define_view_style</item>
<item name="attr4">attr4 from define_view_style</item>
</style>
<!-- 定義一個在xml布局中需要使用到的style -->
<style name="xml_style">
<item name="attr2">attr2 from xml_style</item>
<item name="attr3">attr3 from xml_style</item>
</style>
</resources>
分析:
attr1只在xml布局文件中設置,所以值為attr1 from xml。
attr2在xml布局文件和xml style中都設置了,取值為布局文件中設置的值,所以為attr2 from xml。
attr3沒有在xml布局文件中設置,但是在xml style和defStyleAttr定義的style中設置了,取xml style中的值,所以值為attr3 from xml_style。
attr4只在defStyleAttr定義的style中設置了,所以值為attr4 from custom_view_style。
下面測試一下defStyleRes這個參數,它是一個style
<style name="define_view_res_style">
<item name="attr4">attr4 from define_view_res_style</item>
<item name="attr5">attr5 from define_view_res_style</item>
</style>
當defStyleAttr這個參數定義為0(即不使用這個參數),或者是在theme中找不到defStyleAttr這個屬性時(即使在theme中的配置是這樣的:<item name="defStyleAttr">@null</item>,也代表找到了defStyleAttr屬性,defStyleRes參數也不會生效),defStyleRes參數才會生效。
修改對應的獲取自定義屬性語句:
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DefineView, 0, R.style.define_view_res_style);
由于defStyleAttr已經失效,所以attr4和attr5都是從define_view_res_style中獲取到的值。
在主題中添加指定的屬性值:
<item name="attr5">attr5 from AppTheme</item>
<item name="attr6">attr6 from AppTheme</item>
attr5在define_view_res_style和theme下都定義了,取define-res-style下的值,所以為attr5 from define_view_res_style。
attr6只在theme下定義了,所以取值為attr6 from AppTheme。
當未設置def-res-style時,主題中的屬性是否生效:
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DefineView, defStyle, 0);
總結:
View中的屬性有多處地方可以設置值,這個優先級是:
1、直接在XML布局文件中設置的值優先級最高,如果這里設置了值,就不會去取其他地方的值了。
2、XML布局文件中有一個叫“style”的屬性,它指向一個style,在這個style中設置的屬性值優先級次之。
3、如果上面兩個地方都沒有設置值,那么就會根據View帶三個參數的構造方法中的第三個參數attribute指向的style設置值,前提是這個attribute的值不為0。
4、如果上面的attribute設置為0了,我們就根據obtainStyledAttributes()方法中的最后一個參數指向的style來設置值。
5、如果仍然沒有設置到值,就會用theme中直接設置的屬性值,而不會去管第3步和第4步中是否設置了值。
必須要注意:要想讓View構造方法的第三個參數生效,必須讓它出現在我們自己的Application或者Activity的android:theme所指向的style中。設置Activity的theme一樣可以。