Truy xuất danh sách người liên hệ

Bài học này sẽ hướng dẫn bạn cách truy xuất danh sách người liên hệ có dữ liệu khớp với toàn bộ hoặc một phần của chuỗi tìm kiếm bằng các kỹ thuật sau:

Khớp tên người liên hệ
Truy xuất danh sách người liên hệ bằng cách so khớp chuỗi tìm kiếm với toàn bộ hoặc một phần dữ liệu tên người liên hệ. Trình cung cấp danh bạ cho phép nhiều thực thể trùng tên, vì vậy, kỹ thuật này có thể trả về danh sách các kết quả trùng khớp.
So khớp một loại dữ liệu cụ thể, chẳng hạn như số điện thoại
Truy xuất danh sách người liên hệ bằng cách so khớp chuỗi tìm kiếm với một loại dữ liệu chi tiết cụ thể, chẳng hạn như địa chỉ email. Ví dụ: kỹ thuật này cho phép bạn liệt kê tất cả những người liên hệ có địa chỉ email khớp với chuỗi tìm kiếm.
Khớp với mọi loại dữ liệu
Truy xuất danh sách người liên hệ bằng cách so khớp chuỗi tìm kiếm với bất kỳ loại dữ liệu chi tiết nào, bao gồm tên, số điện thoại, địa chỉ đường phố, địa chỉ email, v.v. Ví dụ: kỹ thuật này cho phép bạn chấp nhận mọi loại dữ liệu cho một chuỗi tìm kiếm và sau đó liệt kê những người liên hệ mà dữ liệu khớp với chuỗi đó.

Lưu ý: Tất cả ví dụ trong bài học này đều sử dụng CursorLoader để truy xuất dữ liệu từ Trình cung cấp danh bạ. CursorLoader chạy truy vấn trên một luồng tách biệt với luồng giao diện người dùng. Điều này đảm bảo rằng truy vấn không làm chậm thời gian phản hồi của giao diện người dùng và gây ra trải nghiệm người dùng kém. Để biết thêm thông tin, hãy xem lớp đào tạo về Android Tải dữ liệu ở chế độ nền.

Yêu cầu quyền đọc nhà cung cấp

Để thực hiện thao tác tìm kiếm bất kỳ bằng Trình cung cấp danh bạ, ứng dụng của bạn phải có quyền READ_CONTACTS. Để yêu cầu điều này, hãy thêm phần tử <uses-permission> này vào tệp kê khai dưới dạng phần tử con của <manifest>:

    <uses-permission android:name="android.permission.READ_CONTACTS" />

Ghép một người liên hệ theo tên rồi liệt kê kết quả

Kỹ thuật này cố gắng so khớp một chuỗi tìm kiếm với tên của một người liên hệ hoặc những người liên hệ trong bảng ContactsContract.Contacts của Trình cung cấp danh bạ. Thường thì bạn muốn hiển thị kết quả trong ListView để cho phép người dùng chọn trong số những người liên hệ phù hợp.

Xác định ListView và bố cục thành phần

Để hiển thị kết quả tìm kiếm trong ListView, bạn cần có tệp bố cục chính xác định toàn bộ giao diện người dùng bao gồm cả ListView và tệp bố cục mục xác định một dòng của ListView. Ví dụ: bạn có thể tạo tệp bố cục chính res/layout/contacts_list_view.xml bằng XML sau:

<?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 này sử dụng tiện ích ListView tích hợp sẵn của Android android:id/list.

Xác định tệp bố cục mục contacts_list_item.xml bằng XML sau:

<?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 này sử dụng tiện ích TextView tích hợp sẵn của Android android:text1.

Lưu ý: Bài học này không mô tả giao diện người dùng để nhận chuỗi tìm kiếm từ người dùng vì có thể bạn sẽ muốn lấy chuỗi đó một cách gián tiếp. Ví dụ: bạn có thể cung cấp cho người dùng tuỳ chọn tìm kiếm các liên hệ có tên khớp với một chuỗi trong tin nhắn văn bản đến.

Hai tệp bố cục bạn đã viết xác định một giao diện người dùng hiển thị ListView. Bước tiếp theo là viết mã sử dụng giao diện người dùng này để cho thấy danh sách người liên hệ.

Xác định Mảnh hiển thị danh sách người liên hệ

