天天看點

RemoteControl應用介紹

本文介紹了RemoteControl應用,該應用來自《Android權威程式設計指南》一書。

RemoteControl簡介

RemoteControl應用隻有一個activity。應用界面的最頂端區域可以顯示目前頻道。下面緊接着的區域顯示使用者正在輸入的新頻道。點選Delete按鈕可以清除輸入區域的頻道;點選Enter按鈕可變換頻道,即更新目前頻道并清除輸入區域的頻道。

通過本應用可以學到的知識點:

  • 自定義style樣式、繼承父類的style樣式,并将樣式加入到XML代碼中;
  • 自定義include标簽以消除重複代碼;
  • 學習各種類型的drawable(state list drawable、shape drawable、layer list drawable、inset drawable、nine patch drawable),通過這些drawable給應用賦予一種全新的獨特視覺體驗。

本應用的效果圖如下所示:

RemoteControl應用介紹

為相同的view定義style

由上圖可以看出,螢幕的下面是button區域,用于輸入不同的頻道号,這些button都具有相同或相似的外觀,不妨為button設定一個統一的樣式,這樣一來消除了重複的代碼、是代碼看起來更加簡潔,二來友善後期維護。下面将結合代碼對style樣式做一介紹。

樣式可用于适配各種UI控件,避免重複性編寫具有相似控件屬性的代碼。樣式定義在res/values/styles.xml檔案中,該檔案以resources标簽為根标簽,每一種樣式均以style标簽作為元素節點,節點内包含一個或多個item子節點,每個樣式item都是以XML屬性進行命名的,元素内的文字即為屬性值。下面為每個button定義了一組統一的樣式:

<!-- res/styles.xml -->
<!-- 用于定義樣式及主題 -->

<style name="RemoteButton">
        <item name="android:layout_width">dp</item>
        <item name="android:layout_height">match_parent</item>
        <item name="android:textColor">@drawable/button_text_color</item>
        <item name="android:textSize">sp</item>
        <item name="android:layout_margin">dp</item>
        <item name="android:paddingTop">dp</item>
        <item name="android:paddingBottom">dp</item>
        <item name="android:background">@drawable/button_shape_shadowed</item>
    </style>
           

在上面的XML代碼中,style标簽的屬性name=”RemoteButton”就是該樣式的名字,當需要在其他XML代碼中引用該樣式,可使用下面的方式:

在子标簽item中定義了button的共同屬性,其中name為該item的屬性名,而元素内為屬性值。

為了突出Delete按鈕和Enter按鈕,将這兩個按鈕的文字加粗,但其餘屬性不變,可以設定一個新的style樣式”RemoteButtonBold”,該樣式的parent屬性指定為”RemoteButton”,表示樣式”RemoteButtonBold”繼承了”RemoteButton”樣式的所有屬性,同時還可以添加子樣式特有的文字加粗屬性:

<!-- 樣式"RemoteButtonBold"繼承了父樣式"RemoteButton"的全部屬性,并添加自己的特有屬性android:textStyle -->

<style name="RemoteButtonBold" parent="RemoteButton">
        <item name="android:textStyle">bold</item>
    </style>
           

XML drawable

Android把任何可繪制在螢幕上的圖形稱為 drawable。drawable是一種抽象的圖形、一個繼承drawable類的子類,或是一張位圖圖像。常見的drawable有state list drawable、shape drawable、layer list drawable、nine patch drawable。由于前三者通常定義在XML檔案中,故統一将他們稱為XML drawable類别。XML drawable有與像素密度無關的優良特性,故可以将他們定義在預設的res/drawable目錄下。下面将結合代碼說明這幾種XML drawable。

注意到在上個小節中定義的style中,有兩個特殊的item:

<!-- 自定義state list drawable;不同狀态下View的狀态不同 -->
<item name="android:textColor">@drawable/button_text_color</item>

<!-- 自定義layer list drawable;使View産生陰影效果 -->
<item name="android:background">@drawable/button_shape_shadowed</item>
           

