官术网_书友最值得收藏!

第3章 控件和布局

3.1 控件

在大多數GUI系統中,控件是其提供的主要功能,使用各種控件也是構建界面的最主要的工作之一。了解系統提供的控件及其擴展性是界面構建的核心。

3.1.1 Android中的控件

1.控件類的繼承結構

android.view.View類(視圖類)呈現了最基本的UI構造塊。一個視圖占據屏幕上的一個方形區域,并且負責繪制和事件處理。

Android中控件類的擴展結構如圖3-1所示。

圖3-1 Android中控件類的擴展結構

View有眾多的擴展者,它們大部分是在android.widget包中,這些繼承者實際上就是Android系統中的“控件”。View實際上就是各個控件的基類,創建交互式的圖形用戶界面的基礎。

View的直接繼承者包括文本視圖(TextView)、圖像視圖(ImageView)、進度條(ProgressBar)等。它們各自又有眾多的繼承者。每個控件除了繼承父類功能之外,一般還具有自己的公有方法、保護方法、XML屬性等。

在Android中使用各種控件的一般情況是在布局文件中可以實現UI的外觀,然后在Java文件中實現對各種控件的控制動作??丶惖拿Q也是它們在布局文件XML中使用的標簽名稱。

2.控件通用行為和屬性

View是Android中所有控件類的基類,因此View中一些內容是所有控件類都具有的通用行為和屬性。

提示:由于Java語言不支持多重繼承,因此Android控件不可能以基本功能的“排列組合”的方式實現。在這種情況下,為了實現功能的復用,基類的功能往往做得較強,作為控件的祖先類,View所實現的功能也是最多的。

控件類經常在布局文件中使用,因此其可以使用XML屬性(XMLAttributes),和Java代碼經常具有對應關系。

View作為各種控件的基類,其XML屬性所有控件通用,幾個重要的XML屬性如表3-1所示。

表3-1 View中幾個重要XML屬性及其對應的方法

其中,android:id表示控件的標識,通常需要在布局文件中指定這個屬性。View中與控件標識相關的幾個方法如下所示:

        public int  getId()                      // 獲得控件的id(int類型)
        public void setId(int id)                // 設置控件的id(int類型)
        public Object  getTag()                  // 獲得控件的tag(Object類型)
        public void  setTag(Object tag)          // 設置控件的tag(Object類型)

對于一個控件,也就是View的繼承者,整數類型id是其主要的標識。其中,getId()可以獲得控件的id,而setId()可以將一個整數設置為控件的id,但是這個方法并不常用。View的id通常可以在布局文件中獲得。

Object類型的標識tag是控件的一個擴展標識,由于使用了Object類型,它可以接受大部分的Java類型。

在一個View中根據id或者tag查找其孩子的方法如下所示:

        public final View  findViewById(int id)
        public final View  findViewWithTag(Object tag)

findViewById()和findViewWithTag()的目的是返回這個View樹中id和tag為某個數值的View的句柄。View樹的含義是View及其所有的孩子。

值得注意的是,id不是控件的唯一標識,例如布局文件中id是可以重復的,在這種重復的情況下,findViewById()的結果不能確保找到唯一的控件。

提示:作為控件的標識的id和tag可以配合使用:當id有重復的時候,可以通過給控件設置不同的tag,對其進行區分。

可見性的問題,android:visibility在布局文件中有三個數值:visibl(e可見,默認),invisible(不可見),gone(去除)。在Java代碼中,setVisibility()能使用的枚舉值與其對應,它們是:View.VISIBLE(0x0),View.INVISIBLE(0x4),View.GONE(0x8)。

參考示例程序:Visibility(ApiDemo=>Views)

源代碼:com/example/android/apis/view/visibility_1.java

布局文件:visibility_1.xml

Visibility程序的運行效果如圖3-2所示。

圖3-2 Visibility程序(左:可見;中:不可見;右:去除)

對于文字為View B的文本框,分別使用了visible、invisible和gone設置。invisible和gone的區別在于invisible只是不可見,但是依然占位,gone表示將控件去除,顯示的效果就像沒有這個控件存在。

和Vi e w形態相關的幾個方法如下所示:

        public void invalidate ()                    // 使無效(重新繪制)
        public void requestLayout ()                 // 申請重新布局
        public final boolean requestFocus ()         // 申請聚焦

這幾個方法都和View的顯示形態有關:invalidate()方法的功能是使得無效,用于重新繪制當前的View;requestLayout()用于更新View樹,也就是由當前View的大小位置變化更新與其相關的View;requestFocus()用于申請當前的聚焦。

查找聚焦的Vi e w的方法如下所示:

        public View findFocus ()           // 找到聚焦的View

在布局文件中,如果在一個控件的標簽中使用<requestFocus />標簽,表示指定它在默認情況下被聚焦。當使用上、下、左、右按鍵的時候,各個控件有著默認的聚焦順序。其他聚焦的問題可以在布局文件中進一步處理,一個處理的方法如下所示:

        <LinearLayout android:orientation="vertical">
            <Button android:id="@+id/top"    android:nextFocusUp="@+id/bottom" />
            <Button android:id="@+id/bottom" android:nextFocusDown="@+id/top" />
        </LinearLayout>

這里android:nextFocusUp和android:nextFocusDown分別是上下按鍵的時候,下一個聚焦的控件的id。

3.1.2 文本類控件

TextView(文本視圖)及其繼承者構成了Android中的“文本類控件”,這是最常用的一類控件。

1.文本視圖TextView

TextView是一個View的直接繼承者,這個類本身作為一個文本框來使用,TextView對View的主要擴展就是在其中實現了顯示文本的功能。TextView中的幾個主要的屬性如表3-2所示。

表3-2 TextView中幾個重要xml屬性及其對應的方法

這些XML屬性均可以在TextView及其繼承者中使用。TextView有以下幾個直接繼承者:EditText(可編輯文本),Button(按鈕),Chronometer(計數器),CheckedTextView(可選擇文本區域)和DigitalClock(數字時鐘)。