Để hiển thị danh sách liên hệ, hãy bắt đầu bằng cách xác định Fragment được Activity tải. Sử dụng Fragment là một kỹ thuật linh hoạt hơn vì bạn có thể sử dụng một Fragment để hiển thị danh sách và một Fragment thứ hai để hiển thị thông tin chi tiết về một mục liên hệ mà người dùng chọn trong danh sách. Khi sử dụng phương pháp này, bạn có thể kết hợp một trong các kỹ thuật trình bày trong bài học này với kỹ thuật trong bài học Truy xuất thông tin chi tiết của một người liên hệ.

Để tìm hiểu cách sử dụng một hoặc nhiều đối tượng Fragment từ một Activity, hãy đọc lớp huấn luyện Xây dựng giao diện người dùng động bằng các mảnh.

Để giúp bạn viết truy vấn dựa vào Trình cung cấp danh bạ, khung Android cung cấp một lớp hợp đồng có tên là ContactsContract. Lớp này xác định các hằng số và phương thức hữu ích để truy cập vào trình cung cấp này. Khi sử dụng lớp này, bạn không phải xác định các hằng số riêng của mình cho URI nội dung, tên bảng hoặc cột. Để sử dụng lớp này, hãy thêm câu lệnh sau:

Kotlin

import android.provider.ContactsContract

Java

import android.provider.ContactsContract;

Vì mã này sử dụng CursorLoader để truy xuất dữ liệu từ trình cung cấp, nên bạn phải chỉ định rằng mã này sẽ triển khai giao diện trình tải LoaderManager.LoaderCallbacks. Ngoài ra, để giúp phát hiện người liên hệ mà người dùng chọn trong danh sách kết quả tìm kiếm, hãy triển khai giao diện bộ chuyển đổi AdapterView.OnItemClickListener. Ví dụ:

Kotlin

