Android資源那些事兒(詳)

發燒的冬瓜發表於2018-01-05

Android資源那些事兒

本文將要著重講解的Android資源大致可以分為三類:

1. values資源

  • string 字串資源
  • color 顏色資源
  • dimen 尺寸資源
  • array 陣列資源
  • style 樣式資源
  • theme 主題資源

2. drawable資源

  • 圖片資源
  • StateListDrawable資源
  • LayerDrawable資源
  • ShapeDrawable資源
  • ClipDrawable資源

3.ColorStateList資源

--以下:正文部分-- Android的設計哲學為:設計與表現分離。 這樣有利於程式的解耦。所以我們才可以在XML檔案中定義各種資源型別,並在其他的xml檔案或java程式碼中進行引用。

1.1 String資源:

字串資源所對應的xml檔案位於/res/values/目錄下。 其預設名為strings.xml 對應於R類中的內部類的名稱:R.string 檔案的根元素為resources

定義:
<resources>
    <string name="app_name">Hello World</string>
    <string name="button_name">Hello World</string>
    <string name="text_name">Hi there</string>
</resources>

複製程式碼
引用:

一般我們都是在同一個包下的其他xml檔案中引用字串資源: 比如在TextView中引用之前定義的字串:

  <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/text_name" />
複製程式碼

android:text="@string/text_name"所表達的 正是引用同一包下字串資原始檔中名為text_name的字串資源。 當然,如果是引用不同包下的資源,可則只需在@和string之間加上包名。 事實上,在xml程式碼中使用資源的通用完整語法格式正是: @[<package_name>:]<resource_type>/<resource_name> 其中中括號代表選填,尖括號代表必填。

1.2 Color資源:

與字串資源類似,我們可以事先在xml檔案中定義,並在之後對其進行引用。 顏色資源所對應的xml檔案位於/res/values/目錄下。 其預設名為colors.xml 對應於R類中的內部類的名稱:R.color 檔案的根元素為resources

定義:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorGray">#aaa</color>
    <color name="colorWhite">#ffffff</color>
    <color name="colorBlack">#454647</color>
</resources>
複製程式碼
引用:
<TextView
            ...
            android:textColor="@color/colorWhite"/>
複製程式碼

方法與對string資源的引用大同小異,不再贅述。

1.3 dimen資源:

dimen是dimension的縮寫,表示尺寸。如果我們的佈局中有多個view需要指定相同的尺寸,那麼我們可以事先在dimen資源中對該尺寸進行定義,之後便可以很方便地複用。 dimen資源所對應的xml檔案位於/res/values/目錄下。 其預設名為dimens.xml 對應於R類中的內部類的名稱:R.dimen 檔案的根元素為resources

定義:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="text_view_height">60dp</dimen>
    <dimen name="text_view_width">60dp</dimen>
</resources>
複製程式碼
引用:
<TextView
            android:layout_width="@dimen/text_view_height"
            android:layout_height="@dimen/text_view_width" />
複製程式碼

1.4 array資源:

陣列資源所對應的xml檔案位於/res/values/目錄下。 其預設名為arrays.xml 對應於R類中的內部類的名稱:R.array 檔案的根元素為resources不同的是,arrays.xml檔案中可以定義三種不同型別的子元素:

  1. 普通型別的陣列,比如Drawable陣列,用<array.../>來表示。
  2. 字串型別的陣列,用<string-array.../>來表示。
  3. 整型陣列,用<integer-array.../>來表示。

1.4.1 typedArray定義:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--陣列名-->
    <array name="color_array">
        <item>@color/colorPrimary</item>
        <item>@color/colorAccent</item>
        <item>@color/colorPrimaryDark</item>
        <item>@color/colorBlack</item>
        <item>@color/colorCyan</item>
        <item>@color/colorGreen</item>
    </array>
</resources>
複製程式碼

以上在/res/values/arrays.xml中定義了一個普通型別的陣列。這種型別的陣列也叫做TypedArray,其中的陣列項可以定義Drawable物件等。

