課程 2: 在 Android 應(yīng)用中使用數(shù)據(jù)庫

這節(jié)課是 Android 開發(fā)(入門)課程 的第四部分《數(shù)據(jù)與數(shù)據(jù)庫》的第二節(jié)課,導(dǎo)師依然是 Jessica Lin 和 Katherine Kuan。這節(jié)課在掌握 SQLite 數(shù)據(jù)庫基本操作的前提下,在 Pets App 的最小可行性應(yīng)用 (MVP, Minimum Viable Product) 中使用 SQLite 數(shù)據(jù)庫,主要參考 這篇 Android Developers 文檔,內(nèi)容包括

  • 使用 Contract 類確定數(shù)據(jù)庫架構(gòu)。
  • 使用 SQLiteOpenHelper 類創(chuàng)建、連接、管理數(shù)據(jù)庫。
  • 插入數(shù)據(jù)時(shí)使用的 ContentValues 類。
  • 讀取數(shù)據(jù)時(shí)使用的 Cursor 對象。

關(guān)鍵詞:Contract Class、SQLiteOpenHelper、SQLiteDatabase 、ContentValues、Cursor、Floating Action Button、Spinner

Contract Class

在 Android 應(yīng)用中使用 SQLite 數(shù)據(jù)庫,首先需要使用 Contract 類確定數(shù)據(jù)庫架構(gòu) (schema)。所謂數(shù)據(jù)庫架構(gòu),可以理解為第一節(jié)課中提到的表格結(jié)構(gòu),主要是每一列的屬性及其對應(yīng)的存儲類和限制信息等。Android 提供了 Contract 類來保存數(shù)據(jù)庫架構(gòu),即保存表格的名稱、每一列的屬性名等。這樣做的好處是,將數(shù)據(jù)庫架構(gòu)及其相關(guān)的常量統(tǒng)一放在一個(gè)類內(nèi),容易訪問和管理;同時(shí)在利用數(shù)據(jù)庫架構(gòu)的常量生成 SQL 指令時(shí),杜絕了錯(cuò)別字的可能性。

例如在 Pets App 中,通過內(nèi)部類 PetEntry 定義了表格的名稱為字符串常量 pets,列 _id 作為數(shù)據(jù)庫表格的 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;   
}
  1. 注意 PetContract 文件放在應(yīng)用包名 (com.example.android.pets) 下單獨(dú)的 data 包內(nèi),與 Activity 文件區(qū)分開。這是因?yàn)?Android 是通過包 (package) 來組織代碼的,一類包存放一類特定功能的代碼,例如 data 包下就存放數(shù)據(jù)相關(guān)的代碼。

  2. 在調(diào)用 PetContract 中 PetEntry 的常量時(shí),需要通過 PetContract.PetEntry 指定,例如

     PetContract.PetEntry.GENDER_MALE
    

    這是因?yàn)檎{(diào)用處導(dǎo)入的包名默認(rèn)為外部類 PetContract

     import com.example.android.pets.data.PetContract;
    

    為了精簡代碼,可以將包名指向 PetContract 的內(nèi)部類

     import com.example.android.pets.data.PetContract.PetEntry;
    

    這樣在調(diào)用其常量時(shí),就可以省略 PetContract

     PetEntry.GENDER_MALE
    

    這種方法適合在 Contract 類只有一個(gè)表格的情況下使用。

  3. PetContract 類要定義為 final 使之不能被擴(kuò)展 (extends),因?yàn)樗粚ν馓峁┏A?,不?shí)現(xiàn)任何功能。

  4. 對于用 INETGER 來存儲固定選項(xiàng)的數(shù)據(jù),其對應(yīng)的規(guī)則常量也在此定義。例如上面的寵物性別信息,0 表示未知,1 表示雄性,2 表示雌性。

SQLiteOpenHelper

在 Contract 類確定數(shù)據(jù)庫架構(gòu)后,使用 Android 提供的 SQLiteOpenHelper 類來創(chuàng)建、連接、管理數(shù)據(jù)庫。SQLiteOpenHelper 可以看成是應(yīng)用與數(shù)據(jù)庫之間的橋梁。