...
import android.support.v4.app.Fragment
import android.support.v4.app.LoaderManager
import android.widget.AdapterView
...
class ContactsFragment :
        Fragment(),
        LoaderManager.LoaderCallbacks<Cursor>,
        AdapterView.OnItemClickListener {

Java

...
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 {

Xác định biến toàn cục

Xác định các biến toàn cục được dùng trong các phần khác của mã:

Kotlin

...
/*
 * Defines an array that contains column names to move from
 * the Cursor to the ListView.
 */
@SuppressLint("InlinedApi")
private val FROM_COLUMNS: Array<String> = arrayOf(
        if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)) {
            ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
        } else {
            ContactsContract.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 val TO_IDS: IntArray = intArrayOf(android.R.id.text1)
...
class ContactsFragment :
        Fragment(),
        LoaderManager.LoaderCallbacks<Cursor>,
        AdapterView.OnItemClickListener {
    ...
    // Define global mutable variables
    // Define a ListView object
    lateinit var contactsList: ListView
    // Define variables for the contact the user selects
    // The contact's _ID value
    var contactId: Long = 0
    // The contact's LOOKUP_KEY
    var contactKey: String? = null
    // A content URI for the selected contact
    var contactUri: Uri? = null
    // An adapter that binds the result Cursor to the ListView
    private val cursorAdapter: SimpleCursorAdapter? = null

Java

    ...
    /*
     * 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 ?
                    ContactsContract.Contacts.DISPLAY_NAME_PRIMARY :
                    ContactsContract.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 contactsList;
    // Define variables for the contact the user selects
    // The contact's _ID value
    long contactId;
    // The contact's LOOKUP_KEY
    String contactKey;
    // A content URI for the selected contact
    Uri contactUri;
    // An adapter that binds the result Cursor to the ListView
    private SimpleCursorAdapter cursorAdapter;
    ...

Lưu ý:Contacts.DISPLAY_NAME_PRIMARY yêu cầu Android 3.0 (API phiên bản 11) trở lên, nên việc đặt minSdkVersion của ứng dụng thành 10 trở xuống sẽ tạo ra cảnh báo tìm lỗi mã nguồn Android trong Android Studio. Để tắt cảnh báo này, hãy thêm chú giải @SuppressLint("InlinedApi") trước định nghĩa FROM_COLUMNS.

Khởi động Mảnh

Khởi động Fragment. Thêm hàm khởi tạo trống, công khai theo yêu cầu của hệ thống Android và tăng cường giao diện người dùng của đối tượng Fragment trong phương thức gọi lại onCreateView(). Ví dụ:

Kotlin

    // A UI Fragment must inflate its View
    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {
        // Inflate the fragment layout
        return inflater.inflate(R.layout.contact_list_fragment, container, false)
    }

Java

    // 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);
    }

Thiết lập CursorAdapter cho ListView

Thiết lập SimpleCursorAdapter để liên kết kết quả tìm kiếm với ListView. Để lấy đối tượng ListView hiển thị danh bạ, bạn cần gọi Activity.findViewById() bằng hoạt động mẹ của Fragment. Sử dụng Context của hoạt động mẹ khi bạn gọi setAdapter(). Ví dụ:

Kotlin

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        ...
        // Gets the ListView from the View list of the parent activity
        activity?.also {
            contactsList = it.findViewById<ListView>(R.id.contact_list_view)
            // Gets a CursorAdapter
            cursorAdapter = SimpleCursorAdapter(
                    it,
                    R.layout.contact_list_item,
                    null,
                    FROM_COLUMNS, TO_IDS,
                    0
            )
            // Sets the adapter for the ListView
            contactsList.adapter = cursorAdapter
        }
    }

Java

    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        ...
        // Gets the ListView from the View list of the parent activity
        contactsList =
            (ListView) getActivity().findViewById(R.layout.contact_list_view);
        // Gets a CursorAdapter
        cursorAdapter = new SimpleCursorAdapter(
                getActivity(),
                R.layout.contact_list_item,
                null,
                FROM_COLUMNS, TO_IDS,
                0);
        // Sets the adapter for the ListView
        contactsList.setAdapter(cursorAdapter);
    }

Thiết lập trình nghe người liên hệ đã chọn

Khi cho thấy kết quả tìm kiếm, thông thường, bạn muốn cho phép người dùng chọn một mục liên hệ duy nhất để tiếp tục xử lý. Ví dụ: khi người dùng nhấp vào một địa chỉ liên hệ, bạn có thể hiển thị địa chỉ của địa chỉ liên hệ đó trên bản đồ. Để cung cấp tính năng này, trước tiên, bạn đã xác định Fragment hiện tại là trình nghe lượt nhấp bằng cách chỉ định rằng lớp này triển khai AdapterView.OnItemClickListener, như minh hoạ trong phần Xác định một mảnh hiển thị danh sách các mục liên hệ.

Để tiếp tục thiết lập trình nghe, hãy liên kết trình nghe đó với ListView bằng cách gọi phương thức setOnItemClickListener() trong onActivityCreated(). Ví dụ:

Kotlin

    fun onActivityCreated(savedInstanceState:Bundle) {
        ...
        // Set the item click listener to be the current fragment.
        contactsList.onItemClickListener = this
        ...
    }

Java

    public void onActivityCreated(Bundle savedInstanceState) {
        ...
        // Set the item click listener to be the current fragment.
        contactsList.setOnItemClickListener(this);
        ...
    }

Vì bạn đã chỉ định rằng Fragment hiện tại là OnItemClickListener cho ListView, nên giờ đây bạn cần triển khai phương thức bắt buộc onItemClick() giúp xử lý sự kiện nhấp chuột. Điều này được mô tả trong phần tiếp theo.

Xác định phép chiếu

Xác định hằng số chứa các cột bạn muốn trả về từ truy vấn của mình. Mỗi mục trong ListView cho thấy tên hiển thị của mục liên hệ, trong đó chứa dạng chính của tên mục liên hệ. Trong Android 3.0 (API phiên bản 11) trở lên, tên của cột này là Contacts.DISPLAY_NAME_PRIMARY; trong các phiên bản trước đó, tên của cột này là Contacts.DISPLAY_NAME.

Cột Contacts._ID được quá trình liên kết SimpleCursorAdapter sử dụng. Contacts._IDLOOKUP_KEY được dùng cùng nhau để tạo URI nội dung cho mục liên hệ mà người dùng chọn.

Kotlin

...
@SuppressLint("InlinedApi")
private val PROJECTION: Array<out String> = arrayOf(
        ContactsContract.Contacts._ID,
        ContactsContract.Contacts.LOOKUP_KEY,
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
            ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
        else
            ContactsContract.Contacts.DISPLAY_NAME
)

Java

...
@SuppressLint("InlinedApi")
private static final String[] PROJECTION =
        {
            Contacts._ID,
            Contacts.LOOKUP_KEY,
            Build.VERSION.SDK_INT
                    >= Build.VERSION_CODES.HONEYCOMB ?
                    ContactsContract.Contacts.DISPLAY_NAME_PRIMARY :
                    ContactsContract.Contacts.DISPLAY_NAME

        };

Xác định hằng số cho chỉ mục cột Con trỏ

Để lấy dữ liệu từ một cột riêng lẻ trong Cursor, bạn cần có chỉ mục của cột đó trong Cursor. Bạn có thể định nghĩa hằng số cho chỉ mục của các cột Cursor, vì các chỉ mục này giống với thứ tự của tên cột trong phép chiếu. Ví dụ:

Kotlin

// The column index for the _ID column
private const val CONTACT_ID_INDEX: Int = 0
// The column index for the CONTACT_KEY column
private const val CONTACT_KEY_INDEX: Int = 1

Java

// The column index for the _ID column
private static final int CONTACT_ID_INDEX = 0;
// The column index for the CONTACT_KEY column
private static final int CONTACT_KEY_INDEX = 1;

Chỉ định tiêu chí lựa chọn

Để chỉ định dữ liệu bạn muốn, hãy tạo một tổ hợp các biến và biểu thức văn bản để cho nhà cung cấp biết các cột dữ liệu cần tìm kiếm và các giá trị cần tìm.

Đối với biểu thức văn bản, hãy xác định một hằng số liệt kê các cột tìm kiếm. Mặc dù biểu thức này cũng có thể chứa các giá trị, nhưng bạn nên biểu diễn các giá trị bằng phần giữ chỗ "?". Trong quá trình truy xuất, phần giữ chỗ được thay thế bằng các giá trị từ một mảng. Việc sử dụng dấu "?" làm phần giữ chỗ giúp đảm bảo rằng quy cách tìm kiếm được tạo bằng cách liên kết thay vì bằng quá trình biên dịch SQL. Phương pháp này giúp loại bỏ khả năng chèn SQL độc hại. Ví dụ:

Kotlin

// Defines the text expression
@SuppressLint("InlinedApi")
private val SELECTION: String =
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
            "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} LIKE ?"
        else
            "${ContactsContract.Contacts.DISPLAY_NAME} LIKE ?"
...
    // Defines a variable for the search string
    private val searchString: String = ...
    // Defines the array to hold values that replace the ?
    private val selectionArgs = arrayOf<String>(searchString)

Java

    // 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 searchString;
    // Defines the array to hold values that replace the ?
    private String[] selectionArgs = { searchString };

Xác định phương thức onItemClick()

Trong phần trước, bạn đã thiết lập trình nghe lượt nhấp vào mục cho ListView. Bây giờ, hãy triển khai thao tác cho trình nghe bằng cách xác định phương thức AdapterView.OnItemClickListener.onItemClick():

Kotlin

    override fun onItemClick(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
        // Get the Cursor
        val cursor: Cursor? = (parent.adapter as? CursorAdapter)?.cursor?.apply {
            // Move to the selected contact
            moveToPosition(position)
            // Get the _ID value
            contactId = getLong(CONTACT_ID_INDEX)
            // Get the selected LOOKUP KEY
            contactKey = getString(CONTACT_KEY_INDEX)
            // Create the contact's content Uri
            contactUri = ContactsContract.Contacts.getLookupUri(contactId, mContactKey)
            /*
             * You can use contactUri as the content URI for retrieving
             * the details for a contact.
             */
        }
    }

Java

    @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
        contactId = cursor.getLong(CONTACT_ID_INDEX);
        // Get the selected LOOKUP KEY
        contactKey = cursor.getString(CONTACT_KEY_INDEX);
        // Create the contact's content Uri
        contactUri = Contacts.getLookupUri(contactId, mContactKey);
        /*
         * You can use contactUri as the content URI for retrieving
         * the details for a contact.
         */
    }

