Android中Button設定background過程的研究

居合子發表於2019-02-20

我們的目的是研究Button設定android:background的過程以及實現。

首先要分兩種情況。
第一種情況,Button所在的Activity繼承於AppCompatActivity,這時使用Android Studio中的Layout Inspector工具解析螢幕中的控制元件,會發現Button被替換成了AppCompatButton

Android中Button設定background過程的研究

第二種情況,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;
    ...
}複製程式碼

追蹤的過程按下不表,這裡說一說最終的結果。

如果這個drawableColorDrawable的話,那麼就會例項化一個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中的靜態方法createFromResourceStreamcreateFromXml方法。

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>複製程式碼

效果圖如下,是一個從橘黃色到藍色的漸變:

Android中Button設定background過程的研究

GradientDrawable中提供了一些和xml屬性對應的方法:

  • setCornerRadii - 設定邊角半徑
  • setStroke - 設定邊框(寬度+顏色)
  • setSize
  • setShape - RECTANGLE | OVAL | LINE | RING
  • ...

GradientDrawable中使用了一個GradientState內部類物件儲存狀態(即在xml裡設定的那些屬性)。

最後就是在GradientDrawabledraw方法中進行繪製。

好了,這就是為Button設定一個android:background的大致過程。

想要了解更多的話,請移步本人的學習筆記,如果覺得有幫助的話,請點一個star。

相關文章