2.可編輯文本EditText

可編輯文本(EditText)是TextView類的一個繼承者,其中最主要的一個特性就是其中文本可以編輯。

EditText在本質上還是一個TextView,主要是其中的屬性設置有所不同。EditText繼承自View的屬性android:focusable,android:focusableInTouchMode和android:clickable默認為true,繼承自TextView的android:editable屬性默認為true。在外觀上,EditText具有特殊的背景,默認的文本屬性也有所不同。除此之外,EditText中的文本有一個選擇的問題,主要由selectAll()和setSelection()幾個方法完成。

EditText包含有以下幾個繼承者。

ExtractEditText:可抽取的可編輯文本;

AutoCompleteTextView:自動補全文本視圖;

MultiAutoCompleteTextView:多行自動補全文本視圖。

3.按鈕Button

按鈕(Button)作為TextView類的繼承者,主要的區別表現在外觀和使用的方式上。Button通常要設置處理單擊動作的處理器(View.OnClickListener)。

Button對TextView的“擴展”其實很少,這個類實際上只是有一個不同的背景(包括聚焦時、單擊時)。

Button類具有一個名為CompoundButton的繼承者。CompoundButtond是具有選擇(checked)和未選擇(unchecked)兩種狀態的按鈕,當單擊的時候自動實現兩種狀態的切換,CompoundButton中具有如下兩個方法:

        public boolean isChecked ()                       // 是否選擇
        public void setChecked (boolean checked)         // 設置選擇狀態

CompoundButton是一個抽象類,不能在程序中直接使用。CompoundButton又有RadioButton、CheckBox和ToggleButton這三個繼承者。

CheckBox是具有一個選項框的多選按鈕,主要特點是形態的差異。

RadioButton是圓形的單選按鈕,除了形態上特別之外,它經常以RadioGroup(單選按鈕組)作為容器,以實現同一組按鈕同時只能選定一個的功能。

ToggleButton為開關按鈕,包含開和關兩個狀態,可以顯示不同的文本textOn(開)和textOff(關),ToggleButton的特定的XML屬性包括了以下的內容。

android:disabledAlpha:禁止的時候的Alpha值,使用浮點數;

android:textOn和android:textOff:定義開狀態和關狀態下顯示的文本。

參考示例程序:Buttons1(ApiDemo=>Views)

源代碼:com/example/android/apis/view/Buttons1.java

布局文件:buttons_1.xml

按鈕程序的運行效果如圖3-3所示。

界面比較簡單,前兩個按鈕是Button類,表示普通的按鈕;第三個按鈕是ToggleButton類,表示可以進行開關操作的按鈕。

本例的布局文件buttons_1.xml的內容如下所示:

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <Button android:id="@+id/button_normal"
              android:text="@string/buttons_1_normal"

圖3-3 按鈕程序(左:ToggleButton關;右:ToggleButton開)

              android:layout_width="wrap_content"
              android:layout_height="wrap_content" />
            <Button android:id="@+id/button_small"
              style="?android:attr/buttonStyleSmall"
              android:text="@string/buttons_1_small"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content" />
            <ToggleButton android:id="@+id/button_toggle"
              android:text="@string/buttons_1_toggle"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content" />
        </LinearLayout>

這里主要引用了三個控件:第1個是一個普通的Button,沒有設置特殊屬性;第2個也是一個Button,但是其style被設置為android:attr/buttonStyleSmall,主要就是Button和其中文字的邊緣比較窄,因此Button形狀較?。坏?個按鈕是ToggleButton,顯示的OFF和ON是其默認的文本,選擇ON狀態的時候,將顯示亮色的標志桿。

提示:Android中的很多控件的大小并不能隨意控制,例如ToggleButton,如果任意設置大小,將出現標志桿和控件大小不匹配的情況。因此,經常使用R.attr類中的buttonStyle和buttonStyleSmall等屬性指定其Style。

4.計時器Chronometer

Chronometer也是TextView的一個直接繼承者。Chronometer呈現的外觀就是一個簡單的文本區域,顯示為可以自動計數的格式。

Chronometer具有一些特殊的方法用于設置計數的基數、格式,如下所示:

        public void  setBase(long base)                  // 設置基數
        public long  getBase()                            // 獲得基數
        public void  setFormat(String format)            // 設置格式
        public String getFormat()                         // 獲得格式

Chronometer具有一個名稱為android:format的XML屬性,它和代碼中的setFormat()和getFormat()指定的格式代表的含義相同。其中可以為使用“%s”表示格式化的字符串,在顯示的時候被替換成“MM:SS”和“H:MM:SS”的形式。

Chronometer.OnChronometerTickListener是一個接口,設置到Chronometer當中后,其中的onChronometerTick()方法在計數器的每一個節拍時刻被調用。

控制計數器開始和停止的方法如下所示:

        public void start()                               // 開始計數器
        public void stop()                                // 停止計數器

參考示例程序:Chronometer(ApiDemo=>Views)

源代碼:com/example/android/apis/view/ChronometerDemo.java

布局文件:chronometer.xml

計數器程序的運行結果如圖3-4所示。

圖3-4 計數器程序的運行結果(左:初始程序;中:設置成某個字符串;右:清除格式)

計數器程序的布局文件chronometer.xml的核心片段如下所示:

        <Chronometer android:id="@+id/chronometer"
            android:format="@string/chronometer_initial_format"
            android:layout_width="wrap_content" android:layout_height="wrap_content"
            android:layout_weight="0" android:paddingBottom="30dip"
            android:paddingTop="30dip" />

其中,android:format使用的字符串如下所示:

        <string name="chronometer_initial_format">
              Initial format: <xliff:g id="initial-format">%s</xliff:g></string>

因此,此處格式化字符串當中的“%s”在初始化顯示的時候被替換成了計數器數據的格式。通過Set format string和Clear format string兩個按鈕,可以將顯示的格式設置為其他,或者清除成無格式。

3.1.3 圖像類控件