Khởi động trình tải

Vì đang sử dụng CursorLoader để truy xuất dữ liệu, nên bạn phải khởi chạy luồng trong nền và các biến khác kiểm soát quá trình truy xuất không đồng bộ. Thực hiện thao tác khởi tạo trong onCreate() như ví dụ sau:

Kotlin

class ContactsFragment :
        Fragment(),
        LoaderManager.LoaderCallbacks<Cursor> {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        // Always call the super method first
        super.onCreate(savedInstanceState)
        ...
        // Initializes the loader
        loaderManager.initLoader(0, null, this)

Java

public class ContactsFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<Cursor> {
    ...
    // Called just before the Fragment displays its UI
    @Override
    public void onCreate(Bundle savedInstanceState) {
        // Always call the super method first
        super.onCreate(savedInstanceState);
        ...
        // Initializes the loader
        getLoaderManager().initLoader(0, null, this);

Triển khai onCreateLoader()

Triển khai phương thức onCreateLoader(). Phương thức này được khung trình tải gọi ngay sau khi bạn gọi initLoader().

Trong onCreateLoader(), hãy thiết lập mẫu chuỗi tìm kiếm. Để tạo một chuỗi thành một mẫu, hãy chèn ký tự "%" (phần trăm) để đại diện cho một chuỗi từ 0 ký tự trở lên, hoặc ký tự "_" (dấu gạch dưới) để đại diện cho một ký tự đơn hoặc cả hai. Ví dụ: mẫu "%Jefferson%" sẽ khớp với cả "Thomas Jefferson" và "Jefferson Davis".

Trả về một CursorLoader mới từ phương thức đó. Đối với URI nội dung, hãy sử dụng Contacts.CONTENT_URI. URI này tham chiếu đến toàn bộ bảng, như trong ví dụ sau:

Kotlin

    ...
    override fun onCreateLoader(loaderId: Int, args: Bundle?): Loader<Cursor> {
        /*
         * Makes search string into pattern and
         * stores it in the selection array
         */
        selectionArgs[0] = "%$mSearchString%"
        // Starts the query
        return activity?.let {
            return CursorLoader(
                    it,
                    ContactsContract.Contacts.CONTENT_URI,
                    PROJECTION,
                    SELECTION,
                    selectionArgs,
                    null
            )
        } ?: throw IllegalStateException()
    }

Java

    ...
    @Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
        /*
         * Makes search string into pattern and
         * stores it in the selection array
         */
        selectionArgs[0] = "%" + searchString + "%";
        // Starts the query
        return new CursorLoader(
                getActivity(),
                ContactsContract.Contacts.CONTENT_URI,
                PROJECTION,
                SELECTION,
                selectionArgs,
                null
        );
    }

Triển khai onLoadFinish() và onLoaderReset()

Triển khai phương thức onLoadFinished(). Khung trình tải sẽ gọi onLoadFinished() khi Trình cung cấp danh bạ trả về kết quả của truy vấn. Trong phương thức này, hãy đặt kết quả Cursor vào SimpleCursorAdapter. Thao tác này sẽ tự động cập nhật kết quả tìm kiếm cho ListView:

Kotlin

    override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) {
        // Put the result Cursor in the adapter for the ListView
        cursorAdapter?.swapCursor(cursor)
    }

Java

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        // Put the result Cursor in the adapter for the ListView
        cursorAdapter.swapCursor(cursor);
    }