SQLiteOpenHelper 是一個(gè)抽象類,實(shí)現(xiàn)時(shí)需要 override onCreateonUpgrade method。例如在 Pets App 中,創(chuàng)建一個(gè) PetDbHelper 類,擴(kuò)展自 SQLiteOpenHelper。注意文件路徑與 PetContract 的相同,表示 PetDbHelper 類屬于數(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.
    }
}
  1. 首先定義兩個(gè)常量,分別表示數(shù)據(jù)庫的名稱,不要忘記后綴 .db;以及數(shù)據(jù)庫版本,初始值默認(rèn)為 1。
  2. 構(gòu)造函數(shù)調(diào)用超級類,輸入?yún)?shù)分別為
    (1)Context,應(yīng)用環(huán)境,執(zhí)行構(gòu)造函數(shù)時(shí)傳入;
    (2)數(shù)據(jù)庫名稱,傳入在外部類定義的常量;
    (3)CursorFactory,設(shè)為 null 以使用默認(rèn)值;
    (4)數(shù)據(jù)庫版本,傳入在外部類定義的常量。
  3. override onCreate method 添加創(chuàng)建數(shù)據(jù)庫時(shí)的指令,其中調(diào)用了 SQLiteDatabase 的 execSQL method 執(zhí)行上面生成的 CREATE TABLE SQL 指令(存為字符串),實(shí)現(xiàn)創(chuàng)建數(shù)據(jù)庫的操作。值得注意的是,由于 execSQL 無返回值,所以它不能用來執(zhí)行如 SELECT 等帶有返回值的 SQL 指令。
  4. override onUpgrade method 添加數(shù)據(jù)庫版本發(fā)生變化時(shí)的指令,例如 SQLite 數(shù)據(jù)庫表格增加了一列,數(shù)據(jù)庫版本變更,可以在 onUpgrade 使用 execSQL 執(zhí)行 DROP TABLE SQL 指令,刪除舊表格后再調(diào)用 onCreate 創(chuàng)建新數(shù)據(jù)庫。不過在這里,目前數(shù)據(jù)庫版本保持不變,所以 onUpgrade 暫時(shí)留空,這部分內(nèi)容將在后續(xù)課程修改。

實(shí)現(xiàn) SQLiteOpenHelper 這個(gè)抽象類后,就可以通過它來創(chuàng)建、連接、管理 SQLiteDatabase 數(shù)據(jù)庫了。例如在 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ù)庫。

SQLiteDatabase db = mDbHelper.getReadableDatabase();

這條指令相當(dāng)于 sqlite3 的 .open 指令,用于打開或創(chuàng)建一個(gè)數(shù)據(jù)庫文件。具體來說,應(yīng)用在調(diào)用 getReadableDatabase() 時(shí),PetDbHelper 會查詢當(dāng)前是否已存在數(shù)據(jù)庫,若無,PetDbHelper 會執(zhí)行其 onCreate method 創(chuàng)建數(shù)據(jù)庫,返回一個(gè) SQLiteDatabase 對象;若有則不執(zhí)行 onCreate method,直接創(chuàng)建一個(gè) SQLiteDatabase 對象,與已有的數(shù)據(jù)庫連接。因此,無論應(yīng)用是否已存在數(shù)據(jù)庫,調(diào)用 getReadableDatabase() 的結(jié)果都是獲得一個(gè)連接數(shù)據(jù)庫的 SQLiteDatabase 對象,通過它來操作數(shù)據(jù)庫。

不過事實(shí)上,通過 getReadableDatabase() 獲得的 SQLiteDatabase 對象,只能對數(shù)據(jù)庫進(jìn)行讀取 (Read) 操作,如果想要 CRUD 的其余操作,需要調(diào)用 getWritableDatabase() 獲取 SQLiteDatabase 對象。

ContentValues

在獲得一個(gè)連接數(shù)據(jù)庫的 SQLiteDatabase 對象后,就可以通過它對數(shù)據(jù)庫進(jìn)行 CRUD 操作,其中經(jīng)常用到 ContentValues 構(gòu)造數(shù)據(jù)。ContentValues 是一個(gè)具象類,用于存儲大量的鍵/值對,對于數(shù)據(jù)庫而言,鍵是對象的屬性,值是對應(yīng)的屬性值。例如在 Pets App 中,創(chuàng)建一個(gè) ContentValues 對象,并通過 put method 添加鍵/值對。

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);
  1. 添加到 ContentValues 對象的鍵為 Contract 類中定義的架構(gòu)列常量,對應(yīng)的值為從 EditText 獲取的用戶輸入值。
  2. toString 后調(diào)用 trim 去除字符串開頭和結(jié)尾的多余空格。
  3. 調(diào)用 Integer.parseInt() 使字符串轉(zhuǎn)換為整數(shù)。

構(gòu)造好往數(shù)據(jù)庫添加的數(shù)據(jù)后,調(diào)用 SQLiteDatabase 的 insert method 向數(shù)據(jù)庫添加數(shù)據(jù)。輸入?yún)?shù)分別為

  1. table: 要添加數(shù)據(jù)的表格名稱。
  2. nullColumnHack: 可選參數(shù),可設(shè)為 null,僅在往數(shù)據(jù)庫添加空行時(shí)用到。
  3. values: 要往數(shù)據(jù)庫添加的數(shù)據(jù),數(shù)據(jù)類型為 ContentValues 對象。

SQLiteDatabase 的 insert method 返回值為新添加的行 ID,發(fā)生錯(cuò)誤時(shí)返回 -1。

Tips:
1. 調(diào)用 finish() method 可以關(guān)閉當(dāng)前 Activity,使屏幕界面回到原先 Activity。
2. 根據(jù) Activity 的生命周期,在 onStart 中添加的代碼,可以在用戶通過 Activity 切換回來時(shí)執(zhí)行。

Cursor

與往數(shù)據(jù)庫添加數(shù)據(jù)類似,SQLiteDatabase 也提供了讀取數(shù)據(jù)的方法,例如通過 rawQuery 傳入 SQL 指令讀取數(shù)據(jù),不過還有更規(guī)范的 query method,可以避免直接操作 SQL 指令導(dǎo)致的語法錯(cuò)誤。SQLiteDatabase 提供了很多不同的 query method,其中較簡單的為

Cursor query (String table, 
              String[] columns, 
              String selection, 
              String[] selectionArgs, 
              String groupBy, 
              String having, 
              String orderBy)

有七個(gè)輸入?yún)?shù),分別為

  1. table
    想要讀取的表格名稱。
  2. columns
    想要讀取的列數(shù)據(jù),傳入 null 表示讀取所有列,但是不建議這么做,尤其是數(shù)據(jù)庫很龐大時(shí)會消耗大量系統(tǒng)資源。
  3. selection
    讀取數(shù)據(jù)的篩選條件,相當(dāng)于 SQL 指令的 WHERE 條件,傳入 null 表示無篩選條件,返回所有數(shù)據(jù)行。
  4. selectionArgs
    與 selection 配合使用,當(dāng) selection 包含 "?" 時(shí),selectionArgs 數(shù)組的字符串會填入相應(yīng)的位置,作為讀取數(shù)據(jù)的篩選條件。這種模式屬于防止 SQL 注入的安全措施。
  5. groupBy
    讀取的數(shù)據(jù)行的分組條件,相當(dāng)于 SQL 指令的 GROUP BY 條件,傳入 null 表示數(shù)據(jù)行不分組。
  6. having
    在讀取的數(shù)據(jù)行通過 groupBy 分組的情況下,指定哪一組包含 Cursor 內(nèi),相當(dāng)于 SQL 指令的 HAVING 條件,傳入 null 表示所有組包含 Cursor 內(nèi)。另外,當(dāng) groupBy 為 null,having 也要傳入 null。
  7. 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è)等號,也可以是兩個(gè)等號。

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 對象,它保存了讀取到的數(shù)據(jù)庫的多行內(nèi)容。在讀取數(shù)據(jù)時(shí),Cursor 可以看成是應(yīng)用與 SQLiteDatabase 之間的橋梁。

Cursor 的一個(gè)重要概念是當(dāng)前行的位置。在 Cursor 沒有數(shù)據(jù)時(shí),它的默認(rèn)位置為無效的 -1,所以與 Array 類似,Cursor 的首個(gè)可用位置為 0,隨后逐步遞增。Cursor 提供了 move method 用于移動當(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)前行的位置返回到原來那一行

Cursor 的 move method 的返回值類型都是布爾類型,移動成功為 true,失敗則 false。例如 Cursor 當(dāng)前行為最后一行時(shí)調(diào)用 moveToNext 的返回值即 false。

Cursor 還提供很多 getter method 用于獲取不同類型的數(shù)據(jù),例如 getInt()、getString、getType 等,它們的輸入?yún)?shù)都是 columnIndex 列索引,這是由 projection 決定的,通過 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();
}
  1. 使用 append 為 TextView 添加內(nèi)容。
  2. 使用 getColumnIndex 獲取每一列的索引。
  3. moveToNext 放入 while 循環(huán)語句中達(dá)到遍歷所有數(shù)據(jù)行的效果。這是因?yàn)檎G闆r下,moveToNext 的返回值為 true 即進(jìn)入循環(huán);直到 Cursor 在最后一行時(shí)調(diào)用 moveToNext 的返回值為 false 即跳出循環(huán)。
  4. 通過不同的 getter method 獲取不同類型的數(shù)據(jù)。
  5. 將上述代碼放入 try/finally 區(qū)塊內(nèi),即使應(yīng)用崩潰,也能保證執(zhí)行 close method 關(guān)閉 Cursor。

如何提取應(yīng)用的數(shù)據(jù)庫文件

在 Android Studio 界面右上角搜索 Device File Explorer 打開模擬器的文件瀏覽器,打開目錄 data > data > com.example.android.pets > databases 即可查看應(yīng)用內(nèi)的數(shù)據(jù)庫文件。右鍵選擇 "Save As..." 將數(shù)據(jù)庫文件提取到電腦中,即可通過終端訪問。

Note:
1. 此方法僅適用于模擬器或具有最高權(quán)限 (rooted) 的物理設(shè)備。
2. 清除應(yīng)用的緩存 (cache) 和數(shù)據(jù) (data) 會刪除應(yīng)用的數(shù)據(jù)庫文件。

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 的子類,所以其顯示圖標(biāo)可以通過 android:src 屬性設(shè)置;其余特性與 Button 類似,例如在 Pets App 中,設(shè)置其 OnClickListener 動作為打開 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)下會顯示默認(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,通過 android:spinnerMode 屬性設(shè)置用戶在點(diǎn)擊 Spinner 時(shí)的展開方式是默認(rèn)的下拉菜單 (dropdown) 還是彈出對話框 (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;
        }
    });
}
  1. onCreate 通過 findViewById 找到 Spinner 對象,并通過輔助方法 setupSpinner() 設(shè)置 Spinner 適配器和監(jiān)聽器。
  2. Spinner 適配器設(shè)置為一個(gè) ArrayAdapter,通過其靜態(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>
  1. 創(chuàng)建 Spinner 的 ArrayAdapter 適配器后,調(diào)用 setDropDownViewResource 設(shè)置菜單的布局,其中 Android 提供的布局 simple_dropdown_item_1line 為每行顯示一個(gè)項(xiàng)目的 ListView。

  2. Spinner 的 AdapterView.OnItemSelectedListener 需要 override 兩個(gè)方法,通過 onItemSelected 設(shè)置用戶選擇不同項(xiàng)目時(shí)的對應(yīng)指令,其中應(yīng)用了 TextUtils.isEmpty 來判斷當(dāng)前選項(xiàng)是否為空,增強(qiáng)代碼的魯棒性;另外,通過 onNothingSelected 設(shè)置沒有項(xiàng)目選中時(shí)的指令。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容