我們的目的是研究Button
設定android:background
的過程以及實現。
首先要分兩種情況。
第一種情況,Button
所在的Activity
繼承於AppCompatActivity
,這時使用Android Studio
中的Layout Inspector
工具解析螢幕中的控制元件,會發現Button
被替換成了AppCompatButton
。
第二種情況,Button
所在的Activity
繼承於Activity
,這種情況下,解析螢幕,Button
沒有被替換。
Button
那麼首先研究Button
的原始碼,Button
的原始碼很少,只是實現了4個構造方法和一個getAccessibilityClassName
方法。
而能夠產生Button
與其父類(TextView
)的差異是體現在第二個構造方法裡
public Button(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.buttonStyle);
}複製程式碼
這裡,將defStyleAttr
(即預設的Style)設定為com.android.internal.R.attr.buttonStyle
,所以正是因為這個屬性,Button
才與TextView
顯示得不一樣。
TextView
Button
的構造方法最終是呼叫TextView
的四個引數構造方法。
public TextView(
Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes){}複製程式碼
經過對這個構造方法裡檢索,並沒有發現與background
有關的程式碼,那麼,真正的與background
有關的程式碼一定在TextView
的父類View
裡。
View
在View
中的四個引數構造方法中,找到了這樣的程式碼:
int attr = a.getIndex(i);
switch (attr) {
case com.android.internal.R.styleable.View_background:
background = a.getDrawable(attr);
break;
...
}複製程式碼
追蹤的過程按下不表,這裡說一說最終的結果。
如果這個drawable
是ColorDrawable
的話,那麼就會例項化一個ColorDrawable
物件,否則,就會呼叫loadDrawableForCookie
方法,即從XML
中或者resources stream
中載入drawable
。
loadDrawableForCookie
方法中的主要邏輯是這樣的:
if (file.endsWith(".xml")) {
final XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
dr = Drawable.createFromXml(wrapper, rp, theme);
rp.close();
} else {
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
is.close();
}複製程式碼
那麼,接下來我們就瞭解一下Drawable
中的靜態方法createFromResourceStream
和createFromXml
方法。
createFromResourceStream
這裡就是先例項化一個Bitmap
物件,然後判斷這個Bitmap
是否為.9
圖。如果是的話,則根據Bitmap
生成一個NinePatchDrawable
,否則,生成一個BitmapDrawable
。
createFromXml
這裡最終是呼叫了DrawableInflater.inflateFromXml
方法,這個方法返回一個drawable
,但是其實返回的是它的子類。下面這段程式碼就是根據XML
中的TAG
進行判定這個drawable
是屬於什麼型別的。
private Drawable inflateFromTag(@NonNull String name) {
switch (name) {
case "selector":
return new StateListDrawable();
case "animated-selector":
return new AnimatedStateListDrawable();
case "level-list":
return new LevelListDrawable();
case "layer-list":
return new LayerDrawable();
case "transition":
return new TransitionDrawable();
case "ripple":
return new RippleDrawable();
case "color":
return new ColorDrawable();
case "shape":
return new GradientDrawable();
case "vector":
return new VectorDrawable();
case "animated-vector":
return new AnimatedVectorDrawable();
case "scale":
return new ScaleDrawable();
case "clip":
return new ClipDrawable();
case "rotate":
return new RotateDrawable();
case "animated-rotate":
return new AnimatedRotateDrawable();
case "animation-list":
return new AnimationDrawable();
case "inset":
return new InsetDrawable();
case "bitmap":
return new BitmapDrawable();
case "nine-patch":
return new NinePatchDrawable();
default:
return null;
}
}複製程式碼
到這裡,終於見到了我們熟悉的東西了。我們這裡就研究一下shape
所對應的GradientDrawable
。
GradientDrawable
Gradient
這個詞我們就很熟悉了,它就是shape
中的漸變屬性嘛,如下面的程式碼:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!-- 設定邊框 -->
<stroke
android:width="2dp"
android:color="@android:color/black" />
<!-- 設定顏色漸變 -->
<gradient
android:angle="45"
android:endColor="@android:color/holo_orange_dark"
android:startColor="@android:color/holo_blue_light"
android:type="linear" />
<corners android:radius="@dimen/activity_horizontal_margin" />
</shape>複製程式碼
效果圖如下,是一個從橘黃色到藍色的漸變:
GradientDrawable
中提供了一些和xml
屬性對應的方法:
- setCornerRadii – 設定邊角半徑
- setStroke – 設定邊框(寬度+顏色)
- setSize
- setShape – RECTANGLE | OVAL | LINE | RING
- …
在GradientDrawable
中使用了一個GradientState
內部類物件儲存狀態(即在xml
裡設定的那些屬性)。
最後就是在GradientDrawable
的draw
方法中進行繪製。
好了,這就是為Button
設定一個android:background
的大致過程。
想要了解更多的話,請移步本人的學習筆記,如果覺得有幫助的話,請點一個star。