Phương thức onLoaderReset() được gọi khi khung trình tải phát hiện thấy kết quả Cursor có chứa dữ liệu lỗi thời. Xoá tham chiếu SimpleCursorAdapter đến Cursor hiện có. Nếu bạn không làm như vậy, khung trình tải sẽ không tái chế Cursor, điều này gây rò rỉ bộ nhớ. Ví dụ:

Kotlin

    override fun onLoaderReset(loader: Loader<Cursor>) {
        // Delete the reference to the existing Cursor
        cursorAdapter?.swapCursor(null)
    }

Java

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        // Delete the reference to the existing Cursor
        cursorAdapter.swapCursor(null);

    }

Giờ đây, bạn đã có các phần khoá của ứng dụng khớp với một chuỗi tìm kiếm với tên liên hệ và trả về kết quả trong ListView. Người dùng có thể nhấp vào tên của một người liên hệ để chọn. Thao tác này sẽ kích hoạt trình nghe, trong đó bạn có thể xử lý thêm với dữ liệu của người liên hệ. Ví dụ: bạn có thể truy xuất thông tin chi tiết của người liên hệ đó. Để tìm hiểu cách thực hiện việc này, hãy tiếp tục với bài học tiếp theo: Truy xuất thông tin chi tiết về một người liên hệ.

Để tìm hiểu thêm về giao diện người dùng tìm kiếm, hãy đọc hướng dẫn API Tạo giao diện tìm kiếm.

Các phần còn lại trong bài học này minh hoạ những cách khác để tìm người liên hệ trong Trình cung cấp danh bạ.