在陣列的每一項中都引用了/res/values/colors/中定義的顏色資源。 接下來可以在java程式碼中對該陣列中的資源加以運用。比如我們可以在佈局檔案中定義一個文字框,再定義一個按鈕,點選按鈕實現文字框背景色的輪播:

public class MainActivity extends AppCompatActivity {

    int counter = 0;
    TextView tv;
    Button bn;
    TypedArray typedArray;
//注意:呼叫typedArray的getColor()方法時
//如果不加這個@SuppressWarning標籤就會報錯
    @SuppressWarnings("ResourceType")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) findViewById(R.id.text_view);
        bn = (Button) findViewById(R.id.bn);
//通過getResources()方法獲取到Resources,並將引用賦給res
        Resources res = getResources();
//向obtainTypedArray()方法中,傳入R.array.color_array
//返回一個TypedArray物件,命名typedArray
//裡面儲存的是<array name="color_array">陣列中的顏色資源
        typedArray = res.obtainTypedArray(R.array.color_array);

        bn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                int color = typedArray.getColor(counter%typedArray.length(), 0);
                tv.setBackgroundColor(color);
                counter++;
            }
        });
    }
}

複製程式碼

效果

應用TypedArray中的顏色資源-效果圖
注:

TypedArray在自定義view時也有應用,限於篇幅,本文不深入講解。

1.4.2 string-array定義:

<string-array name="list_items">
        <item>Android</item>
        <item>Ios</item>
        <item>Swift</item>
        <item>Java</item>
        <item>C##</item>
        <item>@string/text_content</item>
    </string-array>
複製程式碼

方法是類似的。只不過根元素寫的是string-array。 其中的字串既可以直接定義值(前5項),也可以引用事先定義好的字串(最後一項)。 應用 比如我們可以在佈局檔案中定義一個ListView,然後在其entries屬性中引用該陣列:

<ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:entries="@array/list_items"/>
複製程式碼

效果

在ListView中引用string-array中的資源

1.4.3 integer-array定義:

與string-array的定義類似,只是將string資源變成了integer型別的資源。

<integer-array name="int_array">
        <item>2</item>
        <item>4</item>
        <item>8</item>
        <item>16</item>
    </integer-array>
複製程式碼

應用 簡單起見,我們同樣也可以在ListView中對該陣列進行引用:

<ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:entries="@array/int_array"/>
複製程式碼

效果

在ListView中引用integer-array中的資源

1.5 style資源:

style資源指的是Android的樣式資源。 同樣在/res/values/目錄下定義 style資原始檔的根元素也是resourcesresources下可以包含多個<style.../>子元素,每個style子元素可以定義一個樣式,style標籤可以指定兩個屬性:

  • name:指定樣式的名稱;
  • parent: 指定該樣式所繼承的父樣式。與java中的繼承類似:當繼承某個父樣式時,該樣式將會獲得父樣式中定義的全部樣式。同樣地,當前樣式也可以覆蓋父樣式中指定的格式。 <style.../>元素內可以包含多個<item.../>子元素,每個都可以定義一個格式項。
<style name="樣式名" parent="@style/事先定義好的樣式名">
 <!--可以包含多個item子項-->
<item>...</itme>
</style>
複製程式碼

舉例:

<style name="style1">
        <item name="android:text">Button</item>
        <item name="android:textAllCaps">false</item>
    </style>
<style name="style2" parent="@style/style1">
        <item name="android:background">#666</item>
        <!--覆蓋父樣式中指定的屬性-->
        <item name="android:textAllCaps">true</item>
</style>
複製程式碼

我們可以為兩個button分別指定定義的style1和style2:

<Button
       android:layout_width="match_parent"
       android:layout_height="wrap_content" 
       style="@style/style1"/>
<Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content" 
        style="@style/style2"/>
複製程式碼

效果:

為button指定style
可以看到,第二個button所引用的style2的parent屬性指定的是style1。 所以style2繼承了style1屬性。但是style2中也重寫了style1的“textAllCaps”屬性,所以第二個button所顯示的text預設為大寫。 當然,style2繼承了style1後,也可以定義自己屬性,如以上的 <item name="android:background">#666</item>。 如此一來,就可以事先定義好一組樣式的集合,然後將該style一次性應用給某個元件。