在Android提供了功能強大的ImageView控件,表示圖像類控件,用于在界面上顯示圖片。ImageView具有一些繼承者。

1.圖像區域ImageView

ImageView又被稱為圖像視圖,是Android中可以直接顯示圖形的控件。幾個重要的XML屬性及其對應的方法如表3-3所示。

表3-3 ImageView中幾個重要XML屬性及其對應的方法

圖像源是ImageView控件的核心,實際上有多種不同的設置圖像源的方法:

        public void setImageResource (int resId)    // 設置圖像源的資源ID
        public void setImageURI(Uri uri)             // 設置圖像源的URI
        public void setImageBitmap(Bitmap bm)        // 設置一個Bitmap位圖為圖像源

使用ID的方式表示設置包中預置的圖像資源,使用URI可以設置文件系統中存儲在各種地方的圖像等,使用Bitmap的方式可以設置一個已經表示為Bitmap類型的圖像。

android:scaleType表示縮放的類型,由ImageView.ScaleType枚舉類型來表示,包括居中,保持縱橫比例的居中縮放,適應居中等類型。

ImageView還支持縮放、剪裁等功能:最大的寬(android:maxWidth)和最大的高(android:maxHeight)可以實現讓圖像縮小的功能。android:adjustViewBounds表示是否調整ImageView的邊界。

參考示例程序:ImageView(ApiDemo=>Views)

源代碼:com/example/android/apis/view/ImageView1.java

布局文件:image_view_1.xml

ImageView程序的運行效果如圖3-5所示。

程序中的圖像都是通過ImageView類來實現顯示的,ImageView是View的直接擴展者。布局文件image_view_1.xml的一個片段如下所示:

        <!--1. 沒有縮放的圖像:自動調整邊界 -->
        <ImageView android:src="@drawable/sample_1"
            android:adjustViewBounds="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <!--2. 自動調整邊界限制最大的寬高 -->
        <ImageView  android:src="@drawable/sample_1"
            android:adjustViewBounds="true"

圖3-5 ImageView程序的運行效果

            android:maxWidth="50dip"  android:maxHeight="50dip"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

以上布局文件的片段,表示的是前兩個ImageView的顯示情況,這里需要注意的是ImageView涉及兩個尺寸,一個是ImageView控件的尺寸,一個是其中內容的圖片的尺寸。第一個示例使用的是圖片的原始大小,第二個示例使用ImageView的android:maxWidth和android:maxHeight屬性限制了其中圖片的大小。

最后兩個圖像的布局文件如下所示:

        <!--3. 限制最大為70x70, 留有10的邊界,圖像為“笑臉”-->
        <ImageView android:src="@drawable/stat_happy"
            android:background="#FFFFFFFF"
            android:adjustViewBounds="true"
            android:maxWidth="70dip" android:maxHeight="70dip"
            android:padding="10dip"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <!--4. 控件的尺寸為70x70, 留有10的邊界,圖像為“笑臉”-->
        <ImageView android:src="@drawable/stat_happy"
            android:background="#FFFFFFFF"
            android:scaleType="centerInside"
            android:padding="10dip"
            android:layout_width="70dip"
            android:layout_height="70dip" />

倒數第二個ImageView限制了其中的內容圖像的大小最大為70x70,由于內容圖像的大小小于70x70,此設置實際無效;最后將控件的大小設置為一個70x70,并且留有10dip的邊界,將android:scaleType設置為“centerInside”表示居中,由于沒有設置android:adjustViewBounds為true,因此控件不會調整到其中內容的大小。

2.圖像按鈕ImageButton

圖像按鈕ImageButton是一個帶有圖片的按鈕,從邏輯上可以實現普通按鈕功能。圖像按鈕實際上結合了圖像和按鈕的雙重特性。圖像按鈕ImageButton繼承了ImageView,它結合了圖像和按鈕的功能。ImageButton除了可以當做按鈕來使用,其他方面和ImageView基本一致。

ImageButton與圖像相關的XML屬性、方法與ImageView是一樣的,它與ImageView的區別也僅在于外觀和使用方式上,ImageButton具有按鈕樣式的背景。

事實上,ImageButton除了在外觀上表現成一個按鈕的狀態,其他方面和ImageView基本一樣。由于是按鈕的功能,在Java源程序中,ImageButton通常被設定OnClickListener來獲得點擊時候的響應函數。

提示:圖像按鈕ImageButton只繼承了ImageView,和普通按鈕Button并沒有繼承關系,不能作為Button類使用。

ZoomButton是ImageButton的一個繼承者,它是一個帶有動態縮放功能的圖像按鈕。這個類與基類的主要區別也是外觀上,它有方法用于設置縮放的速度,如下所示:

        public void setZoomSpeed (long speed)

參考示例程序:ImageButton(ApiDemo=>Views)

源代碼:com/example/android/apis/view/ImageButton.java

布局文件:image_button_1.xml

ImageButton程序的運行效果如圖3-6所示。

圖3-6 ImageButton程序的運行效果

ImageButton程序的布局文件image_button_1.xml的主要內容如下所示:

        <ImageButton
            android:layout_width="100dip"
            android:layout_height="50dip"
            android:src="@android:drawable/sym_action_call" />
        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@android:drawable/sym_action_chat" />
        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@android:drawable/sym_action_email" />

這里使用的各個XML屬性實際上都是ImageView的屬性。圖像按鈕中經常使用類似上述透明的圖片與背景中的按鈕形態做出配合。

3.1.4 進度條類控件

ProgressBar是View的直接繼承者,表示Android中的進度條。除了可以作為控件使用之外,進度條還可以在對話框和標題欄中使用。

1.進度條ProgressBar

ProgressBar類表示進度條控件,它有兩種基本形態,圓型和水平型。圓型表示沒有確切進度的過程,水平型表示一個百分比的過程。

ProgressBar預定義了以下幾種樣式。

android:progressBarStyle:默認樣式(圓型)

android:progressBarStyleHorizontal:水平樣式(水平)