So khớp người liên hệ theo một loại dữ liệu cụ thể

Kỹ thuật này cho phép bạn chỉ định kiểu dữ liệu mà bạn muốn so khớp. Truy xuất theo tên là một ví dụ cụ thể về loại truy vấn này, nhưng bạn cũng có thể thực hiện việc này cho bất kỳ loại dữ liệu chi tiết nào được liên kết với một người liên hệ. Ví dụ: bạn có thể truy xuất những người liên hệ có mã bưu chính cụ thể. Trong trường hợp này, chuỗi tìm kiếm phải khớp với dữ liệu được lưu trữ trong hàng mã bưu chính.

Để triển khai loại truy xuất này, trước tiên, hãy triển khai mã sau, như được liệt kê trong các phần trước:

  • Yêu cầu quyền đọc nhà cung cấp.
  • Xác định ListView và bố cục thành phần.
  • Xác định Mảnh hiển thị danh sách người liên hệ.
  • Xác định biến toàn cục.
  • Khởi động Mảnh.
  • Thiết lập CursorAdapter cho ListView.
  • Thiết lập trình nghe người liên hệ đã chọn.
  • Xác định hằng số cho chỉ mục cột Con trỏ.

    Mặc dù bạn đang truy xuất dữ liệu từ một bảng khác, nhưng thứ tự của các cột trong phép chiếu là giống nhau. Vì vậy, bạn có thể sử dụng cùng một chỉ mục cho Con trỏ.

  • Xác định phương thức onItemClick().
  • Khởi động trình tải.
  • Triển khai onLoadCompletion() và onLoaderReset().

Các bước sau đây cho bạn thấy mã bổ sung mà bạn cần để so khớp một chuỗi tìm kiếm với một loại dữ liệu chi tiết cụ thể rồi hiện kết quả.

Chọn loại dữ liệu và bảng

Để tìm kiếm một loại dữ liệu chi tiết cụ thể, bạn phải biết giá trị loại MIME tuỳ chỉnh cho loại dữ liệu đó. Mỗi loại dữ liệu có một giá trị loại MIME duy nhất được xác định bởi một CONTENT_ITEM_TYPE hằng số trong lớp con của ContactsContract.CommonDataKinds liên kết với loại dữ liệu đó. Các lớp con có tên cho biết loại dữ liệu tương ứng; ví dụ: lớp con cho dữ liệu email là ContactsContract.CommonDataKinds.Email và loại MIME tuỳ chỉnh cho dữ liệu email được xác định bằng hằng số Email.CONTENT_ITEM_TYPE.

Sử dụng bảng ContactsContract.Data cho nội dung tìm kiếm của bạn. Tất cả hằng số bạn cần cho phép chiếu, mệnh đề lựa chọn và thứ tự sắp xếp đều được bảng này xác định hoặc kế thừa trong bảng này.

Xác định phép chiếu

Để xác định một phép chiếu, hãy chọn một hoặc nhiều cột được xác định trong ContactsContract.Data hoặc các lớp mà phép chiếu đó kế thừa. Trình cung cấp danh bạ thực hiện việc kết hợp ngầm giữa ContactsContract.Data và các bảng khác trước khi trả về các hàng. Ví dụ:

Kotlin

@SuppressLint("InlinedApi")
private val PROJECTION: Array<out String> = arrayOf(
        /*
         * The detail data row ID. To make a ListView work,
         * this column is required.
         */
        ContactsContract.Data._ID,
        // The primary display name
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
            ContactsContract.Data.DISPLAY_NAME_PRIMARY
        else
            ContactsContract.Data.DISPLAY_NAME,
        // The contact's _ID, to construct a content URI
        ContactsContract.Data.CONTACT_ID,
        // The contact's LOOKUP_KEY, to construct a content URI
        ContactsContract.Data.LOOKUP_KEY
)

Java

    @SuppressLint("InlinedApi")
    private static final String[] PROJECTION =
        {
            /*
             * The detail data row ID. To make a ListView work,
             * this column is required.
             */
            ContactsContract.Data._ID,
            // The primary display name
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
                    ContactsContract.Data.DISPLAY_NAME_PRIMARY :
                    ContactsContract.Data.DISPLAY_NAME,
            // The contact's _ID, to construct a content URI
            ContactsContract.Data.CONTACT_ID,
            // The contact's LOOKUP_KEY, to construct a content URI
            ContactsContract.Data.LOOKUP_KEY // A permanent link to the contact
        };