1.6 theme資源:

theme資源與style資源類似。 同樣在/res/values/目錄下定義,根元素同樣是resource,同樣用<style.../>來定義。 區別在於:主題應該作用於整個應用中的所有Activity或者作用於某個指定的Activity。且主題影響的應該是視窗的標題、邊框等屬性:

<style name="my_theme">
        <item name="android:windowFullscreen">true</item>
    </style>
複製程式碼

使用該主題:

 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setTheme(R.style.my_theme);
        ...
複製程式碼

預設情況下,活動的頂部:

預設情況下活動的頂部

設定

 <item name="android:windowFullscreen">true</item>
複製程式碼

之後,活動的頂部:

設定windowFullScreen主題後活動頂部

如果想要讓應用中的所有視窗都應用剛才定義的my_theme主題,則只需要在清單檔案中的<application.../>元素下新增android:theme="@style/my_theme">即可。

--以上:第一部分--

2.1 圖片資源:

圖片資源可謂是最簡單的drawable資源。只需要把Android認可的圖片資源(.png,.jpg,*.gif)放在/res/drawable-xxx目錄下即可。Android SDK在編譯應用時會自動載入圖片資源,並在R類中生成對該資源的索引。如此,圖片資源就和values資源一樣,可以通過 @[<package_name>:]drawable/檔名的方式在xml程式碼中被訪問了。 如果想要在java程式碼中訪問到實際的圖片Drawable物件,而不是R類中int型別的索引,可以利用Resources類提供的```Drawable getDrawable(int id)方法。該方法可以根據R類中的id獲取到實際的Drawable物件。

2.2 StateListDrawable資源 顧名思義,StateList就是一個state(狀態)的集合。它可以用來組織多個Drawable物件,並讓使用了該StateListDrawable的元件根據自身不同的狀態來自動切換至相應的Drawable。 定義:

  • 在Drawable資料夾下,右鍵new一個新的drawable resource file
  • 根元素為selector,可以理解為狀態選擇器
  • 根元素下可以包含多個<item.../> 並可以為其指定如下屬性:
  1. android:color 或 android:drawable: 指定顏色或者drawable物件
  2. android:state_xxx: 指定一個特定的狀態

舉例: 比如我們想讓一個button在按下時候和未被按下的時候的背景顏色不同,可以這樣寫:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!--true說明按下了-->
    <item android:state_pressed="true" android:drawable="@color/colorCyan"/>
    <item android:state_pressed="false" android:drawable="@color/colorPrimary"/>
</selector>
複製程式碼

在button中引用這個StateListDrawable:

<Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:background="@drawable/bn_state_list"
        android:textColor="@color/colorWhite"
        android:text="按下會變換背景色"
     />
複製程式碼

效果:

StateListDrawable
當然,以上只是StateListDrawable所支援的其中兩個狀態。 完整的狀態列表如下:

屬性值 含義
android:state_active 表示是否處於啟用狀態
android:state_checkable 表示是否處於可勾選狀態
android:state_checked 表示是否處於已勾選狀態
android:state_enabled 表示是否處於可用狀態
android:state_first 表示是否處於開始狀態
android:state_focused 表示是否處於已得到焦點狀態
android:state_last 表示是否處於結束狀態
android:state_middle 表示是否處於中間狀態
android:state_pressed 表示是否處於被按下狀態
android:state_selected 表示是否處於被選中狀態
android:state_window_focused 表示視窗是否處於已得到焦點狀態

2.3 LayerDrawable資源

LayerDrawable顧名思義,就表現得和圖層差不多。可以在根元素layer-list中定義多個drawable物件,並且像幀佈局那樣將各個物件堆疊起來。最後定義的物件處於最上面。 相同的時,根元素下同樣可以包含多個<item.../>子項,並可以在其中定義drawable物件的引用。同時還可以設定top,bottom,right以及left屬性來設定堆疊時,drawable物件向各個方向的偏移量(offset)。 不同的是,<item.../>中的各個子項除了指定偏移量之外,還可以指定id屬性。另外,根據官方說法:

預設情況下,所有可繪製項都會縮放以適應包含檢視的大小。因此,將影象放在圖層列表中的不同位置可能會增大檢視的大小,並且有些影象會相應地縮放。為避免縮放列表中的專案,請在 元素內使用 元素指定可繪製物件,並且對某些不縮放的專案(例如 "center")定義重力。例如,以下 定義縮放以適應其容器檢視的專案: <item android:drawable="@drawable/image" />

比如我們要讓兩個圖示堆疊在一起並且在ImageView中顯示,可以這樣寫: 先定義一個layer_drawable.xml檔案:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <!--設定偏移量-->
    <item
        android:right="20dp"
        android:top="20dp">
        <bitmap android:src="@drawable/ic_launcher"
            android:gravity="center"/>
    </item>
    <item>
        <bitmap android:src="@drawable/ic_launcher_round"
            android:gravity="center"/>
    </item>
</layer-list>
複製程式碼

然後在ImageView中引用:

<ImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:src="@drawable/layer_drawable"/>
複製程式碼

效果:

LayerDrawable的堆疊效果

2.4 ShapeDrawable資源

簡單來說,Android的ShapeDrawable讓我們可以不用做圖就能實現各種簡單的幾何圖形,並能控制圓角、填充顏色、邊框、內邊距、半徑等各種屬性。這樣我們在為某個元件(比如TextView)指定背景時,就方便多了。 定義: ShapeDrawable的根元素是<shape.../>。 其中android:shape="屬性有4中值可以選:line, rectangle, oval, ring。 舉例: 下面分別定義了兩個ShapeDrawable: shape1.xml

" class="hljs "><shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <!--設定內邊距-->
    <padding
        android:bottom="4dp"
        android:left="4dp"
        android:right="4dp"
        android:top="4dp" />
    <!--設定填充顏色-->
    <solid android:color="@color/colorBlack" />
    <!--設定邊框-->
    <stroke
        android:width="2dp"
        android:color="@color/colorAccent" />
    <!--設定圓角矩形-->
    <corners android:radius="8dp" />
</shape>
複製程式碼

shape2.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <!--設定填充漸變-->
    <gradient
        android:angle="45"
        android:endColor="@color/colorGreen"
        android:startColor="@color/colorCyan"
        android:type="linear" />

    <stroke
        android:width="2dp"
        android:color="@color/colorPrimaryDark" />
    <size
        android:width="200dp"
        android:height="100dp" />

</shape>
複製程式碼

然後將他們分別設定為兩個TextView元件的背景,效果如下:

ShapeDrawable用作背景
當然,不止是TextView可以用ShapeDrawable作為背景,支援將drawable物件作為背景的所有元件都可以。其中各項屬性的名稱可謂見名知意,不再贅述。

2.5 ClipDrawable資源:

ClipDrawable表示從其他點陣圖(注意是點陣圖)上clip(擷取)的一個圖片片段。 定義時的根元素是<clip.../>。 總共可以指定三個屬性:

  • android:drawable: 指定擷取的源點陣圖檔案;
  • android:clipOrientation: 指定擷取方向,可以指定水平(horizontal)擷取或者垂直(vertical)擷取;
  • android:gravity: 指定擷取時的對齊方式;可選的值為: top, bottom, left, right, center_vertical, fill_vertical, center_horizontal, fill_horizontal, center, fill, clip_vertical, clip_horizontal。 呼叫ClipDrawable物件的setLevel(int level)方法可以設定擷取區域的大小。level的範圍在[0,10000]。也就是說,當level=0時,一點都不擷取;當level=10000時,擷取整張圖片。

舉例: 比如我們可以藉助ClipDrawable和Timer類打造一個簡單的進度顯示圓: 先定義 my_clip.xml:

<?xml version="1.0" encoding="utf-8"?>
<clip xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/circle"
    android:clipOrientation="vertical"
    android:gravity="bottom">
</clip>
複製程式碼

再在ImageView中引用my_clip:

 <ImageView
       android:id="@+id/show_image"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:src="@drawable/my_clip"/>

    <Button
        android:id="@+id/bn_show_progress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="點按以顯示進度"/>
複製程式碼

最後再java程式碼中進行設定:

public class MainActivity extends AppCompatActivity {

    ImageView showImage;
    Button bn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        showImage = (ImageView) findViewById(R.id.show_image);
        bn = (Button) findViewById(R.id.bn_show_progress);
//獲取圖片所顯示的ClipDrawable物件
        final ClipDrawable circle = (ClipDrawable) showImage.getDrawable();

        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
//如果訊息是本程式傳送的:
                if (msg.what == 0x123) {
                    //修改ClipDrawable的level值
                    circle.setLevel(circle.getLevel() + 200);
                    int currentLevel = circle.getLevel();
                }
            }
        };
//設定button的監聽器
        bn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
//設定bn不可被點選
                bn.setEnabled(false);
                final Timer timer = new Timer();
                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        Message msg = new Message();
                        msg.what = 0x123;
//傳送訊息,通知應用修改ClipDrawable物件的level值
                        handler.sendMessage(msg);
//取消定時器
                        if (circle.getLevel() >= 10000)
                            timer.cancel();
                    }
                }, 0, 200);
            }
        });
    }
}
複製程式碼

效果:

ClipDrawable 演示
--以上:第二部分--

3.1 ColorStateList資源

ColorStateList在好多書上都沒提到,但是卻是十分有用。 前面有提到StateListDrawable,它會根據不同的狀態來引用不同的drawable物件。但是改變的往往是背景色,對於文字顏色就愛莫能助了。 比如,我們想要讓一個button在被設定成enabled="false"之後,背景色變為黑色,這很簡單:

<Button
        android:id="@+id/bn_left"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="bn_left"
        android:textAllCaps="false" />

    <Button
        android:id="@+id/bn_right"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="bn_right"
        android:textAllCaps="false" />
複製程式碼

並且我們定義一個StaleListDrawable命名為bn_state_list,使引用它的按鈕在不可使用時背景色變黑:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_enabled="false" android:drawable="@color/colorBlack"/>
   <item android:state_enabled="true" android:drawable="@color/colorCyan"/>
</selector>
複製程式碼

接下來在java程式碼中設定bn_right的監聽器,讓它被按下時,bn_left的enabled的屬性被設定為"false",也就是不可使用的狀態。 此時,我們會發現,非常尷尬的一幕發生了:

很尷尬

當左邊按鈕的背景色變黑之後,它上面文字的顏色卻沒有隨之改變,使用者體驗肯定會大打折扣。 這個時候ColorStateList就能派上用場了: 不同的是,這次我們不再在drawable資料夾上右擊新建了,而是再建立一個color資料夾,並在裡面新建名為button_text_color.xml的檔案:

image.png
剩下的內容就大同小異了:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@color/colorWhite" android:state_enabled="false"/>
    <item android:color="@color/colorBlack" android:state_enabled="true"/>
</selector>
複製程式碼

可以看到我們的根元素同樣是和StateListDrawable一樣的selector(選擇器),並且我們為按鈕的不同狀態指定了不同的文字顏色。接下來只需要引用這個檔案了:

<Button
...
 android:background="@drawable/bn_state_list"
 android:textColor="@color/button_text_color"
.../>
複製程式碼

可以看到,background和textColor引用的是不同的檔案。而使我們能隨狀態改變按鈕文字顏色的正是android:textColor="@color/button_text_color"效果:

ColorStateList的效果
--以上:正文部分--

看別人寫總是很簡單,自己總結一遍才發現: 在寫程式碼的道路上,總有刁民想害朕。所以說,還是得親“歷”親為啊。 篇幅有限,水平有限。文中如有紕漏,歡迎批評指正。 諸君共勉 : )

相關文章