android:progressBarStyleLarge:大的樣式(圓型)

android:progressBarStyleSmall:小的樣式(圓型)

ProgressBar的主要方法如下所示:

        public int  synchronized getMax()                     // 獲得進度條的最大值
        public synchronized int getProgress ()                // 獲得進度值
        public synchronized int getSecondaryProgress ()      // 獲得第二個進度條的進度
        // 設置主進度條的進度和第二個進度條的進度
        public void  synchronized setProgress(int progress)
        public void  synchronized setSecondaryProgress(int secondaryProgress) // “增量”方
    式設置主進度條的進度和第二個進度條的進度
        public final synchronized void incrementProgressBy (int diff)
        public final synchronized void incrementSecondaryProgressBy (int diff)

以上的幾個方法,只對水平ProgressBar有效,顯示效果實際是當前值和最大值的一個比例。ProgressBar比較特殊的地方是這個類還支持第二個進度條。

參考示例程序:1.Incremetal和2.Smoth(ApiDemo=>Views=>ProgressBar)

源代碼:

              com/example/android/apis/view/ProgressBar1.java
              com/example/android/apis/view/ProgressBar2.java

布局文件:progressbar_1.xml和progressbar_2.xml

1.Incremetal和2.Smoth的運行效果如圖3-7所示。

ProgressBar1的布局文件progressbar_1.xml的主要內容如下所示:

        <ProgressBar android:id="@+id/progress_horizontal"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="200dip" android:layout_height="wrap_content"
            android:max="100"
            android:progress="50" android:secondaryProgress="75" />

圖3-7 ProgressBar程序的運行效果(左:ProgressBar1;右:ProgressBar2)

進度條的形態使用XML屬性表示,progressBarStyleHorizontal表示使用水平模式的進度條。android:max表示最大數值,android:progress和android:secondaryProgress分別表示主進度條和第二個進度條的當前數值。

進度條的進度可以根據按鈕控制,其中一個按鈕的處理如下所示:

        Button button = (Button) findViewById(R.id.increase);
        button.setOnClickListener(new Button.OnClickListener() {
            public void onClick(View v) {
              progressHorizontal.incrementProgressBy(1); // 設置進度條的進度
              setProgress(100 * progressHorizontal.getProgress());
            }
        });

這里調用的progressHorizontal是ProgressBar類的一個實例,此處調用incrementProgressBy()根據相對值調整進度,與之類似,進度條中的第二個進度使用incrementSecondaryProgressBy()方法進行調整。

布局文件progressbar_2.xml的主要內容如下所示:

        <ProgressBar android:id="@+android:id/progress_large"
            style="?android:attr/progressBarStyleLarge"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <ProgressBar android:id="@+android:id/progress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <ProgressBar android:id="@+android:id/progress_small"
            style="?android:attr/progressBarStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <ProgressBar android:id="@+android:id/progress_small_title"
            style="?android:attr/progressBarStyleSmallTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

此處通過style設置了進度條的不同樣式,第一個的樣式為大,第二個為默認樣式,第三個、第四個的樣式為小。這些內容使用的都是圓型進度條。

2.AbsSeekBar及其繼承者

AbsSeekBar是ProgressBar的一個主要繼承者,這是一個抽象類,這也是其名稱中Abs的含義。

AbsSeekBar的主要擴展是可以設置其中的內容,幾個相關的方法如下所示:

        public void setThumb(Drawable thumb)                  // 設置其中的內容
        public void setThumbOffset(int thumbOffset)           // 設置內容的偏移量
        public int getThumbOffset()                           // 內容的偏移量

由于AbsSeekBar是一個抽象類,因此這個類并不能直接使用。一般使用的是其兩個繼承者:RatingBar和SeekBar。

SeekBar具有一個XML屬性android:thumb,對應于setThumb()的設置,在SeekBar中thumb的含義是其中的滑桿(可拖曳的小圖標)。

SeekBar具有一個設置監聽者的方法:

        public void setOnSeekBarChangeListener (SeekBar.OnSeekBarChangeListener l)

SeekBar.OnSeekBarChangeListener是一個用于監聽的接口,包含如下幾個方法:

        Public abstract void onProgressChanged(SeekBar seekBar,       // 獲得進度
                                                  int progress, boolean fromUser)
        Public abstract void onStartTrackingTouch(SeekBar seekBar)    // 開始跟蹤觸摸
        Public abstract void onStopTrackingTouch(SeekBar seekBar)     // 停止跟蹤觸摸

基類ProgressBar通常用來向用戶顯示一個進度,而SeekBar通??梢宰層脩艚换ナ娇刂七M度。