Xác định tiêu chí tìm kiếm

Để tìm kiếm một chuỗi trong một loại dữ liệu cụ thể, hãy tạo mệnh đề lựa chọn từ:

  • Tên của cột chứa chuỗi tìm kiếm của bạn. Tên này thay đổi tuỳ theo loại dữ liệu, vì vậy, bạn cần tìm lớp con của ContactsContract.CommonDataKinds tương ứng với loại dữ liệu, sau đó chọn tên cột của lớp con đó. Ví dụ: để tìm kiếm địa chỉ email, hãy sử dụng cột Email.ADDRESS.
  • Bản thân chuỗi tìm kiếm, được biểu thị dưới dạng ký tự "?" trong mệnh đề lựa chọn.
  • Tên của cột chứa giá trị loại MIME tuỳ chỉnh. Tên này luôn là Data.MIMETYPE.
  • Giá trị loại MIME tuỳ chỉnh cho loại dữ liệu. Như đã mô tả trước đó, đây là hằng số CONTENT_ITEM_TYPE trong lớp con ContactsContract.CommonDataKinds. Ví dụ: giá trị loại MIME cho dữ liệu email là Email.CONTENT_ITEM_TYPE. Chứa giá trị trong dấu ngoặc đơn bằng cách nối một ký tự "'" (dấu ngoặc đơn) với đầu và cuối của hằng số; nếu không, trình cung cấp sẽ diễn giải giá trị dưới dạng tên biến thay vì giá trị chuỗi. Bạn không cần dùng phần giữ chỗ cho giá trị này vì bạn đang dùng hằng số thay vì giá trị do người dùng cung cấp.

Ví dụ:

Kotlin

/*
 * Constructs search criteria from the search string
 * and email MIME type
 */
private val SELECTION: String =
        /*
         * Searches for an email address
         * that matches the search string
         */
        "${Email.ADDRESS} LIKE ? AND " +
        /*
         * Searches for a MIME type that matches
         * the value of the constant
         * Email.CONTENT_ITEM_TYPE. Note the
         * single quotes surrounding Email.CONTENT_ITEM_TYPE.
         */
        "${ContactsContract.Data.MIMETYPE } = '${Email.CONTENT_ITEM_TYPE}'"

Java

    /*
     * Constructs search criteria from the search string
     * and email MIME type
     */
    private static final String SELECTION =
            /*
             * Searches for an email address
             * that matches the search string
             */
            Email.ADDRESS + " LIKE ? " + "AND " +
            /*
             * Searches for a MIME type that matches
             * the value of the constant
             * Email.CONTENT_ITEM_TYPE. Note the
             * single quotes surrounding Email.CONTENT_ITEM_TYPE.
             */
            ContactsContract.Data.MIMETYPE + " = '" + Email.CONTENT_ITEM_TYPE + "'";

Tiếp theo, hãy xác định các biến để chứa đối số lựa chọn:

Kotlin

    private var searchString: String? = null
    private val selectionArgs: Array<String> = arrayOf("")

Java

    String searchString;
    String[] selectionArgs = { "" };

Triển khai onCreateLoader()

Giờ đây, khi bạn đã chỉ định dữ liệu mà bạn muốn và cách tìm dữ liệu đó, hãy xác định truy vấn trong quá trình triển khai onCreateLoader(). Trả về một CursorLoader mới từ phương thức này, sử dụng phép chiếu, biểu thức văn bản lựa chọn và mảng lựa chọn làm đối số. Đối với URI nội dung, hãy sử dụng Data.CONTENT_URI. Ví dụ:

Kotlin

    override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
        // OPTIONAL: Makes search string into pattern
        searchString = "%$mSearchString%"

        searchString?.also {
            // Puts the search string into the selection criteria
            selectionArgs[0] = it
        }
        // Starts the query
        return activity?.let {
            CursorLoader(
                    it,
                    ContactsContract.Data.CONTENT_URI,
                    PROJECTION,
                    SELECTION,
                    selectionArgs,
                    null
            )
        } ?: throw IllegalStateException()
    }

Java

@Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
        // OPTIONAL: Makes search string into pattern
        searchString = "%" + searchString + "%";
        // Puts the search string into the selection criteria
        selectionArgs[0] = searchString;
        // Starts the query
        return new CursorLoader(
                getActivity(),
                Data.CONTENT_URI,
                PROJECTION,
                SELECTION,
                selectionArgs,
                null
        );
    }