以上代碼通過引用drawable的方式(@drawable/)将名為”button_text_color”的XML檔案引用進來,該檔案就是定義在res/drawable目錄下的XML drawable,由于該XML drawable負責指定處于點選與非點選狀态下button的drawable,是以這個自定義的XML drawable屬于state list drawable,”button_text_color”檔案的内容如下:

<!-- button_text_color.xml -->
<!-- state list drawable -->

<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_pressed="false" android:color="#ffffff"></item>
    <item android:state_pressed="true" android:color="#556699"></item>

</selector>
           

state list drawable必須有一個selector作為根标簽,每一個子标簽item表示各個狀态下button的表現形式。

第二個引用的drawable檔案名為”button_shape_shadowed”,同樣定義在res/drawable目錄中,代碼如下所示:

<!-- button_shape_shadowed.xml -->
<!-- layer list drawable -->

<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item>
        <shape android:shape="rectangle" >
            <corners android:radius="5dp" />

            <gradient
                android:angle="90"
                android:centerColor="#303339"
                android:centerY="0.05"
                android:endColor="#000000"
                android:startColor="#00000000" />
        </shape>
    </item>
    <item>
        <inset
            android:drawable="@drawable/button_shape"
            android:insetBottom="5dp" />
    </item>

</layer-list>
           

上面XML代碼的根标簽是layer-list,表示這是一個layer list drawable,該drawable主要用于使UI控件在不同狀态切換時産生層次效果,使控件具有立體感,提高使用者的互動體驗。

每個子标簽item中,都包含了一個drawable,并以從後至前的順序進行排序,第二個drawable是一個inset drawable,其任務就是在已建立的drawable底部做5dp機關的位移,并剛好落在位移形成的陰影上,同時又引入了一個名為”button_shape”的drawable,該drawable是一個state list drawable,具體代碼見本段文字的下面;第一個drawable是一個shape drawable,通過shape标簽定義控件的形狀,若不指定,系統将預設指定為rectangle,shape内的子标簽corners和gradients分别定義了控件的四角圓滑半徑和漸變色。

<!-- button_shape.xml -->
<!-- state list drawable -->

<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/button_shape_normal" android:state_pressed="false"></item>
    <item android:drawable="@drawable/button_shape_pressed" android:state_pressed="true"></item>

</selector>
           

button_shape.xml中的引用drawable:”button_shape_normal”和”button_shape_pressed”是兩個shape drawable,定義如下:

<!-- button_shape_normal.xml -->
<!-- shape drawable -->

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <corners android:radius="3dp" />

    <gradient
        android:angle="90"
        android:endColor="#cccccc"
        android:startColor="#acacac" />

</shape>
           
<!-- button_shape_pressed.xml -->
<!-- shape drawable -->

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <corners android:radius="3dp" />

    <gradient
        android:angle="270"
        android:endColor="#cccccc"
        android:startColor="#acacac" />

</shape>
           

引用style樣式、引用include标簽

将主activity(fragment)的布局以TableLayout為根标簽,則每一行button可以由一個TableRow包含,即每個TableRow包含三個button,由于每一行的TableRow的布局都一樣,故可以單獨定義一個layout資源,最後通過include标簽引入至主布局:

<!-- button_row.xml -->
<!--使用 @style/ 的方式為控件添加樣式-->

<TableRow xmlns:android="http://schemas.android.com/apk/res/android" >

    <Button style="@style/RemoteButton" />

    <Button style="@style/RemoteButton" />

    <Button style="@style/RemoteButton" />

</TableRow>
           
<!-- button_row_bottom.xml -->
<!-- 最後一行按鈕需特殊定制 -->

<TableRow xmlns:android="http://schemas.android.com/apk/res/android" >

    <Button style="@style/RemoteButtonBold" />

    <Button style="@style/RemoteButton" />

    <Button style="@style/RemoteButtonBold" />

</TableRow>
           

以下是主布局XML:

<!-- fragment_remote_control.xml -->