RatingBar可以直接用星星的方式來表示進度,主要有以下幾個XML屬性:android:isIndicator(是否可以被用戶控制),android:numStars(星星的數目),android:rating(初始化比例),android:stepSize(每次改變的大?。?。

RatingBar具有一個設置監聽者的方法:

        public void setOnRatingBarChangeListener (RatingBar.OnRatingBarChangeListener l)

RatingBar.OnRatingBarChangeListener是一個用于監聽的接口,包含如下方法:

        public abstract void onRatingChanged (RatingBar ratingBar,
                                                  float rating, boolean fromUser)

參考示例程序:Seek Bar和Ratting Bar(ApiDemo=>Views=>)

源代碼:

              com/example/android/apis/view/SeekBar1.java
              com/example/android/apis/view/RatingBar1.java

布局文件:seekbar_1.xml和ratingbar_1.xml

Seek Bar和Ratting Bar的運行效果如圖3-8所示。

Seek Bar的布局文件seekbar_1.xml的內容如下所示:

        <SeekBar android:id="@+id/seek"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:max="100" android:progress="50"
            android:secondaryProgress="75" />

圖3-8 Seek Bar和Ratting Bar的運行效果

這里設置的屬性實際上都是基類ProgressBar的屬性。在實際的應用中,SeekBar相當于一個可以由用戶自己進行控制的水平型的ProgressBar,并且可以通過設置監聽者得到用戶控制的情況。

從SeekBar的運行效果中可以看到,SeekBar的外觀和水平進度條類似,但是進度條中間具有一個滑桿的圖標。這個滑桿可以被用戶拖曳,拖曳后SeekBar將自動產生相應的效果。在程序中,可以設置圖標的外觀,也可以通過SeekBar.OnSeekBarChangeListener接口在程序中得到拖曳的進度。

Ratting Bar的布局文件ratingbar_1.xml的主要內容如下所示:

        <RatingBar android:id="@+id/ratingbar1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:numStars="3"    android:rating="2.5" />
        <RatingBar android:id="@+id/ratingbar2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:numStars="5"    android:rating="2.25" />

這里一共定義了4個RatingBar標簽,前兩個分別定義了星星的數目為3和5,后兩個則將style設置為不同的樣式,因此顯示了不同的效果。

在實際的應用中,RatingBar一般用于打分操作。

3.在對話框和標題欄中使用進度條

在對話框和標題欄中使用進度條,不需要直接使用ProgressBar類。對話框和標題欄中的進度條也有水平型(Horizontal)和圓型(Spinner)兩種模式,其中圓型模式又稱為不確定(Indeterminate)模式。

android.app包中的ProgressDialog是AlartDialog的繼承者,可以構建一個直接帶有進度條的對話框。使用ProgressDialog類,不需要AlartDialog.Builder,而是通過直接建立這個類來完成的。

ProgressDialog類具有如下方法,用于設置其樣式:

        public static final int STYLE_SPINNER                     // 0x0,表示圓型
        public static final int STYLE_HORIZONTAL                  // 0x1,表示水平型
        public void setProgressStyle (int style)                  // 設置進度條樣式
        public void setIndeterminate (boolean indeterminate)      //設置進度條是否為“不確定”
        public boolean isIndeterminate ()

這里的“Indeterminate”含義為不確定,實際上也就是圓型的進度條,也是STYLE_SPINNER樣式的含義;而STYLE_HORIZONTAL樣式則表示水平進度條。除此之外,ProgressDialog類中也具有setProgress (),incrementProgressBy()等方法,含義和ProgressBar中的相同。

Activity中的幾個方法用于設置其標題欄中的進度條:

        public final void setProgressBarIndeterminate(boolean indeterminate)
        public final void setProgressBarIndeterminateVisibility(boolean visible)
        public final void setProgressBarVisibility(boolean visible)   // 設置可見性
        public final void setProgress(int progress)                    // 設置進度
        public final void setSecondaryProgress(int secondaryProgress) // 設置第二個進度

如果希望Activity標題欄中具有進度條,首先需要使用requestWindowFeature()函數申請相關特性,如下所示。

        requestWindowFeature(Window.FEATURE_PROGRESS);                 // 水平型
        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);  // 圓型

FEATURE_PROGRESS和FEATURE_INDETERMINATE_PROGRESS是Windows類的屬性,分別代表為水平型和圓型。

前面的ProgressBar=>1.Incremetal展示的是一個標題欄中的水平進度條,另外幾個對話框和標題欄中的進度條如圖3-9所示。

圖3-9 對話框和標題欄中的進度條

圖中的三個程序分別為ApiDemo中的App=>Dialog中的Progress Dialog示例和ProgressBar=>中的3.Dialog和4.In Title Bar。分別表示:對話框中的水平型進度條,對話框中的圓型進度條,標題欄中的圓型進度條。

Progress Dialog的核心內容如下所示:

        private static final int DIALOG_PROGRESS = 4;
        private static final int MAX_PROGRESS = 100;
        private ProgressDialog mProgressDialog;
        private int mProgress;
        @Override protected Dialog onCreateDialog(int id) {
        // ...... 省略其他內容
            case DIALOG_PROGRESS:
              mProgressDialog = new ProgressDialog(AlertDialogSamples.this);
              mProgressDialog.setIcon(R.drawable.alert_dialog_icon);
              mProgressDialog.setTitle(R.string.select_dialog);
              mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
              mProgressDialog.setMax(MAX_PROGRESS);  // 設置最大值
              mProgressDialog.setButton(DialogInterface.BUTTON_POSITIVE,
                      getText(R.string.alert_dialog_hide),
                      new DialogInterface.OnClickListener() {
                  public void onClick(DialogInterface dialog, int whichButton) {
                  }
              });
              mProgressDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
                      getText(R.string.alert_dialog_cancel),
                      new DialogInterface.OnClickListener() {
                  public void onClick(DialogInterface dialog, int whichButton) {
                  }
              });
              return mProgressDialog;  // 將ProgressDialog返回為Dialog類型
        // ...... 省略其他內容
            }

除了按鈕、標題等默認內容之外,主要設置的內容是進度條的樣式(使用setProgressStyle())和最大數值(使用setMax())。

在ProgressBar=>1.Incremetal示例程序中與標題欄進度條相關的內容如下所示:

        protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              requestWindowFeature(Window.FEATURE_PROGRESS);
              setContentView(R.layout.progressbar_1);
              setProgressBarVisibility(true);           // 設置進度條出現
              final ProgressBar progressHorizontal
                              = (ProgressBar) findViewById(R.id.progress_horizontal);
              setProgress(progressHorizontal.getProgress() * 100);
              setSecondaryProgress(progressHorizontal.getSecondaryProgress() * 100);
        }

這里使用requestWindowFeature(Window.FEATURE_PROGRESS)來實現了將進度條設置到標題欄當中。

3.1.5 繼承View實現自定義控件

除了Android系統的預定義控件之外,在程序中還可以實現自定義控件。自定義控件一般可以基于所有控件類的基類Vi e w來實現,它也可以具有公開或者保護的屬性、方法,還可以具有自己的XML屬性。在布局文件中,自定義控件的使用方法和預定義的控件也是類似的。

自定義控件可以在本應用程序包中實現功能的復用,但是不能跨應用程序包使用。也就是說A包(Apk)中的自定義控件,不能在B包中使用。

實現自定義的View,繼承View并重新實現其中的方法是關鍵。

View具有以下三個構造函數,分別用于在不同場合產生View:

        public View(Context context)                                     // 簡單構造
        public View(Context context, AttributeSet attrs)                // 帶有屬性
        public View(Context context, AttributeSet attrs, int defStyle) // 帶屬性和默認值

View類中的一系列方法(protected類型的居多),用于View的運行周期內的特定時刻調用。通過定制實現這些方法,可以定制Vi e w行為。

Vi e w在創建結束的時候方法如下所示:

        protected void onFinishInflate()

onFinishInflate()方法將在布局XML文件Inflate完成的時刻被調用。Vi e w中與布局相關的幾個方法如下所示:

        protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
        protected void onLayout (boolean changed, int left, int top, int right, int bottom)
        protected void onSizeChanged (int w, int h, int oldw, int oldh)

onMeasure()用于確認View及其孩子要求的尺寸,onLayout()用于在View完成給它的孩子們分配好尺寸和位置的時刻調用,onSizeChanged()用于在View的尺寸發生變化的時刻調用。

Vi e w在繪制時的方法如下所示:

        protected void onDraw (Canvas canvas)

onDraw()是View類的核心方法,在繪制的時候被調用,這是一個用于確定View外觀的關鍵方法。

一般情況下,在一個控件的構建過程中,依次將調用onFinishInflate()、onMeasure()、onLayout()和onDraw()方法。

Vi e w中幾個設備相關事件處理方法如下所示:

        public boolean onKeyDown (int keyCode, KeyEvent event)    // 按鍵按下事件
        public boolean onKeyUp (int keyCode, KeyEvent event)      // 按鍵抬起事件
        public boolean onTouchEvent (MotionEvent event)           // 觸摸屏事件
        public boolean onTrackballEvent (MotionEvent event)       // 軌跡球事件

Vi e w中與聚焦相關的方法如下所示:

        public void onWindowFocusChanged (boolean hasWindowFocus)
        protected void onFocusChanged (boolean gainFocus, int direction,
                                          Rect previouslyFocusedRect)

onWindowFocusChanged()將在包含當前控件的窗口聚焦改變的情況下被調用,onFocusChanged()將在當前控件的聚焦發生變化的時候被調用。

與聯系到窗口相關的方法如下所示:

        protected void onAttachedToWindow()
        protected void onDetachedFromWindow()
        protected void onWindowVisibilityChanged(int visibility)

onAttachedToWindow()和onDetachedFromWindow()只有在View包含特定繪制功能的時候才被調用(前者將在onDraw()之前被調用);onWindowVisibilityChanged()將在包含View的窗口的可見性發生變化的時候被調用。

通過繼承Vi e w實現一個自定義控件的時候,各個方法只有在需要的時候才需要被重新實現,基本的實現是實現onDraw()方法完成繪制。

基于Vi e w自定義控件的實現可以分成如下三個層次。

第一層次:自定義控件可在程序中復用。

通過實現View的onDraw()方法,在Java代碼中可以使用new構建View。

第二層次:在布局文件中使用控件。

進一步實現View帶有屬性的構造方法,可以在布局文件中定義View。

第三層次:自定義控件的屬性。

使用XML文件描述自定義屬性,在代碼中解析自定義屬性,在布局文件中增加名稱空間以使用自定義屬性。

參考示例程序為:CustomView(ApiDemo=>Views)

源代碼:

              com/example/android/apis/view/CustomView1.java
              com/example/android/apis/view/LabelView.java

布局文件:custom_view_1.xml

CustomView程序的運行效果如圖3-10所示。

圖3-10 CustomView程序

3行的內容均為LabelView

第1行的內容:文本為"Red"

第2行的內容:文本為"Blue",文本大小為20dp

第3行的內容:文本為"Green",文本顏色為白色

這個程序的三個“文字標簽”實際上是一個自定義控件的三個實例,這個自定義控件就是由LabelView.java實現的LabelView,CustomView1.java是簡單使用這個控件的Activity。

LabView.java實現的基本結構如下所示:

        import android.view.View;
        public class LabelView extends View {            // 與View類繼承關系
            public LabelView(Context context) {           // View的構造函數
              super(context);  // 調用基類的方法
              initLabelView();
            }
        // ...... 省略部分內容
            protected void onDraw(Canvas canvas) {        // View繪制方法
              super.onDraw(canvas);
              canvas.drawText(mText, getPaddingLeft(),
                                  getPaddingTop() - mAscent, mTextPaint);
            }
        }

從最基本的角度,以上程序實現View的構造函數和onDraw()方法也就是實現了自定義控件的第一個層次。在這種情況下實現的控件只能在Java代碼中,使用new新建出來使用,但是還不能在布局文件中使用。

CustomView1中使用的布局文件custom_view_1.xml如下所示:

        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res/com.example.android.apis"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="wrap_content">
            <com.example.android.apis.view.LabelView
                  android:background="@drawable/red"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  app:text="Red"/>
            <com.example.android.apis.view.LabelView
                  android:background="@drawable/blue"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  app:text="Blue"        app:textSize="20dp"/>
            <com.example.android.apis.view.LabelView
                  android:background="@drawable/green"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  app:text="Green"       app:textColor="#ffffffff" />
        </LinearLayout>

這里使用的標簽com.example.android.apis.view.LabelView也就是在LabelView代碼中自己實現的控件。在布局文件使用這種自定義控件,需要使用類的全路徑。

之所以能在布局文件中使用這個自定義控件,必須實現Vi e w的第二個構造函數,也就是含有Context和AttributeSet類型參數的構造函數。

LabelView實現的方法如下所示:

        public LabelView(Context context, AttributeSet attrs) {
            super(context, attrs);
            initLabelView();
        // 以上的內容和第一個構造函數相同,以下的內容為屬性(Attributes)解析
            TypedArray a = context.obtainStyledAttributes(attrs, // 獲得屬性組
                                        R.styleable.LabelView);
            CharSequence s = a.getString(R.styleable.LabelView_text);
            if (s != null) {
              setText(s.toString());    // 設置文本
            }
            setTextColor(a.getColor(R.styleable.LabelView_textColor, 0xFF000000));
            int textSize =
                      a.getDimensionPixelOffset(R.styleable.LabelView_textSize, 0);
            if (textSize > 0) {
              setTextSize(textSize);   // 設置文本大小
            }
            a.recycle();
        }

Android從布局文件(XML)中解析建立一個View的時候,需要調用如上類型的構造函數,因此只要在布局文件中使用自定義控件,這個構造函數就必須實現。到這個階段相當于實現了自定義控件的第二個層次。

提示:只要在布局文件中使用控件,View(Context, AttributeSet)類型的構造函數就必須實現,無論有沒有自定義屬性。當然,在沒有自定義屬性的情況下,不需要對AttributeSet類型參數進行解析。

以上構造函數中的AttributeSet類型的參數,實際上是布局文件在被解析之后,被解析器傳入的參數。處理自定義屬性就是實現自定義控件的第三個層次。

在上述的布局文件中,有app:text,app:textSize和app:textColor三個自定義屬性,因此可以看到第二個文本標簽的大小不同,第三個文本標簽的顏色不同。

在LabelView的第二個構造函數中,通過context.obtainStyledAttributes()對這幾個屬性進行解析。R.styleable中的LabelView表示的是一個整數類型的數組,LabelView_text,LabelView_textSize和LabelView_textColor分別代表三個整數,它們對應于布局文件中的定義。

之所以能在代碼中找到這樣幾個屬性,實際上需要在res/values/的attrs.xml文件中進行定義,內容如下所示:

        <declare-styleable name="LabelView">
            <attr name="text"       format="string" />
            <attr name="textColor"  format="color" />
            <attr name="textSize"   format="dimension" />
        </declare-styleable>

根據以上的實現,將生成R.styleable中的整型數組和整型值,這樣的幾個數值由此可以在布局文件中被引用。

另一方面,在布局文件中使用這些自定義屬性,還需要增加命名空間(Name Space),custom_view_1.xml的開頭位置有如下描述。

        xmlns:app="http://schemas.android.com/apk/res/com.example.android.apis"

此處表示所指定的XML命名空間(xmlns)中的app為當前類的路徑,因此在這個布局文件中可以使用attrs.xml文件中定義的屬性。

至此,LabelView實現了自定義控件的第三個層次,這個類實際上就是一個簡化版的TextView。從繪制的角度,實現的內容較少,更復雜的實現需要在onDraw()中完成。