Các đoạn mã này là cơ sở của một thao tác tra cứu ngược đơn giản dựa trên một loại dữ liệu chi tiết cụ thể. Đây là kỹ thuật hay nhất nên sử dụng nếu ứng dụng của bạn tập trung vào một loại dữ liệu cụ thể, chẳng hạn như email và bạn muốn cho phép người dùng lấy tên liên kết với một phần dữ liệu.

So khớp người liên hệ theo bất kỳ loại dữ liệu nào

Việc truy xuất thông tin về người liên hệ dựa trên bất kỳ loại dữ liệu nào sẽ trả về thông tin về người liên hệ nếu có bất kỳ dữ liệu nào của người liên hệ đó khớp với một chuỗi tìm kiếm, bao gồm tên, địa chỉ email, địa chỉ bưu chính, số điện thoại, v.v. Việc này dẫn đến một loạt kết quả tìm kiếm. Ví dụ: nếu chuỗi tìm kiếm là "Doe", thì thao tác tìm kiếm bất kỳ loại dữ liệu nào sẽ trả về địa chỉ liên hệ "John Doe"; thao tác này cũng trả về các địa chỉ liên hệ hiện sống ở "Doe Street".

Để triển khai loại truy xuất này, trước tiên, hãy triển khai mã sau, như được liệt kê trong các phần trước:

  • Yêu cầu quyền đọc nhà cung cấp.
  • Xác định ListView và bố cục thành phần.
  • Xác định Mảnh hiển thị danh sách người liên hệ.
  • Xác định biến toàn cục.
  • Khởi động Mảnh.
  • Thiết lập CursorAdapter cho ListView.
  • Thiết lập trình nghe người liên hệ đã chọn.
  • Xác định phép chiếu.
  • Xác định hằng số cho chỉ mục cột Con trỏ.

    Đối với hình thức truy xuất này, bạn sẽ sử dụng chính bảng mà bạn đã dùng trong phần So khớp một người liên hệ theo tên và liệt kê kết quả. Bạn cũng nên sử dụng cùng một chỉ mục cột.

  • Xác định phương thức onItemClick().
  • Khởi động trình tải.
  • Triển khai onLoadCompletion() và onLoaderReset().

Các bước sau đây cho bạn thấy mã bổ sung mà bạn cần để so khớp một chuỗi tìm kiếm với bất kỳ loại dữ liệu nào và hiện kết quả.

Xoá tiêu chí lựa chọn

Không xác định hằng số SELECTION hoặc biến mSelectionArgs. Chúng không được sử dụng trong loại truy xuất này.

Triển khai onCreateLoader()

Triển khai phương thức onCreateLoader(), trả về một CursorLoader mới. Bạn không cần chuyển đổi chuỗi tìm kiếm thành mẫu vì Trình cung cấp danh bạ sẽ tự động làm việc đó. Sử dụng Contacts.CONTENT_FILTER_URI làm URI cơ sở rồi thêm chuỗi tìm kiếm vào URI đó bằng cách gọi Uri.withAppendedPath(). Việc sử dụng URI này sẽ tự động kích hoạt hoạt động tìm kiếm bất kỳ loại dữ liệu nào, như trong ví dụ sau:

Kotlin

    override fun onCreateLoader(loaderId: Int, args: Bundle?): Loader<Cursor> {
        /*
         * Appends the search string to the base URI. Always
         * encode search strings to ensure they're in proper
         * format.
         */
        val contentUri: Uri = Uri.withAppendedPath(
                ContactsContract.Contacts.CONTENT_FILTER_URI,
                Uri.encode(searchString)
        )
        // Starts the query
        return activity?.let {
            CursorLoader(
                    it,
                    contentUri,
                    PROJECTION2,
                    null,
                    null,
                    null
            )
        } ?: throw IllegalStateException()
    }

Java

    @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(searchString));
        // Starts the query
        return new CursorLoader(
                getActivity(),
                contentUri,
                PROJECTION,
                null,
                null,
                null
        );
    }

Các đoạn mã này là cơ sở của ứng dụng thực hiện tìm kiếm rộng về Nhà cung cấp danh bạ. Kỹ thuật này hữu ích cho các ứng dụng muốn triển khai chức năng tương tự như màn hình danh bạ của ứng dụng Mọi người.