這節課将通過下面的技術為你展示怎樣根據搜尋内容進行比對部分或全部來擷取通訊錄清單:
-
根據姓名:
通過搜尋姓名的部分或全部來擷取通訊清單。
Contacts Provider允許有過個相同的名字,是以這種方法額可以傳回一個比對清單。
-
根據具體類型,例如一個電話号碼:
通過一個具體的資料類型來搜尋擷取通訊清單,像一個郵件位址。比如,這個方法允許你通過搜尋郵件位址來擷取所有比對的通訊錄清單。
-
根據任何類型:
通過任何具體的資料類型進行搜尋來擷取通訊清單,包括姓名,電話号碼,街道位址,郵箱位址等四項。比如。這個方法允許你通過搜尋通訊錄所能比對的任何類型的字元來擷取通訊清單。
記:這節課裡所有的例子都是運用CursorLoader從Contacts Provider來擷取資料。CursorLoader通過一個不同于UI線程的線程進行的。這樣確定查詢不會拖慢UI的響應時間而影響使用者體驗。相應擷取更多資訊,請檢視Android 教育訓練裡的類:背景擷取資料(Loading Data in the Background)。
擷取權限讀取Provider
通過任何類型字元來搜尋contacts Provider,你的App都必須有
READ_CONTACTS
的權限。添加
<uses-permission>
元素作為一個
<mainfest>
子元素到你的清單檔案裡
通過姓名比對通訊錄并展示出來
這個方法是嘗試通過搜尋Contact Provider裡表
ContactsContract.Contacts
通訊錄裡的名字。你通常是通過ListView将結果展示出來讓使用者選擇其中的通訊錄。
定義ListView和條目布局
為了在ListView裡面展示搜尋結果,你需要一個包含ListView的完整UI的main布局檔案和ListView的子條目布局檔案。例如,通過如下的XML建立一個main布局檔案
res/layout/contacts_list_view.xml
:
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
這個XML檔案使用的Android内置ListView控件
android:id/list
.
定義子布局
contacts_list_item.xml
檔案如下:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"/>
這個XML檔案使用Android内置TextView控件
android:text1
.
記:這節課沒有對擷取搜尋字元的UI的描述,因為你可能間接擷取。比如,你可以讓使用者選擇搜尋名字與輸入内容比對的.
這兩個布局檔案定義了UI展示一個ListView。下一步是寫代碼讓UI展示通訊清單。
建立Fragment來顯示通訊清單
為了展示通訊清單,通過建立Activity加載的Fragment開始。使用Fragment是一個更為合适的方法。因為你可以用一個Fragment展示通訊清單,另一個Fragment展示選中的條目詳情頁面。用這個方法,你可以将本課中的介紹的一種技術與
Retrieving Details for a Contact
(擷取聯系人詳情)中的一種技術相結合。
學習怎麼在一個Activ用一個或更多Fragment,請閱讀
Building a Dynamic UI with Fragments
(用Fragment建立動态UI).
為了查詢Contact Provider,Android framework提供了一個contracts類叫做ContractsContract,裡面定義了進入provider的常量和方法。當使用此類時,你不用為content URI定義自己的常量,表名,列。使用此類,包含如下狀态:
由于代碼使用CursorLoader擷取資料,你必須確定實作loader interface(加載接口)
LoaderManager.LoaderCallbacks
。同時,為了檢測使用者在查詢清單中選擇的聯系人,必須實作adapter interface(擴充卡接口)
AdapterView.OnItemClickListener
.
如:
...
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.widget.AdapterView;
...
public class ContactsFragment extends Fragment implements
LoaderManager.LoaderCallbacks<Cursor>,
AdapterView.OnItemClickListener {
定義全局變量
定義在代碼其他地方使用到的全局變量
...
/*
* Defines an array that contains column names to move from
* the Cursor to the ListView.
*/
@SuppressLint("InlinedApi")
private final static String[] FROM_COLUMNS = {
Build.VERSION.SDK_INT
>= Build.VERSION_CODES.HONEYCOMB ?
Contacts.DISPLAY_NAME_PRIMARY :
Contacts.DISPLAY_NAME
};
/*
* Defines an array that contains resource ids for the layout views
* that get the Cursor column contents. The id is pre-defined in
* the Android framework, so it is prefaced with "android.R.id"
*/
private final static int[] TO_IDS = {
android.R.id.text1
};
// Define global mutable variables
// Define a ListView object
ListView mContactsList;
// Define variables for the contact the user selects
// The contact's _ID value
long mContactId;
// The contact's LOOKUP_KEY
String mContactKey;
// A content URI for the selected contact
Uri mContactUri;
// An adapter that binds the result Cursor to the ListView
private SimpleCursorAdapter mCursorAdapter;
...
記:由于需要Android3.0(API版本11)或以上,在Android Studio中設定app的
Contacts.DISPLAY_NAME_PRIMARY
為10或以下來生成一個 Android Lint警告。要關閉警告,在
minSdkVersion
前添加
FROM_COLUMNS
注解即可
@SuppressLint("InlinedApi")
初始化Fragment
初始化Fragment,需要通過Android系統添加一個無參構造方法,并在回調方法onCreateView()中為Fragment填充UI。如:
// Empty public constructor, required by the system
public ContactsFragment() {}
// A UI Fragment must inflate its View
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the fragment layout
return inflater.inflate(R.layout.contact_list_fragment,
container, false);
}
為ListView設定CursorAdapter
設定SimpleCursorAdapter綁定ListView中的搜尋結果,為擷取ListView類展示通訊錄清單,需要用Fragment的父層Activity調用
Activity.findViewById()
,用父層Activity的上下文調用setAdapter()如:
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
...
// Gets the ListView from the View list of the parent activity
mContactsList =
(ListView) getActivity().findViewById(R.layout.contact_list_view);
// Gets a CursorAdapter
mCursorAdapter = new SimpleCursorAdapter(
getActivity(),
R.layout.contact_list_item,
null,
FROM_COLUMNS, TO_IDS,
);
// Sets the adapter for the ListView
mContactsList.setAdapter(mCursorAdapter);
}
設定選中聯系人監聽
當你展示搜尋結果的時候,你通常想要使用者進一步的選擇單個聯系 。例如,當使用者點選一個可以在地圖上展示出來的聯系人的通訊位址的時候。為了提供這個功能,首先要讓目前Fragment實習一個點選監聽
AdapterView.OnItemClickListener
,像在
Define a Fragment that displays the list of contacts
(定義一個顯示通訊清單的Fragment)中寫的那樣。
為了繼續設定監聽,通過在方法onActivityCreated()中調用setOnItemClickListener(),例如:
public void onActivityCreated(Bundle savedInstanceState) {
...
// Set the item click listener to be the current fragment.
mContactsList.setOnItemClickListener(this);
...
}
由于你已經指定目前Fragment中的ListView的OnItemClickListener(條目點選監聽),你現在需要實作用來執行點選事件的方法onItemClick(),這個将在後續部分講解。
定義projection
定義一個常量包含你查詢中所需要的列,每個ListView條目中顯示聯系人的名字。在安卓3.0(API版本11)及以上,列名是
Contacts.DISPLAY_NAME_PRIMARY
,3.0以前的版本列名是
Contacts.DISPLAY_NAME
。
...
@SuppressLint("InlinedApi")
private static final String[] PROJECTION =
{
Contacts._ID,
Contacts.LOOKUP_KEY,
Build.VERSION.SDK_INT
>= Build.VERSION_CODES.HONEYCOMB ?
Contacts.DISPLAY_NAME_PRIMARY :
Contacts.DISPLAY_NAME
};
為Cursor每一列定義一個常量
為了從Cursor中的列中擷取資訊,你需要列在Cursor中的索引。你可以為列的索引定義常量,因為在你的projection中索引順序和列名一樣。如:
// The column index for the _ID column
private static final int CONTACT_ID_INDEX = ;
// The column index for the LOOKUP_KEY column
private static final int LOOKUP_KEY_INDEX = ;
指定選擇條件
為了指定所需要的資訊,建立文本表達式和變量的組合,告訴provider要搜尋的資料列和要查找的值。
對于文本表達式,定義出搜尋列的常量。雖然這個表達式也可以包含值,但是最好的做法是用“?”占位符。在檢索期間,占位符由數組中的值替換。使用“?”作為占位符,確定搜尋規範是通過綁定而不是通過SQL編譯生成的。這種做法消除了SQL惡意注入的可能性。例如:
// Defines the text expression
@SuppressLint("InlinedApi")
private static final String SELECTION =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
Contacts.DISPLAY_NAME_PRIMARY + " LIKE ?" :
Contacts.DISPLAY_NAME + " LIKE ?";
// Defines a variable for the search string
private String mSearchString;
// Defines the array to hold values that replace the ?
private String[] mSelectionArgs = { mSearchString };
定義onItemClick()方法
在上一節中,你為ListView設定項目點選監聽。現在通過定義方法
AdapterView.OnItemClickListener.onItemClick()
來實作監聽器的操作:
@Override
public void onItemClick(
AdapterView<?> parent, View item, int position, long rowID) {
// Get the Cursor
Cursor cursor = parent.getAdapter().getCursor();
// Move to the selected contact
cursor.moveToPosition(position);
// Get the _ID value
mContactId = getLong(CONTACT_ID_INDEX);
// Get the selected LOOKUP KEY
mContactKey = getString(CONTACT_KEY_INDEX);
// Create the contact's content Uri
mContactUri = Contacts.getLookupUri(mContactId, mContactKey);
/*
* You can use mContactUri as the content URI for retrieving
* the details for a contact.
*/
}
初始化loader(加載器)
由于你使用CursorLoader檢索資料,是以必須初始化背景線程和控制異步檢索的其他變量。在onActivityCreated()中進行初始化,它在Fragment UI出現之前立即被調用,如下例所示:
public class ContactsFragment extends Fragment implements
LoaderManager.LoaderCallbacks<Cursor> {
...
// Called just before the Fragment displays its UI
@Override
public void onActivityCreated(Bundle savedInstanceState) {
// Always call the super method first
super.onActivityCreated(savedInstanceState);
...
// Initializes the loader
getLoaderManager().initLoader(, null, this);
實作onCreateLoader()
實作方法onCreateLoader(),它是在調用initLoader()之後立即由loader架構調用的。在onCreateLoader()中,設定搜尋字元串規則。要将字元串轉換為規則,請插入“%”(百分比)字元以表示零個或多個字元的序列,或者使用“_”(下劃線)字元表示單個字元,或兩者都用。例如,模式“%Jefferson%”将比對“Thomas Jefferson”和“Jefferson Davis”。
從方法中傳回一個新的CursorLoader。對于内容URI,使用
Contacts.CONTENT_URI
。此URI引用整個表,如以下示例所示:
...
@Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
/*
* Makes search string into pattern and
* stores it in the selection array
*/
mSelectionArgs[] = "%" + mSearchString + "%";
// Starts the query
return new CursorLoader(
getActivity(),
Contacts.CONTENT_URI,
PROJECTION,
SELECTION,
mSelectionArgs,
null
);
}
實作onLoadFinished() and onLoaderReset()
實作onLoadFinished()方法。當Contacts Provider傳回查詢的結果時,loader架構調用onLoadFinished()。在這種方法中,将結果Cursor放在SimpleCursorAdapter中。這将通過搜尋結果自動更新ListView:
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
// Put the result Cursor in the adapter for the ListView
mCursorAdapter.swapCursor(cursor);
}
當loader架構檢測到結果Cursor包含老的資料時,會調用onLoaderReset()方法。删除引用已有Cursor的SimpleCursorAdapter。如果不這樣做,loader架構将不會回收Cursor,這會導緻記憶體洩漏。例如:
@Override
public void onLoaderReset(Loader<Cursor> loader) {
// Delete the reference to the existing Cursor
mCursorAdapter.swapCursor(null);
}
現在,你獲得了應用程式的關鍵部分,它與搜尋字元串相比對的聯系人姓名,并在ListView中傳回。使用者可以單擊聯系人姓名來選擇它。這将觸發監聽,這樣你可以進一步對聯系人資料進行處理。例如,您可以檢索聯系人的詳細資訊。要了解如何執行此操作,請繼續下一課,檢索聯系人的詳細資訊。要了解有關搜尋使用者界面的更多資訊,請閱讀API Creating a Search Interface(指南建立搜尋界面)。
本課程的其餘部分示範了在Contacts Provider中查找聯系人的其他方法
按特定類型的資料比對聯系人
此技術允許您指定要比對的資料類型。按名稱檢索是此類型查詢的特定示例,但也可以對與聯系人相關的任何類型的詳細資訊資料執行此操作。例如,您可以檢索具有具體郵政編碼的聯系人;在這種情況下,搜尋字元串必須比對存儲在郵政編碼行中的資料。
要實作此類型的檢索,請首先實作以下代碼,如上一節所述:
- 請求讀取Provider權限。
- 定義ListView和條目布局。
- 定義顯示聯系人清單的Fragment。
- 定義全局變量。
- 初始化Fragment。
- 設定ListView的CursorAdapter。
- 設定所選的聯系人監聽。
- 定義Cursor列索引的常量。 雖然您正在從不同的表中檢索資料,但projection中列的順序是相同的,是以您可以為Cursor使用相同的索引。
- 初始化loader。
- 實作onLoadFinished()和onLoaderReset()。
以下步驟顯示了将搜尋字元串與特定類型的詳細資訊資料比對并顯示結果所需的附加代碼。
移除選擇條件
不要定義SELECTION常量或mSelectionArgs變量。這些不用于這種類型的檢索。
實作onCreateLoader()
實作onCreateLoader()方法,傳回一個新的CursorLoader。你不需要将搜尋字元串轉換為所需規則,因為Contacts Provider會自動執行此操作。使用
Contacts.CONTENT_FILTER_URI
作為基本URI,并通過調用
Uri.withAppendedPath()
将你的搜尋字元串添加到它後面。使用此URI會自動觸發搜尋任何資料類型,如以下示例所示:
@Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
/*
* Appends the search string to the base URI. Always
* encode search strings to ensure they're in proper
* format.
*/
Uri contentUri = Uri.withAppendedPath(
Contacts.CONTENT_FILTER_URI,
Uri.encode(mSearchString));
// Starts the query
return new CursorLoader(
getActivity(),
contentUri,
PROJECTION,
null,
null,
null
);
}
這段代碼是對Contacts Provider進行模糊搜尋的基本代碼,該技術對于要實作類似應用程式聯系人清單展示到螢幕上很有用。
注:
本篇文章系本人第一次嘗試翻譯,有很多不足之處,還望見諒。後面會不斷完善。
希望共同學習,共同進步!