作為公共的屬性,LabelView在實現上也具有公共的函數來設置這幾個屬性(文本、文本顏色、文本大?。7椒ㄈ缦滤荆?/p>

        public void setText(String text) {
            mText = text;
            requestLayout();            // 需要重新刷新布局
            invalidate();               // 迫使控件重新繪制
        }
        public void setTextSize(int size) {
            mTextPaint.setTextSize(size);
            requestLayout();            // 需要重新刷新布局
            invalidate();               // 迫使控件重新繪制
        }
        public void setTextColor(int color) {
            mTextPaint.setColor(color);
            invalidate();               // 迫使控件重新繪制
        }

以上的幾個函數和幾個XML中的屬性是對應的,如果沒有它們,這些屬性在指定后,就不能再通過Java源代碼調用改變。以上的幾個方法其實更新的是幾個類的變量,通過調用invalidate()促使onDraw()函數被調用,進行重新繪制。

提示:setText() 和setTextSize() 調用requestLayout()表示需要更新View樹,setTextColor() 不需要這個調用,這是因為文本和文本的變化影響尺寸,但是顏色的變化不會影響到尺寸。

3.1.6 繼承控件實現自定義控件

在自定義控件的實現過程中,大多數情況是繼承控件類的基類Vi e w。在某些情況下,也可以實現一個預定義的控件來實現。這樣做的主要目的是復用預定義控件中的功能,對其進行配置和定制。

通過繼承一個控件來實現自定義控件的方法,需要根據具體控件的情況來確定哪些內容需要重新實現。

一般情況下,繼承控件的自定義控件有幾個方面的內容:

繪制,同View中的onDraw()方法;

XML屬性的處理,可能包括XML屬性的重新解釋;

調用父類的方法。如果某個控件適合被繼承,會提供相應的方法幫助繼承者復用這個類的功能。

AttributeSet類中的以下幾個方法用于遍歷其中的各個屬性,如下所示:

        public abstract int getAttributeCount ()                  // 屬性的數目
        public abstract String getAttributeName (int index)       // 屬性的名字
        public abstract String getAttributeValue (int index)      // 屬性的數值

根據以上的幾個方法,不需要得到各個屬性常量,而是可以根據在布局文件中書寫的表示屬性和屬性值的字符串來完成解析。相對來說,這種方法比根據整數值得到屬性的方法更為直接。

以下的實例程序是一個通過繼承TextView所實現的自定義控件,其中復用了TextView的主要功能,并且定制了其中的一些內容。

程序的運行效果如圖3-11所示。

圖3-11 繼承TextView所實現的自定義控件

第1行的內容:文本內容為“Text 1”,文本顏色為紅

第2行的內容:文本內容為“Text 2”,文本顏色為綠

第3行的內容:文本內容為“Text 3”,文本顏色為藍

第4行的內容:文本內容無設置,文本顏色為白(被截獲更改)

第5行的內容:文本內容無設置,文本顏色為默認

程序中自上而下的5個內容實際上就是由繼承TextView所實現的自定義控件的5個實例。程序所使用的布局文件如下所示:

        <?xml version="1.0" encoding="utf-8"?>
        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/screen"
            android:layout_width="fill_parent"  android:layout_height="fill_parent"
            android:orientation="vertical">
            <com.android.screenui.CustomTextView android:id="@+id/text1"
              android:layout_width="240sp" android:layout_height="wrap_content"
              android:layout_gravity="center" android:textColor="#ffff0000"
              android:text="@string/text1"/>
            <com.android.screenui.CustomTextView android:id="@+id/text2"
              android:layout_width="240sp" android:layout_height="wrap_content"
              android:layout_gravity="center" android:textColor="#ff00ff00"
              android:text="@string/text2"/>
            <com.android.screenui.CustomTextView android:id="@+id/text3"
              android:layout_width="240sp" android:layout_height="wrap_content"
              android:layout_gravity="center" android:textColor="#ff0000ff"
              android:text="@string/text3"/>
            <com.android.screenui.CustomTextView android:id="@+id/text4"
              android:layout_width="240sp" android:layout_height="wrap_content"
              android:textColor="#ffffffff" android:layout_gravity="center" />
            <com.android.screenui.CustomTextView android:id="@+id/text5"
              android:layout_width="240sp"  android:layout_height="wrap_content"
              android:layout_gravity="center" />
        </LinearLayout>

由于這里的自定義控件CustomTextView繼承自TextView,因此在布局文件中使用的android:text和android:textColor等內容屬性都是來自于TextView的XML屬性。屬性的使用方面和TextView是一樣的,主要的差別是在Java代碼中對屬性的解析不同。

本例的Java文件實現了自定義控件CustomTextView,內容如下所示:

        import android.content.Context;
        import android.content.res.TypedArray;
        import android.graphics.Canvas;
        import android.graphics.Paint;
        import android.graphics.Color;
        import android.graphics.drawable.Drawable;
        import android.util.AttributeSet;
        import android.view.View;
        import android.widget.TextView;
        import android.R.styleable;
        import android.util.Log;
        public class CustomTextView extends TextView {       // 繼承TextView
            private static final String TAG = "CustomTextView";
            private static final String DEFAULT_TEXT = "Default Text";
            private Paint mPaint;
            private String mText;
            public CustomTextView(Context context) {          // 構造函數
              super(context);
              initView();   // 調用進行View的初始化
            }
            public CustomTextView(Context context, AttributeSet attrs){ // 帶屬性的構造函數
              super(context, attrs);
              Log.v(TAG,"CustomTextView");
              for(int i = 0; i< attrs.getAttributeCount();i++){  // 遍歷控件的XML屬性
                  int res = attrs.getAttributeNameResource(i);
                  String attrname = attrs.getAttributeName(i);    // 獲得屬性的名稱
                  String attrvalue = attrs.getAttributeValue(i);  // 獲得屬性的值
                  Log.v(TAG,"ATTR "+res + " - " + attrname + " - "+ attrvalue);
                  if(attrname.equals("textColor") && attrvalue.equals("#ffffffff")){
                      setTextColor(0xff000000);
                  }
              }
              initView();   // 調用進行View的初始化
            }
            private final void initView() {
              String text = getText().toString();
              if(text.length() == 0){                    // 設置默認的文本
                  setText(DEFAULT_TEXT);
              }
              setTextSize(24);                           // 設置文本尺寸
              Drawable dr = getContext().getResources() // 獲得背景圖片
                                            .getDrawable(R.drawable.background);
              setBackgroundDrawable(dr);                 // 設置背景
              mPaint = new Paint();                      // 設置自定義繪制的內容
              mPaint.setColor(getCurrentTextColor());   // 設置附加內容的繪制信息
              mPaint.setTextSize(30);
              mPaint.setTextAlign(Paint.Align.RIGHT);
            }
            @Override
            protected void onDraw(Canvas canvas) {
              super.onDraw(canvas);                      // 默認的繪制
              canvas.drawText("@",canvas.getClipBounds().right,  // 自定義的附加繪制
                                  canvas.getClipBounds().bottom,mPaint);
            }
        }

這是繼承實現的CustomTextView,主要自定義了以下幾個方面的內容:

指定控件的背景;

指定文本的大小;

在沒有文本的情況下,指定默認的文本;

自定義繪制一個附加的內容。

在程序中,調用了setBackgroundDrawable()方法,這實際上是View中的方法,強行指定了這個控件的背景,因此其程序的背景和基類TextView不同。

程序調用了基類TextView的setTextSize()方法,設置了默認的文本尺寸。

當文本的顏色(android:textColor)為白色(#ffffffff)時,調用TextView的setTextColor()將文本設置為黑色。

調用TextView的getText()方法獲得程序中設置的文本,當文本為空的時候,TextView的setText()將其設置為默認的文本。

由于以上的處理,前三行內容使用了設置的文本內容,顏色分別為紅、綠和藍;第四行和第五行內容由于沒有設置文本,使用了默認文本("Default Text");第四行文本的顏色由于設置文本顏色為白色,在程序中被轉換成了黑色;第五行沒有設置文本顏色,實際上使用了TextView的默認顏色。

以上設置的過程,大都使用了基類的方法,這些設置的內容在onDraw()情況下對設置的內容進行了繪制。以上的程序實際上是截獲并處理了部分XML屬性。

提示:由于TextView是一個適合繼承的控件類,因此其很多屬性具有get和set方法用于獲得和重新設置數值,設置的內容可以在onDraw()的繪制中得到體現。

如果自定義控件僅僅處理XML屬性的處理,onDraw()方法都是不需要實現的。上述程序的onDraw()實現的主要目標是為了繪制一個附加的字符(@),并且這個字符的顏色和文本的顏色相同。

主站蜘蛛池模板: 白银市| 娄底市| 台安县| 文登市| 滦平县| 祁阳县| 永川市| 吉林市| 汕头市| 成安县| 龙陵县| 宝山区| 那坡县| 滦平县| 三明市| 曲松县| 铜陵市| 得荣县| 紫云| 太康县| 九龙县| 通榆县| 古田县| 陵川县| 巴彦淖尔市| 民勤县| 花莲市| 武安市| 观塘区| 疏附县| 盐山县| 新乐市| 九龙城区| 昭通市| 江津市| 朝阳县| 朝阳区| 丹棱县| 织金县| 铜陵市| 涞源县|