<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_remote_control_tableLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/remote_background"
    android:stretchColumns="*" >

    <TextView
        android:id="@+id/fragment_remote_control_selectedTextView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="2"
        android:background="@drawable/window_patch"
        android:gravity="center"
        android:text="0"
        android:textColor="#ffffff"
        android:textSize="50sp" />

    <TextView
        android:id="@+id/fragment_remote_control_workingTextView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_margin="16dp"
        android:layout_weight="1"
        android:background="@drawable/bar_patch"
        android:gravity="center"
        android:text="0"
        android:textColor="#cccccc"
        android:textSize="20sp"
        android:textStyle="italic" />

    <include layout="@layout/button_row" />

    <include layout="@layout/button_row" />

    <include layout="@layout/button_row" />

    <include layout="@layout/button_row_bottom" />

</TableLayout>
           

以上XML有如下幾點說明:

  • 屬性android:stretchColumns= “*” 表示TableRow中的每一個控件大小均分;
  • 最上面的兩個TextView分别使用了nine-patch圖檔作為背景,有關nine-patch圖檔也屬于drawable的一種(nine patch drawable),有關nine-patch圖檔的定義及制作,請參見我的博文《Nine-Patch格式圖檔淺析》。
  • 兩個TextView分别設定了android:layout_weight屬性,比例為2:1,表示在豎直方向上,兩個TextView按照2:1的高度配置設定空間。注意:由于button屬性中定義了android:layout _height=”0dp”,同時未指定android:layout _weight,是以在高度的配置設定上,應先為TableRow所需空間占用豎直方向的高度,剩餘的高度空間再按2:1 的比例配置設定給兩個TextView。

實作邏輯功能

代碼如下:

public class ReomoteControlFragment extends android.support.v4.app.Fragment {

    private TextView mSelectedTextView;
    private TextView mWorkingTextView;

    @Override
    @Nullable
    public View onCreateView(LayoutInflater inflater,
            @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        final View view = inflater.inflate(R.layout.fragment_remote_control,
                container, false);
        mSelectedTextView = (TextView) view
                .findViewById(R.id.fragment_remote_control_selectedTextView);
        mWorkingTextView = (TextView) view
                .findViewById(R.id.fragment_remote_control_workingTextView);

        View.OnClickListener numberButtonListener = new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub

                TextView textView = (TextView) v;
                String working = mWorkingTextView.getText().toString();
                String text = textView.getText().toString();
                if (working.equals("0")) {
                    mWorkingTextView.setText(text);
                } else {
                    mWorkingTextView.setText(working + text);
                }

            }
        };
        // 為每個按鈕動态設定内容、批量綁定監聽器
        TableLayout tableLayout = (TableLayout) view
                .findViewById(R.id.fragment_remote_control_tableLayout);
        int number = ;
        // getChildCount傳回tableLayout中直接子控件的個數
        for (int _i = ; _i < tableLayout.getChildCount() - ; ++_i) {
            TableRow row = (TableRow) tableLayout.getChildAt(_i);
            for (int _j = ; _j < row.getChildCount(); _j++) {
                Button button = (Button) row.getChildAt(_j);
                button.setText("" + number);
                button.setOnClickListener(numberButtonListener);
                ++number;

            }

        }

        // 最後一排按鈕的内容及監聽器特殊處理
        TableRow bottomRow = (TableRow) tableLayout.getChildAt(tableLayout
                .getChildCount() - );
        // 最後一排第一個按鈕為"delete"
        Button deleteButton = (Button) bottomRow.getChildAt();
        deleteButton.setText("Delete");
        deleteButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                mWorkingTextView.setText("0");

            }
        });
        // 最後一排第二個按鈕為"0"
        Button zeroButton = (Button) bottomRow.getChildAt();
        zeroButton.setText("0");
        zeroButton.setOnClickListener(numberButtonListener);

        // 最後一排第三個按鈕為"Enter"
        Button enterButton = (Button) bottomRow.getChildAt();
        enterButton.setText("Enter");

        enterButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                CharSequence working = mWorkingTextView.getText();
                if (working.length() > ) {
                    mSelectedTextView.setText(working);

                }
                mWorkingTextView.setText("0");
            }
        });

        return view;
    }

}
           

注:邏輯代碼添加在了Fragment的onCreateView方法中,這與将代碼邏輯寫在Activity的onCreate方法中是一樣的。

最後,為了強制activity豎屏顯示,應在AndroidManifest中為activity指定如下屬性: