Android-入门学习笔记-使用 CursorLoader 加载数据
3
使用這個(gè)代碼片段開(kāi)始練習(xí)
也可以參考?Codepath 教程
高級(jí)內(nèi)容補(bǔ)充:
你是否在思考ArrayAdapter’s 的 getView() 方法和CursorAdapter 的 newView() 和 bindView() 方法?
你可以查看?CursorAdapter 類(lèi)的源碼. getView() 方法依然存在, 但是它實(shí)際根據(jù)是否存在列表項(xiàng)能夠被循環(huán)使用,來(lái)決定調(diào)用 newView() 或 bindView(),如果 convertView 為空,那么我們需要?jiǎng)?chuàng)建新的列表項(xiàng),如果 convertView 不為空,那么我們可以循環(huán)使用舊的列表項(xiàng)。
因此,作為一個(gè)開(kāi)發(fā)者,我們不需要重載 CursorAdapter 的 getView() 方法. 我們可以?xún)H僅重載 newView() 和 bindView() 方法, 剩下的工作交給適配器來(lái)完成!
4
空視圖是在 ListView 中無(wú)項(xiàng)目時(shí)展示的視圖。在沒(méi)有數(shù)據(jù)的情況下不是在應(yīng)用中展示一個(gè)空白屏幕,而是通過(guò)有吸引力的圖像或描述性文字來(lái)提升用戶(hù)體驗(yàn)。 文字甚至可以提示用戶(hù)添加一些數(shù)據(jù)。
在我們的 Pets 應(yīng)用中,我們想要在 ListView 中沒(méi)有要顯示的寵物時(shí),設(shè)置以下空視圖。
設(shè)置空視圖非常簡(jiǎn)單。
第 1 步:在 ListView 旁邊創(chuàng)建空視圖
首先在你的布局中創(chuàng)建空視圖。在我們的案例中,應(yīng)該有兩個(gè) TextViews 和一個(gè) ImageView,像這樣對(duì)齊。并給空視圖一個(gè) id“@+id/empty_view”,以便我們之后在 Java 代碼中引用。
<red lines><!-- Empty view for the list --><RelativeLayout android:id="@+id/empty_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true"> <ImageView android:id="@+id/empty_shelter_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:src="@drawable/ic_empty_shelter"/> <TextView android:id="@+id/empty_title_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/empty_shelter_image" android:layout_centerHorizontal="true" android:fontFamily="sans-serif-medium" android:paddingTop="16dp" android:text="@string/empty_view_title_text" android:textAppearance="?android:textAppearanceMedium"/> <TextView android:id="@+id/empty_subtitle_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/empty_title_text" android:layout_centerHorizontal="true" android:fontFamily="sans-serif" android:paddingTop="8dp" android:text="@string/empty_view_subtitle_text" android:textAppearance="?android:textAppearanceSmall" android:textColor="#A2AAB0"/> </RelativeLayout>對(duì)于文本,使用 strings.xml 資源文件。
<!-- Title text for the empty view, which describes the empty dog house image [CHAR LIMIT=50] --><string name="empty_view_title_text">It\'s a bit lonely here...</string> <!-- Subtitle text for the empty view that prompts the user to add a pet [CHAR LIMIT=50] --> <string name="empty_view_subtitle_text">Get started by adding a pet</string>第 2 步:添加空視圖
在創(chuàng)建 ListView 時(shí),你可以使用 id 指定一個(gè)空視圖。使用我們之前設(shè)置的 id,用 findViewById() 方法獲取視圖,然后使用 ListView 的 setEmptyView() 方法設(shè)置空視圖。
// Find the ListView which will be populated with the pet data ListView petListView = (ListView) findViewById(R.id.list);// Find and set empty view on the ListView, so that it only shows when the list has 0 items. View emptyView = findViewById(R.id.empty_view);petListView.setEmptyView(emptyView);第 3 步:測(cè)試
刪除數(shù)據(jù)或卸載并重裝應(yīng)用,使用一個(gè)空數(shù)據(jù)庫(kù)啟動(dòng)。現(xiàn)在,你應(yīng)該會(huì)看到空視圖出現(xiàn)在屏幕上!
練習(xí)完成前后的差異。
6
參閱此教程?或此文檔。
提示:添加需要從接口實(shí)現(xiàn)的方法的鍵盤(pán)快捷鍵:Ctrl + I 提示:將 CursorAdapter 作為全局變量 提示:對(duì)于 onLoadFinished() 和 onLoaderReset() 方法,你可以使用 CursorAdapter 的?swapCursor() 方法更改數(shù)據(jù)源 Cursor。
注:插入新寵物(從虛擬菜單項(xiàng)或編輯器)不會(huì)自動(dòng)更新寵物列表(直到下一個(gè)編碼任務(wù)前)。你需要強(qiáng)制關(guān)閉應(yīng)用并重新打開(kāi)它,才能看到寵物列表更新。
如果你在想為什么 projection 需要包含 _id,請(qǐng)參見(jiàn)CursorAdapter 類(lèi)的描述。CursorAdapter 假設(shè) Cursor 包含一個(gè)名為 _id 的列。
7
目前,我們的 CursorLoader 有一個(gè)小缺陷,那就是它不會(huì)在底層 SQLite 數(shù)據(jù)庫(kù)變化時(shí)進(jìn)行自動(dòng)更新。
為什么列表之前可以更新,而現(xiàn)在不行?我們返回去看看舊的代碼。
在之前,displayDatabaseInfo 在 onStart() 內(nèi)及在菜單項(xiàng)被單擊時(shí)調(diào)用。這可以完全確保數(shù)據(jù)始終為最新?tīng)顟B(tài),但它也會(huì)在主線程上多次進(jìn)行不必要的數(shù)據(jù)加載。
在主線程上進(jìn)行加載會(huì)降低應(yīng)用的速度,如我們之前所講,而且進(jìn)行無(wú)用的加載更是浪費(fèi)資源。
那么,它什么時(shí)候的數(shù)據(jù)加載是無(wú)用的呢?是這樣,每次當(dāng)應(yīng)用被旋轉(zhuǎn)或你導(dǎo)航到別處一會(huì)兒并返回后 onStart 就會(huì)被觸發(fā)。 如果你旋轉(zhuǎn)應(yīng)用或?qū)Ш降絼e處并返回后,你真的需要再次從數(shù)據(jù)庫(kù)獲取數(shù)據(jù)嗎?數(shù)據(jù)庫(kù)中有任何東西發(fā)生變化了嗎?答案是沒(méi)有,數(shù)據(jù)庫(kù)未發(fā)生任何變化,所以你并不需要重新加載數(shù)據(jù)。
這實(shí)際上就是 CursorLoader 主要用于解決的問(wèn)題之一:我們想追蹤數(shù)據(jù)是否已加載,且如果已加載,則避免重新加載,除非有必要。
很顯然我們不需要在每次調(diào)用 onStart 時(shí)重新加載方法。那么我們何時(shí)需要重新加載數(shù)據(jù)呢?
解答
我們僅需要在 SQLite 數(shù)據(jù)庫(kù)中的某些數(shù)據(jù)發(fā)生變化時(shí)更新Cursor。
用戶(hù)關(guān)閉和重新打開(kāi)應(yīng)用、旋轉(zhuǎn)應(yīng)用和滾動(dòng) ListView 實(shí)際上不會(huì)更改數(shù)據(jù)庫(kù)中的數(shù)據(jù),我們這時(shí)就不需要重新加載數(shù)據(jù)。
8
這個(gè)?教程?展示了何時(shí)調(diào)用notifyChange()
Cursor?setNotificationUri()?方法
?
9
注意:在這里,編輯器尚不會(huì)顯示數(shù)據(jù)庫(kù)中特定寵物的數(shù)據(jù)。這將在之后的步驟中實(shí)現(xiàn)。
提示 1:通過(guò)?setOnItemClickListener()?方法將 OnItemClickListener 設(shè)置到 ListView。
提示 2:創(chuàng)建特定寵物內(nèi)容 URI,使用?Uri.withAppendedId()?方法。
提示 3:在 AndroidManifest.xml 中,你可以刪除 EditorActivity 活動(dòng)元素中的?label?屬性,因?yàn)閼?yīng)用欄中新寵物和現(xiàn)有寵物情況的活動(dòng)標(biāo)題被在 Java 代碼中通過(guò)編程方式覆寫(xiě)了。
10
好的,現(xiàn)在我們的 Editor Activity 中有了寵物的 URI。在此情況下,我們還應(yīng)從內(nèi)容提供程序 中加載寵物數(shù)據(jù)。我們可以使用 CursorLoader 進(jìn)行加載。
這與我們上次使用 CursorLoader 的區(qū)別這次我們不是獲取Cursor并將其放入 CursorAdapter,而是獲取Cursor中的所有項(xiàng)然后使用它們來(lái)填充 EditText 字段。
我們使用的步驟大體和之前一樣,只是當(dāng)我們創(chuàng)建加載器時(shí),URI 將為單個(gè)寵物的,而非全部寵物的。
然后我們從 onLoadFinished 中獲得Cursor,這時(shí)候我們不是使用 Cursor Adapter,而是更新所有輸入,即包含寵物值的 editTexts 和性別 spinner。
最后在 onLoaderReset 方法中,我們應(yīng)清除輸入字段。
現(xiàn)在花一些時(shí)間來(lái)實(shí)際操作吧。
你的步驟:
提示 1:當(dāng)你從加載器收到Cursor結(jié)果,記得在開(kāi)始從中提取列值前,將Cursor移到位置 0。
提示 2:你可以使用?Spinner?的?setSelection()?方法來(lái)設(shè)置下拉菜單 Spinner。
解答
首先實(shí)現(xiàn)加載器回調(diào),然后使用 Ctrl + I 熱鍵來(lái)獲取所有加載器回調(diào)方法:
public class EditorActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {接下來(lái),使用此代碼初始化加載器:
getLoaderManager().initLoader(EXISTING_PET_LOADER, null, this);在 onCreateLoader() 中,我將創(chuàng)建一個(gè)新的 CursorLoader,傳入 uri 和 projection。我需要從 onCreate() 獲取 uri,所以我需要將它放在一個(gè)名為?mCurrentPetUri?的實(shí)例變量:
/** Content URI for the existing pet (null if it's a new pet) */ private Uri mCurrentPetUri;...@Override public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { // Since the editor shows all pet attributes, define a projection that contains // all columns from the pet table String[] projection = { PetEntry._ID, PetEntry.COLUMN_PET_NAME, PetEntry.COLUMN_PET_BREED, PetEntry.COLUMN_PET_GENDER, PetEntry.COLUMN_PET_WEIGHT }; // This loader will execute the ContentProvider's query method on a background thread return new CursorLoader(this, // Parent activity context mCurrentPetUri, // Query the content URI for the current pet projection, // Columns to include in the resulting Cursor null, // No selection clause null, // No selection arguments null); // Default sort order }當(dāng)寵物的數(shù)據(jù)加載到游標(biāo)后,onLoadFinished() 將被調(diào)用。在這里,我首先要將游標(biāo)移到第一個(gè)項(xiàng)的位置。盡快它只有一個(gè)項(xiàng),并從位置 -1 開(kāi)始。
// Proceed with moving to the first row of the cursor and reading data from it// (This should be the only row in the cursor)if (cursor.moveToFirst()) {然后,我將獲得每個(gè)數(shù)據(jù)項(xiàng)的索引,然后使用所有和 get() 方法抓取實(shí)際整數(shù)和字符串,以從游標(biāo)中獲得數(shù)據(jù)。
if (cursor.moveToFirst()) {// Find the columns of pet attributes that we're interested inint 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); // Extract out the value from the Cursor for the given column index String name = cursor.getString(nameColumnIndex); String breed = cursor.getString(breedColumnIndex); int gender = cursor.getInt(genderColumnIndex); int weight = cursor.getInt(weightColumnIndex);對(duì)于每一個(gè) textView,我將設(shè)置適當(dāng)?shù)奈谋尽?/p> // Update the views on the screen with the values from the database mNameEditText.setText(name); mBreedEditText.setText(breed); mWeightEditText.setText(Integer.toString(weight));
對(duì)于 Spinner,我將使用一個(gè) switch 語(yǔ)句來(lái)使用 setSelection 方法設(shè)置它的狀態(tài)。
// Gender is a dropdown spinner, so map the constant value from the database// into one of the dropdown options (0 is Unknown, 1 is Male, 2 is Female).// Then call setSelection() so that option is displayed on screen as the current selection.switch (gender) {case PetEntry.GENDER_MALE: mGenderSpinner.setSelection(1); break; case PetEntry.GENDER_FEMALE: mGenderSpinner.setSelection(2); break; default: mGenderSpinner.setSelection(0); break; }練習(xí)完成前后的差異
11
我們的“編輯寵物”(Edit Pet) 版本頁(yè)面缺失一項(xiàng)重大功能,那就是實(shí)際編輯寵物。
目前,當(dāng)你更改現(xiàn)有寵物的任何值時(shí),并不會(huì)更新寵物,而是會(huì)保存寵物的一個(gè)副本,這并非我們想要的。
解決方法就是使“編輯寵物”模式更新當(dāng)前寵物,使“添加寵物”模式插入新寵物。
這次我們不使用“insertPet”方法,我們將它改名或“重構(gòu)”為“savePet”。在這里,就像我們之前一樣,你可以判斷 EditorActivity 是“插入”模式還是“編輯”模式。如果為編輯模式,你需要使用內(nèi)容提供程序 執(zhí)行一個(gè)“更新”操作。
好的,現(xiàn)在來(lái)試一試吧,更新 savePet 方法。完成后,EditActivity 應(yīng)該不會(huì)創(chuàng)建寵物的副本,而是更新現(xiàn)有寵物。在你按下 FAB 按鈕來(lái)插入新寵物時(shí),它也應(yīng)正確執(zhí)行。在這兩種情況下,你的應(yīng)用都應(yīng)顯示 toast 消息。
提示:如何判斷我們是在“插入新寵物”還是“編輯現(xiàn)有寵物”?你可以在設(shè)置 EditorActivity 標(biāo)題時(shí)執(zhí)行此檢查。
注意:確保在 strings.xml 文件中聲明 toast 消息的字符串。
ContentResolver update() 示例。
練習(xí)完成前后的差異.
無(wú)論你是第一次更新寵物還是創(chuàng)建寵物,你都需要使用 content values 對(duì)象。創(chuàng)建它的方式還是跟之前一樣。創(chuàng)建好后,你需要根據(jù)是要編輯現(xiàn)有寵物還是插入新寵物來(lái)進(jìn)行不同操作。
你可以使用設(shè)置標(biāo)題時(shí)所用的方法,通過(guò)檢查 mCurrentPetUri 是否為空值來(lái)區(qū)分編輯模式和插入模式。如果為空值,那么就是新寵物,你可以運(yùn)行此方法中原有的代碼。
if (mCurrentPetUri == null) {如果 uri 不為空值,這意味著你在更新現(xiàn)有寵物。你可以使用 ContentResolver 的 update 方法來(lái)更新寵物。你更新的內(nèi)容的 URI 為 mCurrentPetUri,這會(huì)簡(jiǎn)化代碼編寫(xiě)。
} else {// Otherwise this is an EXISTING pet, so update the pet with content URI: mCurrentPetUri// and pass in the new ContentValues. Pass in null for the selection and selection args// because mCurrentPetUri will already identify the correct row in the database that// we want to modify. int rowsAffected = getContentResolver().update(mCurrentPetUri, values, null, null);現(xiàn)在,它會(huì)返回已更新的列數(shù)。要顯示更新是否成功,我們可以檢查已更新的列數(shù)。若為 0,那么更新不成功,則我們可以顯示一條錯(cuò)誤 toast 消息。若為相反情況,則顯示成功 toast 消息。
// Show a toast message depending on whether or not the update was successful.if (rowsAffected == 0) {// If no rows were affected, then there was an error with the update.Toast.makeText(this, getString(R.string.editor_update_pet_failed), Toast.LENGTH_SHORT).show(); } else { // Otherwise, the update was successful and we can display a toast. Toast.makeText(this, getString(R.string.editor_update_pet_successful), Toast.LENGTH_SHORT).show(); }12
干得不錯(cuò)!
EditorActivity 在編輯模式下的主要功能已完成。
后續(xù)操作
我們依然需要實(shí)現(xiàn) delete 和解決幾個(gè) bug。例如,當(dāng)你為插入新寵物而打開(kāi) EditorActivity 時(shí),你在沒(méi)有輸入任何東西時(shí)不小心點(diǎn)了保存按鈕……這時(shí)候就會(huì)發(fā)生崩潰。
閱讀錯(cuò)誤消息,我們得知這是因?yàn)槟銢](méi)有為體重輸入數(shù)字;基本上是因?yàn)樗鼰o(wú)法將空白值轉(zhuǎn)換為數(shù)字。
通常情況下,我們應(yīng)在 UI 中要求用戶(hù)在嘗試將寵物信息存入數(shù)據(jù)庫(kù)之前先輸入一些寵物信息。而所有空白值應(yīng)視為用戶(hù)不小心按下保存鍵,而非作為新寵物進(jìn)行保存。
你的任務(wù)
你的任務(wù)就是解決這個(gè) bug。為此,首先你得檢查輸入是否為空字符串。你可以使用?TextUtils.isEmpty()) 方法來(lái)檢查一個(gè)字符串是否全由空格或空字符串構(gòu)成。如果所有的輸入都為空字符串,且根據(jù) GENDER UNKNOWN 消息性別未更新,則 savePet 方法會(huì)直接返回但不包含任何內(nèi)容。
此外,當(dāng)你在保存新寵物時(shí)沒(méi)有提供體重,則寵物的給定體重將為 0,而不會(huì)導(dǎo)致應(yīng)用崩潰。
提示 1:調(diào)用?finish()?方法來(lái)終止 activity。
提示 2:如果輸入字符串為空值或空字符串,TextUtils.isEmpty(String s)?返回 true。
解答
我們要做的所有更改都在 EditorActivity.java 文件中進(jìn)行,特別是在 savePet() 方法中。
要解決第一個(gè)問(wèn)題,在我們獲得所有值后但在創(chuàng)建 ContentValues 對(duì)象之前,我將添加一個(gè) if 語(yǔ)句來(lái)檢查它們是否為空字符串及性別是否為未知。如果是,我將不再執(zhí)行剩余的方法或插入寵物,而是直接返回。
if (mCurrentPetUri == null &&TextUtils.isEmpty(nameString) && TextUtils.isEmpty(breedString) &&TextUtils.isEmpty(weightString) && mGender == PetEntry.GENDER_UNKNOWN) {return;}在將體重放入 content value 對(duì)象之前,我要解決的下一個(gè)問(wèn)題是:如果用戶(hù)未指定寵物體重,應(yīng)將其設(shè)為 0,而不是導(dǎo)致崩潰。所以在代碼中,我們將體重的默認(rèn)值設(shè)為 0,然后在用戶(hù)輸入體重后,我會(huì)將其更改為輸入的整數(shù)值。
// If the weight is not provided by the user, don't try to parse the string into an// integer value. Use 0 by default.int weight = 0;if (!TextUtils.isEmpty(weightString)) { weight = Integer.parseInt(weightString); } values.put(PetEntry.COLUMN_PET_WEIGHT, weight);做了這些更改后,應(yīng)用便不會(huì)崩潰,也不會(huì)保存完全空白的寵物信息。非常棒!
練習(xí)完成前后的差異
13
警告用戶(hù)
你可能注意到了,按下 EditorActivity 的“上一個(gè)”(UP) 或“返回”(back) 按鈕會(huì)使你離開(kāi) EditorActivity,但不會(huì)保存你的更改。你想要明確保存數(shù)據(jù)庫(kù)更改的方法,那么我們只在用戶(hù)點(diǎn)擊“保存”(Save) 按鈕的時(shí)候保存更改。
那么,當(dāng)用戶(hù)在添加一些編輯時(shí)不小心按下“上一個(gè)”(UP) 或“返回”(back) 按鈕時(shí)會(huì)怎樣呢?為避免用戶(hù)丟失工作,我們可以跳出一個(gè)對(duì)話(huà)框,警告用戶(hù)其在離開(kāi)編輯器時(shí)有未保存的更改。
下面是聯(lián)系人應(yīng)用中的警告演示。當(dāng)你輸入新的聯(lián)系人信息,然后在未保存的情況下點(diǎn)擊了“上一個(gè)”(UP) 按鈕時(shí),你會(huì)看到此行為:
期望的行為
在我們的應(yīng)用中,我們所期望的行為是跳出一個(gè)對(duì)話(huà)框,用消息“是否放棄更改并退出編輯”(Discard your changes and quit editing?) 來(lái)警告用戶(hù)。用戶(hù)可以選擇“繼續(xù)編輯”(Keep Editing)(即留在此活動(dòng))或“放棄”(Discard)(即結(jié)束活動(dòng))。
步驟
第 1 步:監(jiān)聽(tīng)是否進(jìn)行了更改
第一步是決定什么時(shí)候顯示“放棄更改”對(duì)話(huà)框。這應(yīng)僅在用戶(hù)更改了表格的某一個(gè)部分時(shí)發(fā)生。你可以做的是創(chuàng)建一個(gè)名為 mPetHasChanged 的 boolean,如果用戶(hù)更新了 pet 表的任何部分則為 true。
private boolean mPetHasChanged = false;你可以添加?OnTouchListener?來(lái)檢查是否發(fā)生了更改:
private View.OnTouchListener mTouchListener = new View.OnTouchListener() {@Overridepublic boolean onTouch(View view, MotionEvent motionEvent) { mPetHasChanged = true; return false; } };在?onCreate?中:
mNameEditText.setOnTouchListener(mTouchListener);mBreedEditText.setOnTouchListener(mTouchListener); mWeightEditText.setOnTouchListener(mTouchListener); mGenderSpinner.setOnTouchListener(mTouchListener);第 2 步:編寫(xiě)創(chuàng)建“放棄更改”對(duì)話(huà)框的方法
我們?cè)谙旅婢帉?xiě)一個(gè)創(chuàng)建此對(duì)話(huà)框的方法:
private void showUnsavedChangesDialog(DialogInterface.OnClickListener discardButtonClickListener) {// Create an AlertDialog.Builder and set the message, and click listeners// for the positive and negative buttons on the dialog.AlertDialog.Builder builder = new AlertDialog.Builder(this);builder.setMessage(R.string.unsaved_changes_dialog_msg);builder.setPositiveButton(R.string.discard, discardButtonClickListener);builder.setNegativeButton(R.string.keep_editing, new DialogInterface.OnClickListener() {public void onClick(DialogInterface dialog, int id) {// User clicked the "Keep editing" button, so dismiss the dialog// and continue editing the pet.if (dialog != null) {dialog.dismiss();}}});// Create and show the AlertDialogAlertDialog alertDialog = builder.create();alertDialog.show(); }此代碼使用?AlertDialogBuilder?創(chuàng)建了一個(gè) AlertDialog。此方法接受放棄按鈕的 OnClickListener。我們這樣做是因?yàn)辄c(diǎn)擊“返回”(back) 或“上一個(gè)”(up) 的行為略有不同。
并且注意,我們還使用了很多 R.string,你可以添加這些:
Discard your changes and quit editing?DiscardKeep Editing第 3 步:連接“返回”(Back)按鈕
這是“返回”按鈕的代碼。你需要覆蓋此活動(dòng)的正常返回按鈕。如果寵物發(fā)生了變化,你建立會(huì)關(guān)閉當(dāng)前活動(dòng)的放棄點(diǎn)擊偵聽(tīng)器。然后將此偵聽(tīng)器傳入剛創(chuàng)建的?showUnsavedChangesDialog?方法。
@Override public void onBackPressed() {// If the pet hasn't changed, continue with handling back button pressif (!mPetHasChanged) {super.onBackPressed();return;}// Otherwise if there are unsaved changes, setup a dialog to warn the user.// Create a click listener to handle the user confirming that changes should be discarded.DialogInterface.OnClickListener discardButtonClickListener =new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialogInterface, int i) {// User clicked "Discard" button, close the current activity.finish();}};// Show dialog that there are unsaved changesshowUnsavedChangesDialog(discardButtonClickListener); }第 4 步:連接“上一個(gè)”(Up)按鈕
這里是“上一個(gè)”按鈕的代碼。它在?onOptionsItemSelected?方法中。如果寵物發(fā)生變化,你建立一個(gè)會(huì)導(dǎo)航至“上一個(gè)”的放棄點(diǎn)擊偵聽(tīng)器。然后將此偵聽(tīng)器傳入剛創(chuàng)建的?showUnsavedChangesDialog?方法。
case android.R.id.home:// If the pet hasn't changed, continue with navigating up to parent activity// which is the {@link CatalogActivity}.if (!mPetHasChanged) {NavUtils.navigateUpFromSameTask(EditorActivity.this);return true;}// Otherwise if there are unsaved changes, setup a dialog to warn the user.// Create a click listener to handle the user confirming that// changes should be discarded.DialogInterface.OnClickListener discardButtonClickListener =new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialogInterface, int i) {// User clicked "Discard" button, navigate to parent activity.NavUtils.navigateUpFromSameTask(EditorActivity.this);}};// Show a dialog that notifies the user they have unsaved changesshowUnsavedChangesDialog(discardButtonClickListener);return true;練習(xí)完成前后的差異
有用鏈接:
要向“返回”(back)按鈕被點(diǎn)擊的情況添加行為,參閱此 StackOverflow 帖子。
要向“上一個(gè)”(Up)按鈕被點(diǎn)擊的情況添加行為,參閱此文章。你還需要向 android.R.id.home 按鈕被點(diǎn)擊的情況添加代碼。
如何創(chuàng)建 AlertDialog。
14
如果寵物還不存在,則無(wú)法刪除它
我們當(dāng)前的代碼還有一個(gè)“問(wèn)題”,那就是當(dāng)你處于 EditorActivity 的插入模式時(shí),會(huì)在菜單中看到一個(gè)“刪除”(Delete) 選項(xiàng)。由于你當(dāng)前在插入寵物,因此沒(méi)有寵物可供刪除,那么這個(gè)選項(xiàng)是多余的。
我們來(lái)解決這個(gè)問(wèn)題。我們要?jiǎng)h除“插入模式”中的刪除選項(xiàng)。
如何選擇運(yùn)行應(yīng)用時(shí)的菜單
當(dāng)你第一次打開(kāi)活動(dòng)時(shí),會(huì)調(diào)用?onCreateOptionsMenu?方法。它會(huì)用所有可見(jiàn)的菜單項(xiàng)填充菜單。要更改它,你需要做的是在確定它是一個(gè)新寵物后(mPetContentUri 為空值),你要使選項(xiàng)菜單無(wú)效。
所以在 onCreate 中……
if (mCurrentPetUri == null) {// This is a new pet, so change the app bar to say "Add a Pet"setTitle(getString(R.string.editor_activity_title_new_pet));// Invalidate the options menu, so the "Delete" menu option can be hidden.// (It doesn't make sense to delete a pet that hasn't been created yet.) invalidateOptionsMenu(); } else { //other stuff }然后,onPrepareOptionsMenu?會(huì)被調(diào)用,你可以在它是新寵物時(shí)隱藏刪除菜單選項(xiàng)來(lái)修改菜單對(duì)象。 此操作的代碼如下:
@Overridepublic boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); // If this is a new pet, hide the "Delete" menu item. if (mCurrentPetUri == null) { MenuItem menuItem = menu.findItem(R.id.action_delete); menuItem.setVisible(false); } return true; }練習(xí)完成前后的差異
有用鏈接:
Android開(kāi)發(fā)文檔:菜單選項(xiàng)
Stack Overflow 提問(wèn):如何改變菜單項(xiàng)
15
我們的最后一項(xiàng)重大任務(wù)是實(shí)現(xiàn)刪除寵物的能力。此功能在應(yīng)用的兩個(gè)地方:第一個(gè)是在 EditorActivity 頁(yè)面的菜單中,當(dāng)你更新寵物時(shí)會(huì)用到。 第二個(gè)是在 CatalogActivity 的菜單中,在此處,刪除 (delete) 選項(xiàng)可用于刪除所有寵物,你在測(cè)試數(shù)據(jù)庫(kù)時(shí)可以使用。
首先我們來(lái)看 EditorActivity 中的 delete 方法。我們已添加了使它僅在“編輯寵物”模式出現(xiàn)的代碼。現(xiàn)在我想要實(shí)現(xiàn)的功能是,當(dāng)我們選擇刪除寵物時(shí),出現(xiàn)一個(gè)對(duì)話(huà)框, 即按下刪除按鈕應(yīng)出現(xiàn)一條 toast 消息“已刪除寵物”(Pet deleted) ,并從數(shù)據(jù)庫(kù)中刪除寵物。
我們復(fù)制此代碼片段。它包含 tring.xml 的一些新字符串,也包含生成對(duì)話(huà)框的代碼,位于一個(gè)名為“showDeleteConfirmationDialog”的方法和一個(gè)空的“deletePet”方法中。
此對(duì)話(huà)框有兩個(gè) onClick 偵聽(tīng)器,一個(gè)用于這里顯示的“刪除”(delete) 按鈕,另一個(gè)用于這里顯示的“取消”(cancel) 按鈕。Delete 對(duì)話(huà)框調(diào)用空的“deletePet”方法,你需要在這個(gè)方法中添加代碼來(lái)實(shí)際刪除當(dāng)前寵物。
你的任務(wù):
首先,找到代碼中正確的位置,以當(dāng)你從下拉菜單中選擇“刪除寵物”(Delete Pet) 時(shí),它會(huì)打開(kāi) DeleteConfirmationDialog。
接下來(lái),填上deletePet 方法以刪除當(dāng)前寵物。你應(yīng)僅在“編輯模式”中刪除寵物。你還應(yīng)顯示一條 toast 消息,說(shuō)明是否刪除了寵物,方法和我們之前用 toast 消息表明是否成功更新了寵物一樣。
ContentResolver delete() 示例
解答
在復(fù)制代碼片段后,首先我們要通過(guò)在按下刪除按鈕時(shí)調(diào)用 showDeleteConfirmationDialog 方法來(lái)確保觸發(fā)它。方法是前往 onOptionsItemSelected 并在 action.delete 下添加對(duì) showDeleteConfirmationDialog 的調(diào)用。
// Respond to a click on the "Delete" menu optioncase R.id.action_delete:// Pop up confirmation dialog for deletionshowDeleteConfirmationDialog();showDeleteConfirmationDialog 會(huì)創(chuàng)建一個(gè)對(duì)話(huà)框,在刪除按鈕被按下時(shí)調(diào)用 deletePet。首先,我確保我們是對(duì)現(xiàn)有寵物執(zhí)行刪除。因?yàn)閷?duì)數(shù)據(jù)庫(kù)中尚不存在的新寵物執(zhí)行刪除是沒(méi)有意義的。所以我檢查是否存在現(xiàn)有寵物(mCurrentPetUri 是否等于 null)。然后使用 content resolver 和當(dāng)前的寵物 uri 刪除寵物。
/*** Perform the deletion of the pet in the database.*/ private void deletePet() {// Only perform the delete if this is an existing pet.if (mCurrentPetUri != null) { // Call the ContentResolver to delete the pet at the given content URI. // Pass in null for the selection and selection args because the mCurrentPetUri // content URI already identifies the pet that we want. int rowsDeleted = getContentResolver().delete(mCurrentPetUri, null, null);delete 方法和 update 方法一樣,會(huì)返回被刪除的行數(shù)。我可以通過(guò)檢查被刪除的行數(shù)是否為 0 來(lái)確定刪除成功與否。若為 0,則刪除失敗,我會(huì)顯示一條 toast 消息,說(shuō)明“刪除寵物時(shí)出錯(cuò)”(Error with deleting pet)。 否則,操作將是成功的,且我跳出一條 toast 消息說(shuō)“寵物已刪除”(Pet deleted)。
// Show a toast message depending on whether or not the delete was successful.if (rowsDeleted == 0) {// If no rows were deleted, then there was an error with the delete.Toast.makeText(this, getString(R.string.editor_delete_pet_failed), Toast.LENGTH_SHORT).show(); } else { // Otherwise, the delete was successful and we can display a toast. Toast.makeText(this, getString(R.string.editor_delete_pet_successful), Toast.LENGTH_SHORT).show(); }完成操作后,調(diào)用 finish() 方法關(guān)閉此 activity。
// Close the activityfinish();到此,我們就能成功從 EditorActivity 中刪除寵物了!
練習(xí)完成前后的差異.
16
現(xiàn)在,我要你添加當(dāng)用戶(hù)從 CatalogActivity 上的溢出菜單點(diǎn)擊“刪除所有寵物”(Delete All Pets) 選項(xiàng)時(shí),用于刪除所有寵物的代碼。無(wú)需添加確認(rèn)消息,但是如果你想,也可以自行實(shí)現(xiàn)。
完成后,菜單看起來(lái)應(yīng)該是這樣的:
這里的變更較小,所以我就不多做講解,由你自己來(lái)完成。
Android 中的?菜單選項(xiàng)。
解答
對(duì)于此練習(xí),我們?cè)?CatalogActivity.java 文件中進(jìn)行。
此菜單按鈕與 R.id.action_delete_all_entries 關(guān)聯(lián),所以在 onOptionsMenuSelected 方法的該 case 下,我將添加對(duì)名為“deleteAllPets()”方法的調(diào)用。
case R.id.action_delete_all_entries:deleteAllPets();return true;然后在該方法中,我將使用 ContentResolver 的 delete 方法,并傳入PetEntry.CONTENT_URI。為什么要用內(nèi)容 URI?因?yàn)檫@是一個(gè)一般?__/pets uri,在我們的 內(nèi)容提供程序中將刪除所有寵物。
/*** Helper method to delete all pets in the database.*/ private void deleteAllPets() {int rowsDeleted = getContentResolver().delete(PetEntry.CONTENT_URI, null, null); Log.v("CatalogActivity", rowsDeleted + " rows deleted from pet database"); }此菜單按鈕與R.id.action_delete_all_entries 關(guān)聯(lián),所以在 onOptionsMenuSelected 方法的該 case 下,我將添加對(duì)名為“deleteAllPets()”方法的調(diào)用。
練習(xí)完成前后的差異.
17
對(duì)于我們的最后一項(xiàng)更新,你需要解決 UI 中的另一個(gè)奇怪的行為。當(dāng)品種未知時(shí),寵物列表看起來(lái)總覺(jué)得缺點(diǎn)什么。因?yàn)閷櫸锩戮褪且黄瞻住?/p>
在未提供品種信息的情況下,比起留白,顯示一個(gè)語(yǔ)句“品種未知”(Unknown breed) 會(huì)提供更好的用戶(hù)體驗(yàn)。
完成此步后應(yīng)用看起來(lái)應(yīng)該是這個(gè)樣子的:
注意,這只是 CatalogActivity 中的一個(gè) UI 變化,文本“Unknown breed”不應(yīng)保存在數(shù)據(jù)庫(kù)中。如果你在編輯器中打開(kāi)此寵物,“品種”(breed) 字段應(yīng)該是空白的:
提示:如果輸入字符串為空值或空字符串TextUtils.isEmpty(String s)?將返回 true。
解答
首先,由于我們要添加字符串“Unknown breed”,我們?cè)?strings.xml 文件中保存它。
<!-- Label for the pet's breed if the breed is unknown [CHAR LIMIT=20] --> <string name="unknown_breed">Unknown breed</string>接下來(lái),我們移至 PetCursorAdapter.java 文件。當(dāng)我們?cè)?CatalogActivity 中顯示寵物時(shí),我們要檢查是否有使用 TextUtils.isEmpty 方法的品種。如果沒(méi)有,我們將列表項(xiàng)的 TextView 設(shè)為顯示“Unknown breed”。
In PetCursorAdapter.java:
/*** This method binds the pet data (in the current row pointed to by cursor) to the given* list item layout. For example, the name for the current pet can be set on the name TextView* in the list item layout.** @param view Existing view, returned earlier by newView() method* @param context app context* @param cursor The cursor from which to get the data. The cursor is already moved to the* correct row.*/ @Override public void bindView(View view, Context context, Cursor cursor) { // Find individual views that we want to modify in the list item layout TextView nameTextView = (TextView) view.findViewById(R.id.name); TextView summaryTextView = (TextView) view.findViewById(R.id.summary); // Find the columns of pet attributes that we're interested in int nameColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_NAME); int breedColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_BREED); // Read the pet attributes from the Cursor for the current pet String petName = cursor.getString(nameColumnIndex); String petBreed = cursor.getString(breedColumnIndex); // If the pet breed is empty string or null, then use some default text // that says "Unknown breed", so the TextView isn't blank. if (TextUtils.isEmpty(petBreed)) { petBreed = context.getString(R.string.unknown_breed); } // Update the TextViews with the attributes for the current pet nameTextView.setText(petName); summaryTextView.setText(petBreed); }練習(xí)完成前后的差異.
?
?
?
?
?
?
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/infocodez/p/8449730.html
總結(jié)
以上是生活随笔為你收集整理的Android-入门学习笔记-使用 CursorLoader 加载数据的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Redis服务器启动之后3个警告信息的解
- 下一篇: 梦到猪追着咬我是什么意思