這節(jié)課是 Android 開(kāi)發(fā)(入門(mén))課程 的第四部分《數(shù)據(jù)與數(shù)據(jù)庫(kù)》的第二節(jié)課,導(dǎo)師依然是 Jessica Lin 和 Katherine Kuan。這節(jié)課在掌握 SQLite 數(shù)據(jù)庫(kù)基本操作的前提下,在 Pets App 的最小可行性應(yīng)用 (MVP, Minimum Viable Product) 中使用 SQLite 數(shù)據(jù)庫(kù),主要參考 這篇 Android Developers 文檔,內(nèi)容包括
- 使用 Contract 類(lèi)確定數(shù)據(jù)庫(kù)架構(gòu)。
- 使用 SQLiteOpenHelper 類(lèi)創(chuàng)建、連接、管理數(shù)據(jù)庫(kù)。
- 插入數(shù)據(jù)時(shí)使用的 ContentValues 類(lèi)。
- 讀取數(shù)據(jù)時(shí)使用的 Cursor 對(duì)象。
關(guān)鍵詞:Contract Class、SQLiteOpenHelper、SQLiteDatabase 、ContentValues、Cursor、Floating Action Button、Spinner
Contract Class
在 Android 應(yīng)用中使用 SQLite 數(shù)據(jù)庫(kù),首先需要使用 Contract 類(lèi)確定數(shù)據(jù)庫(kù)架構(gòu) (schema)。所謂數(shù)據(jù)庫(kù)架構(gòu),可以理解為第一節(jié)課中提到的表格結(jié)構(gòu),主要是每一列的屬性及其對(duì)應(yīng)的存儲(chǔ)類(lèi)和限制信息等。Android 提供了 Contract 類(lèi)來(lái)保存數(shù)據(jù)庫(kù)架構(gòu),即保存表格的名稱(chēng)、每一列的屬性名等。這樣做的好處是,將數(shù)據(jù)庫(kù)架構(gòu)及其相關(guān)的常量統(tǒng)一放在一個(gè)類(lèi)內(nèi),容易訪問(wèn)和管理;同時(shí)在利用數(shù)據(jù)庫(kù)架構(gòu)的常量生成 SQL 指令時(shí),杜絕了錯(cuò)別字的可能性。
例如在 Pets App 中,通過(guò)內(nèi)部類(lèi) PetEntry 定義了表格的名稱(chēng)為字符串常量 pets,列 _id 作為數(shù)據(jù)庫(kù)表格的 ID,寵物名字的列為字符串常量 name 等。
In java/com.example.android.pets/data/PetContract.java
/**
* Inner class that defines constant values for the pets database table.
* Each entry in the table represents a single pet.
*/
public static final class PetEntry implements BaseColumns {
/** Name of database table for pets */
public final static String TABLE_NAME = "pets";
/**
* Unique ID number for the pet (only for use in the database table).
*
* Type: INTEGER
*/
public final static String _ID = BaseColumns._ID;
/**
* Name of the pet.
*
* Type: TEXT
*/
public final static String COLUMN_PET_NAME ="name";
/**
* Possible values for the gender of the pet.
*/
public static final int GENDER_UNKNOWN = 0;
public static final int GENDER_MALE = 1;
public static final int GENDER_FEMALE = 2;
}
注意 PetContract 文件放在應(yīng)用包名 (com.example.android.pets) 下單獨(dú)的 data 包內(nèi),與 Activity 文件區(qū)分開(kāi)。這是因?yàn)?Android 是通過(guò)包 (package) 來(lái)組織代碼的,一類(lèi)包存放一類(lèi)特定功能的代碼,例如 data 包下就存放數(shù)據(jù)相關(guān)的代碼。
-
在調(diào)用 PetContract 中 PetEntry 的常量時(shí),需要通過(guò)
PetContract.PetEntry指定,例如PetContract.PetEntry.GENDER_MALE這是因?yàn)檎{(diào)用處導(dǎo)入的包名默認(rèn)為外部類(lèi) PetContract
import com.example.android.pets.data.PetContract;為了精簡(jiǎn)代碼,可以將包名指向 PetContract 的內(nèi)部類(lèi)
import com.example.android.pets.data.PetContract.PetEntry;這樣在調(diào)用其常量時(shí),就可以省略 PetContract
PetEntry.GENDER_MALE這種方法適合在 Contract 類(lèi)只有一個(gè)表格的情況下使用。
PetContract 類(lèi)要定義為
final使之不能被擴(kuò)展 (extends),因?yàn)樗粚?duì)外提供常量,不實(shí)現(xiàn)任何功能。對(duì)于用 INETGER 來(lái)存儲(chǔ)固定選項(xiàng)的數(shù)據(jù),其對(duì)應(yīng)的規(guī)則常量也在此定義。例如上面的寵物性別信息,0 表示未知,1 表示雄性,2 表示雌性。
SQLiteOpenHelper
在 Contract 類(lèi)確定數(shù)據(jù)庫(kù)架構(gòu)后,使用 Android 提供的 SQLiteOpenHelper 類(lèi)來(lái)創(chuàng)建、連接、管理數(shù)據(jù)庫(kù)。SQLiteOpenHelper 可以看成是應(yīng)用與數(shù)據(jù)庫(kù)之間的橋梁。
SQLiteOpenHelper 是一個(gè)抽象類(lèi),實(shí)現(xiàn)時(shí)需要 override onCreate 和 onUpgrade method。例如在 Pets App 中,創(chuàng)建一個(gè) PetDbHelper 類(lèi),擴(kuò)展自 SQLiteOpenHelper。注意文件路徑與 PetContract 的相同,表示 PetDbHelper 類(lèi)屬于數(shù)據(jù)功能的代碼。
In java/com.example.android.pets/data/PetDbHelper.java
public class PetDbHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "shelter.db";
private static final int DATABASE_VERSION = 1;
public PetDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// Create a String that contains the SQL statement to create the pets table
String SQL_CREATE_PETS_TABLE = "CREATE TABLE " + PetEntry.TABLE_NAME + " ("
+ PetEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ PetEntry.COLUMN_PET_NAME + " TEXT NOT NULL, "
+ PetEntry.COLUMN_PET_BREED + " TEXT, "
+ PetEntry.COLUMN_PET_GENDER + " INTEGER NOT NULL, "
+ PetEntry.COLUMN_PET_WEIGHT + " INTEGER NOT NULL DEFAULT 0);";
// Execute the SQL statement
db.execSQL(SQL_CREATE_PETS_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// The database is still at version 1, so there's nothing to do be done here.
}
}
- 首先定義兩個(gè)常量,分別表示數(shù)據(jù)庫(kù)的名稱(chēng),不要忘記后綴
.db;以及數(shù)據(jù)庫(kù)版本,初始值默認(rèn)為 1。 - 構(gòu)造函數(shù)調(diào)用超級(jí)類(lèi),輸入?yún)?shù)分別為
(1)Context,應(yīng)用環(huán)境,執(zhí)行構(gòu)造函數(shù)時(shí)傳入;
(2)數(shù)據(jù)庫(kù)名稱(chēng),傳入在外部類(lèi)定義的常量;
(3)CursorFactory,設(shè)為 null 以使用默認(rèn)值;
(4)數(shù)據(jù)庫(kù)版本,傳入在外部類(lèi)定義的常量。 - override
onCreatemethod 添加創(chuàng)建數(shù)據(jù)庫(kù)時(shí)的指令,其中調(diào)用了 SQLiteDatabase 的execSQLmethod 執(zhí)行上面生成的CREATE TABLESQL 指令(存為字符串),實(shí)現(xiàn)創(chuàng)建數(shù)據(jù)庫(kù)的操作。值得注意的是,由于execSQL無(wú)返回值,所以它不能用來(lái)執(zhí)行如SELECT等帶有返回值的 SQL 指令。 - override
onUpgrademethod 添加數(shù)據(jù)庫(kù)版本發(fā)生變化時(shí)的指令,例如 SQLite 數(shù)據(jù)庫(kù)表格增加了一列,數(shù)據(jù)庫(kù)版本變更,可以在onUpgrade使用execSQL執(zhí)行DROP TABLESQL 指令,刪除舊表格后再調(diào)用onCreate創(chuàng)建新數(shù)據(jù)庫(kù)。不過(guò)在這里,目前數(shù)據(jù)庫(kù)版本保持不變,所以onUpgrade暫時(shí)留空,這部分內(nèi)容將在后續(xù)課程修改。
實(shí)現(xiàn) SQLiteOpenHelper 這個(gè)抽象類(lèi)后,就可以通過(guò)它來(lái)創(chuàng)建、連接、管理 SQLiteDatabase 數(shù)據(jù)庫(kù)了。例如在 Pets App 中,首先創(chuàng)建 SQLiteOpenHelper 實(shí)例。
In CatalogActivity.java
PetDbHelper mDbHelper = new PetDbHelper(this);
調(diào)用 PetDbHelper 的構(gòu)造函數(shù),傳入 this 即當(dāng)前 Activity 的環(huán)境。獲得 PetDbHelper 實(shí)例后,調(diào)用 getReadableDatabase() 創(chuàng)建或連接 SQLiteDatabase 數(shù)據(jù)庫(kù)。
SQLiteDatabase db = mDbHelper.getReadableDatabase();
這條指令相當(dāng)于 sqlite3 的 .open 指令,用于打開(kāi)或創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)文件。具體來(lái)說(shuō),應(yīng)用在調(diào)用 getReadableDatabase() 時(shí),PetDbHelper 會(huì)查詢當(dāng)前是否已存在數(shù)據(jù)庫(kù),若無(wú),PetDbHelper 會(huì)執(zhí)行其 onCreate method 創(chuàng)建數(shù)據(jù)庫(kù),返回一個(gè) SQLiteDatabase 對(duì)象;若有則不執(zhí)行 onCreate method,直接創(chuàng)建一個(gè) SQLiteDatabase 對(duì)象,與已有的數(shù)據(jù)庫(kù)連接。因此,無(wú)論應(yīng)用是否已存在數(shù)據(jù)庫(kù),調(diào)用 getReadableDatabase() 的結(jié)果都是獲得一個(gè)連接數(shù)據(jù)庫(kù)的 SQLiteDatabase 對(duì)象,通過(guò)它來(lái)操作數(shù)據(jù)庫(kù)。
不過(guò)事實(shí)上,通過(guò) getReadableDatabase() 獲得的 SQLiteDatabase 對(duì)象,只能對(duì)數(shù)據(jù)庫(kù)進(jìn)行讀取 (Read) 操作,如果想要 CRUD 的其余操作,需要調(diào)用 getWritableDatabase() 獲取 SQLiteDatabase 對(duì)象。
ContentValues
在獲得一個(gè)連接數(shù)據(jù)庫(kù)的 SQLiteDatabase 對(duì)象后,就可以通過(guò)它對(duì)數(shù)據(jù)庫(kù)進(jìn)行 CRUD 操作,其中經(jīng)常用到 ContentValues 構(gòu)造數(shù)據(jù)。ContentValues 是一個(gè)具象類(lèi),用于存儲(chǔ)大量的鍵/值對(duì),對(duì)于數(shù)據(jù)庫(kù)而言,鍵是對(duì)象的屬性,值是對(duì)應(yīng)的屬性值。例如在 Pets App 中,創(chuàng)建一個(gè) ContentValues 對(duì)象,并通過(guò) put method 添加鍵/值對(duì)。
String nameString = mNameEditText.getText().toString().trim();
String breedString = mBreedEditText.getText().toString().trim();
String weightString = mWeightEditText.getText().toString().trim();
int weight = Integer.parseInt(weightString);
ContentValues values = new ContentValues();
values.put(PetEntry.COLUMN_PET_NAME, nameString);
values.put(PetEntry.COLUMN_PET_BREED, breedString);
values.put(PetEntry.COLUMN_PET_GENDER, mGender);
values.put(PetEntry.COLUMN_PET_WEIGHT, weight);
long newRowId = db.insert(PetEntry.TABLE_NAME, null, values);
- 添加到 ContentValues 對(duì)象的鍵為 Contract 類(lèi)中定義的架構(gòu)列常量,對(duì)應(yīng)的值為從 EditText 獲取的用戶輸入值。
- 在
toString后調(diào)用trim去除字符串開(kāi)頭和結(jié)尾的多余空格。 - 調(diào)用
Integer.parseInt()使字符串轉(zhuǎn)換為整數(shù)。
構(gòu)造好往數(shù)據(jù)庫(kù)添加的數(shù)據(jù)后,調(diào)用 SQLiteDatabase 的 insert method 向數(shù)據(jù)庫(kù)添加數(shù)據(jù)。輸入?yún)?shù)分別為
- table: 要添加數(shù)據(jù)的表格名稱(chēng)。
- nullColumnHack: 可選參數(shù),可設(shè)為 null,僅在往數(shù)據(jù)庫(kù)添加空行時(shí)用到。
- values: 要往數(shù)據(jù)庫(kù)添加的數(shù)據(jù),數(shù)據(jù)類(lèi)型為 ContentValues 對(duì)象。
SQLiteDatabase 的 insert method 返回值為新添加的行 ID,發(fā)生錯(cuò)誤時(shí)返回 -1。
Tips:
1. 調(diào)用 finish() method 可以關(guān)閉當(dāng)前 Activity,使屏幕界面回到原先 Activity。
2. 根據(jù) Activity 的生命周期,在 onStart 中添加的代碼,可以在用戶通過(guò) Activity 切換回來(lái)時(shí)執(zhí)行。
Cursor
與往數(shù)據(jù)庫(kù)添加數(shù)據(jù)類(lèi)似,SQLiteDatabase 也提供了讀取數(shù)據(jù)的方法,例如通過(guò) rawQuery 傳入 SQL 指令讀取數(shù)據(jù),不過(guò)還有更規(guī)范的 query method,可以避免直接操作 SQL 指令導(dǎo)致的語(yǔ)法錯(cuò)誤。SQLiteDatabase 提供了很多不同的 query method,其中較簡(jiǎn)單的為
Cursor query (String table,
String[] columns,
String selection,
String[] selectionArgs,
String groupBy,
String having,
String orderBy)
有七個(gè)輸入?yún)?shù),分別為
- table
想要讀取的表格名稱(chēng)。 - columns
想要讀取的列數(shù)據(jù),傳入 null 表示讀取所有列,但是不建議這么做,尤其是數(shù)據(jù)庫(kù)很龐大時(shí)會(huì)消耗大量系統(tǒng)資源。 - selection
讀取數(shù)據(jù)的篩選條件,相當(dāng)于 SQL 指令的WHERE條件,傳入 null 表示無(wú)篩選條件,返回所有數(shù)據(jù)行。 - selectionArgs
與 selection 配合使用,當(dāng) selection 包含 "?" 時(shí),selectionArgs 數(shù)組的字符串會(huì)填入相應(yīng)的位置,作為讀取數(shù)據(jù)的篩選條件。這種模式屬于防止 SQL 注入的安全措施。 - groupBy
讀取的數(shù)據(jù)行的分組條件,相當(dāng)于 SQL 指令的GROUP BY條件,傳入 null 表示數(shù)據(jù)行不分組。 - having
在讀取的數(shù)據(jù)行通過(guò) groupBy 分組的情況下,指定哪一組包含 Cursor 內(nèi),相當(dāng)于 SQL 指令的HAVING條件,傳入 null 表示所有組包含 Cursor 內(nèi)。另外,當(dāng) groupBy 為 null,having 也要傳入 null。 - orderBy
讀取的數(shù)據(jù)行的排序方法,相當(dāng)于 SQL 指令的ORDER BY條件,傳入 null 表示數(shù)據(jù)行保持默認(rèn)排序。
例如 這篇 Android Developers 文檔 中的例子,它讀取的數(shù)據(jù)是 ID、標(biāo)題、副標(biāo)題三列,標(biāo)題為 "My Title",按照副標(biāo)題降序排列的,不分組的。其中 selection 中的 "= ?" 可以是一個(gè)等號(hào),也可以是兩個(gè)等號(hào)。
SQLiteDatabase db = mDbHelper.getReadableDatabase();
// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
FeedEntry._ID,
FeedEntry.COLUMN_NAME_TITLE,
FeedEntry.COLUMN_NAME_SUBTITLE
};
// Filter results WHERE "title" = 'My Title'
String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";
String[] selectionArgs = { "My Title" };
// How you want the results sorted in the resulting Cursor
String sortOrder = FeedEntry.COLUMN_NAME_SUBTITLE + " DESC";
Cursor cursor = db.query(
FeedEntry.TABLE_NAME, // The table to query
projection, // The columns to return
selection, // The columns for the WHERE clause
selectionArgs, // The values for the WHERE clause
null, // don't group the rows
null, // don't filter by row groups
sortOrder // The sort order
);
SQLiteDatabase 的所有 query method 的返回值都是一個(gè) Cursor 對(duì)象,它保存了讀取到的數(shù)據(jù)庫(kù)的多行內(nèi)容。在讀取數(shù)據(jù)時(shí),Cursor 可以看成是應(yīng)用與 SQLiteDatabase 之間的橋梁。
Cursor 的一個(gè)重要概念是當(dāng)前行的位置。在 Cursor 沒(méi)有數(shù)據(jù)時(shí),它的默認(rèn)位置為無(wú)效的 -1,所以與 Array 類(lèi)似,Cursor 的首個(gè)可用位置為 0,隨后逐步遞增。Cursor 提供了 move method 用于移動(dòng)當(dāng)前行的位置,以獲取指定行的數(shù)據(jù),常用的有
| Method | Description |
|---|---|
| moveToFirst () | 使 Cursor 當(dāng)前行的位置移至首行 |
| moveToLast () | 使 Cursor 當(dāng)前行的位置移至末行 |
| moveToNext () | 使 Cursor 當(dāng)前行的位置下移一行 |
| moveToPosition (int position) | 使 Cursor 當(dāng)前行的位置移至指定行,-1 <= position <= count |
| moveToPrevious () | 使 Cursor 當(dāng)前行的位置返回到原來(lái)那一行 |
Cursor 的 move method 的返回值類(lèi)型都是布爾類(lèi)型,移動(dòng)成功為 true,失敗則 false。例如 Cursor 當(dāng)前行為最后一行時(shí)調(diào)用 moveToNext 的返回值即 false。
Cursor 還提供很多 getter method 用于獲取不同類(lèi)型的數(shù)據(jù),例如 getInt()、getString、getType 等,它們的輸入?yún)?shù)都是 columnIndex 列索引,這是由 projection 決定的,通過(guò) getColumnIndex 獲得。
使用完 Cursor 后,一定要調(diào)用 close method 關(guān)閉 Cursor,防止內(nèi)存泄漏。
以 Pets App 為例
try {
displayView.setText("The pets table contains " + cursor.getCount() + " pets.\n\n");
displayView.append(PetEntry._ID + " - " +
PetEntry.COLUMN_PET_NAME + " - " +
PetEntry.COLUMN_PET_BREED + " - " +
PetEntry.COLUMN_PET_GENDER + " - " +
PetEntry.COLUMN_PET_WEIGHT + "\n");
int idColumnIndex = cursor.getColumnIndex(PetEntry._ID);
int nameColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_NAME);
int breedColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_BREED);
int genderColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_GENDER);
int weightColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_WEIGHT);
while (cursor.moveToNext()) {
int currentID = cursor.getInt(idColumnIndex);
String currentName = cursor.getString(nameColumnIndex);
String currentBreed = cursor.getString(breedColumnIndex);
int currentGender = cursor.getInt(genderColumnIndex);
int currentWeight = cursor.getInt(weightColumnIndex);
displayView.append(("\n" + currentID + " - " +
currentName + " - " +
currentBreed + " - " +
currentGender + " - " +
currentWeight));
}
} finally {
cursor.close();
}
- 使用
append為 TextView 添加內(nèi)容。 - 使用
getColumnIndex獲取每一列的索引。 - 將
moveToNext放入 while 循環(huán)語(yǔ)句中達(dá)到遍歷所有數(shù)據(jù)行的效果。這是因?yàn)檎G闆r下,moveToNext的返回值為 true 即進(jìn)入循環(huán);直到 Cursor 在最后一行時(shí)調(diào)用moveToNext的返回值為 false 即跳出循環(huán)。 - 通過(guò)不同的 getter method 獲取不同類(lèi)型的數(shù)據(jù)。
- 將上述代碼放入 try/finally 區(qū)塊內(nèi),即使應(yīng)用崩潰,也能保證執(zhí)行
closemethod 關(guān)閉 Cursor。
如何提取應(yīng)用的數(shù)據(jù)庫(kù)文件
在 Android Studio 界面右上角搜索 Device File Explorer 打開(kāi)模擬器的文件瀏覽器,打開(kāi)目錄 data > data > com.example.android.pets > databases 即可查看應(yīng)用內(nèi)的數(shù)據(jù)庫(kù)文件。右鍵選擇 "Save As..." 將數(shù)據(jù)庫(kù)文件提取到電腦中,即可通過(guò)終端訪問(wèn)。
Note:
1. 此方法僅適用于模擬器或具有最高權(quán)限 (rooted) 的物理設(shè)備。
2. 清除應(yīng)用的緩存 (cache) 和數(shù)據(jù) (data) 會(huì)刪除應(yīng)用的數(shù)據(jù)庫(kù)文件。
Floating Action Button
Pets App 使用了 FloatingActionButton 連接兩個(gè) Activity,它是一個(gè)懸浮在 UI 之上的圓形按鈕,有獨(dú)特的顯示和交互效果。
In activity_catalog.xml
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="@dimen/fab_margin"
android:src="@drawable/ic_add_pet"/>
將 FloatingActionButton 的 ID 設(shè)置為 fab,圓形按鈕的位置放在屏幕的右下角,距離邊緣 16dp。由于 FloatingActionButton 是 ImageView 的子類(lèi),所以其顯示圖標(biāo)可以通過(guò) android:src 屬性設(shè)置;其余特性與 Button 類(lèi)似,例如在 Pets App 中,設(shè)置其 OnClickListener 動(dòng)作為打開(kāi) EditorActivity。
In CatalogActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_catalog);
// Setup FAB to open EditorActivity
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(CatalogActivity.this, EditorActivity.class);
startActivity(intent);
}
});
...
}
Spinner
Pets App 使用了 Spinner 作為輸入寵物性別的選項(xiàng),它是一個(gè)單選菜單,默認(rèn)狀態(tài)下會(huì)顯示默認(rèn)值和一個(gè)向下箭頭的圖標(biāo)。
In activity_editor.xml
<Spinner
android:id="@+id/spinner_gender"
android:layout_height="48dp"
android:layout_width="wrap_content"
android:paddingRight="16dp"
android:spinnerMode="dropdown"/>
將 Spinner 的 ID 設(shè)置為 spinner_gender,通過(guò) android:spinnerMode 屬性設(shè)置用戶在點(diǎn)擊 Spinner 時(shí)的展開(kāi)方式是默認(rèn)的下拉菜單 (dropdown) 還是彈出對(duì)話框 (dialog)。
In EditorActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_editor);
...
mGenderSpinner = (Spinner) findViewById(R.id.spinner_gender);
setupSpinner();
}
/**
* Setup the dropdown spinner that allows the user to select the gender of the pet.
*/
private void setupSpinner() {
// Create adapter for spinner. The list options are from the String array it will use
// the spinner will use the default layout
ArrayAdapter genderSpinnerAdapter = ArrayAdapter.createFromResource(this,
R.array.array_gender_options, android.R.layout.simple_spinner_item);
// Specify dropdown layout style - simple list view with 1 item per line
genderSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
// Apply the adapter to the spinner
mGenderSpinner.setAdapter(genderSpinnerAdapter);
// Set the integer mSelected to the constant values
mGenderSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String selection = (String) parent.getItemAtPosition(position);
if (!TextUtils.isEmpty(selection)) {
if (selection.equals(getString(R.string.gender_male))) {
mGender = PetEntry.GENDER_MALE;
} else if (selection.equals(getString(R.string.gender_female))) {
mGender = PetEntry.GENDER_FEMALE;
} else {
mGender = PetEntry.GENDER_UNKNOWN;
}
}
}
// Because AdapterView is an abstract class, onNothingSelected must be defined
@Override
public void onNothingSelected(AdapterView<?> parent) {
mGender = PetEntry.GENDER_UNKNOWN;
}
});
}
- 在
onCreate通過(guò)findViewById找到 Spinner 對(duì)象,并通過(guò)輔助方法setupSpinner()設(shè)置 Spinner 適配器和監(jiān)聽(tīng)器。 - Spinner 適配器設(shè)置為一個(gè) ArrayAdapter,通過(guò)其靜態(tài)方法
createFromResource創(chuàng)建,輸入?yún)?shù)分別為應(yīng)用環(huán)境 (Context)、Array 資源 ID、布局資源 ID。其中,布局資源 ID 使用 Android 提供的默認(rèn)布局simple_spinner_item;Array 資源 ID 則是在應(yīng)用內(nèi)定義的資源。
In res/values/arrays.xml
<resources>
<!-- These are the options displayed in the gender drop-down Spinner -->
<string-array name="array_gender_options">
<item>@string/gender_unknown</item>
<item>@string/gender_male</item>
<item>@string/gender_female</item>
</string-array>
</resources>
創(chuàng)建 Spinner 的 ArrayAdapter 適配器后,調(diào)用
setDropDownViewResource設(shè)置菜單的布局,其中 Android 提供的布局simple_dropdown_item_1line為每行顯示一個(gè)項(xiàng)目的 ListView。Spinner 的
AdapterView.OnItemSelectedListener需要 override 兩個(gè)方法,通過(guò)onItemSelected設(shè)置用戶選擇不同項(xiàng)目時(shí)的對(duì)應(yīng)指令,其中應(yīng)用了TextUtils.isEmpty來(lái)判斷當(dāng)前選項(xiàng)是否為空,增強(qiáng)代碼的魯棒性;另外,通過(guò)onNothingSelected設(shè)置沒(méi)有項(xiàng)目選中時(shí)的指令。
