אחזור רשימה של אנשי קשר

בשיעור הזה נסביר איך לאחזר רשימה של אנשי קשר שהנתונים שלהם תואמים לכל מחרוזת החיפוש או לחלק ממנה, באמצעות השיטות הבאות:

התאמת שמות של אנשי קשר
שליפת רשימה של אנשי קשר על ידי התאמת מחרוזת החיפוש לכל הנתונים של שם איש הקשר או לחלק מהם. ניהול אנשי הקשר מאפשר כמה מופעים של אותו שם, כך שהשיטה הזו יכולה להחזיר רשימה של התאמות.
התאמה לסוג נתונים ספציפי, כמו מספר טלפון
אחזור רשימה של אנשי קשר על ידי התאמת מחרוזת החיפוש לסוג מסוים של נתונים כמו כתובת אימייל. לדוגמה, בעזרת הטכניקה הזו אפשר להציג רשימה של כל אנשי הקשר שכתובת האימייל שלהם תואמת למחרוזת החיפוש.
התאמה לכל סוג של נתונים
אחזור רשימה של אנשי קשר על ידי התאמת מחרוזת החיפוש לכל סוג של נתון פרטים, כולל שם, מספר טלפון, כתובת, כתובת אימייל וכו'. לדוגמה, הטכניקה הזו מאפשרת לקבל כל סוג של נתונים למחרוזת חיפוש, ואז להציג רשימה של אנשי הקשר שהנתונים שלהם תואמים למחרוזת.

הערה: כל הדוגמאות בשיעור הזה משתמשות ב-CursorLoader כדי לאחזר נתונים מספק אנשי הקשר. CursorLoader מריץ את השאילתה שלו בשרשור נפרד משרשור ה-UI. כך אפשר לוודא שהשאילתה לא תאט את זמני התגובה של ממשק המשתמש ותגרום לחוויית משתמש גרועה. מידע נוסף זמין בשיעור ההדרכה של Android בנושא טעינת נתונים ברקע.

בקשת הרשאה לקרוא את הספק

כדי לבצע חיפוש מכל סוג שהוא בניהול אנשי הקשר, לאפליקציה שלכם צריכה להיות הרשאת READ_CONTACTS. כדי לבקש את זה, מוסיפים את הרכיב <uses-permission> לקובץ המניפסט כרכיב צאצא של <manifest>:

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

התאמה של איש קשר לפי שם ורשימת התוצאות

הטכניקה הזו מנסה להתאים מחרוזת חיפוש לשם של איש קשר או אנשי קשר בטבלה ContactsContract.Contacts של ניהול אנשי הקשר. בדרך כלל רוצים להציג את התוצאות בListView, כדי לאפשר למשתמש לבחור מבין אנשי הקשר התואמים.

הגדרת ListView ופריסות של פריטים

כדי להציג את תוצאות החיפוש ב-ListView, צריך קובץ פריסה ראשי שמגדיר את כל ממשק המשתמש, כולל ה-ListView, וקובץ פריסה של פריט שמגדיר שורה אחת של ה-ListView. לדוגמה, אפשר ליצור את קובץ הפריסה הראשי res/layout/contacts_list_view.xml עם ה-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 הזה נעשה שימוש בווידג'ט ListView המובנה של Android android:id/list.

מגדירים את קובץ הפריסה של הפריט contacts_list_item.xml באמצעות ה-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 הזה נעשה שימוש בווידג'ט TextView המובנה של Android android:text1.

הערה: בשיעור הזה לא מתואר ממשק המשתמש לקבלת מחרוזת חיפוש מהמשתמש, כי יכול להיות שתרצו לקבל את המחרוזת באופן עקיף. לדוגמה, אפשר לתת למשתמש אפשרות לחפש אנשי קשר שהשם שלהם תואם למחרוזת בהודעת SMS נכנסת.

שני קובצי הפריסה שכתבתם מגדירים ממשק משתמש שמוצג בו ListView. השלב הבא הוא לכתוב קוד שמשתמש בממשק המשתמש הזה כדי להציג רשימה של אנשי קשר.

הגדרת Fragment שמציג את רשימת אנשי הקשר

כדי להציג את רשימת אנשי הקשר, קודם צריך להגדיר Fragment שנטען על ידי Activity. שימוש ב-Fragment הוא טכניקה גמישה יותר, כי אפשר להשתמש ב-Fragment אחד כדי להציג את הרשימה וב-Fragment שני כדי להציג את הפרטים של איש קשר שהמשתמש בוחר מהרשימה. באמצעות הגישה הזו, אפשר לשלב בין אחת מהטכניקות שמוצגות בשיעור הזה לבין אחת מהטכניקות שמוצגות בשיעור אחזור פרטים של איש קשר.

כדי ללמוד איך להשתמש באובייקט אחד או יותר של Fragment מ-Activity, קראו את שיעור ההדרכה בניית ממשק משתמש דינמי באמצעות מקטעים.

כדי לעזור לכם לכתוב שאילתות לניהול אנשי הקשר, מסגרת Android מספקת מחלקה של ערכים קבועים (contract class) שנקראת ContactsContract, שמגדירה קבועים ושיטות שימושיים לגישה לספק. כשמשתמשים במחלקה הזו, לא צריך להגדיר קבועים משלכם ל-URI של תוכן, לשמות של טבלאות או לעמודות. כדי להשתמש במחלקה הזו, צריך לכלול את ההצהרה הבאה:

Kotlin

import android.provider.ContactsContract

Java

import android.provider.ContactsContract;

מכיוון שהקוד משתמש ב-CursorLoader כדי לאחזר נתונים מהספק, צריך לציין שהוא מטמיע את ממשק הטעינה LoaderManager.LoaderCallbacks. בנוסף, כדי לזהות את איש הקשר שהמשתמש בוחר מתוך רשימת תוצאות החיפוש, צריך להטמיע את ממשק המתאם AdapterView.OnItemClickListener. לדוגמה:

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 {

הגדרה של משתנים גלובליים

מגדירים משתנים גלובליים שמשמשים בחלקים אחרים של הקוד:

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

הערה: מאחר שהתג Contacts.DISPLAY_NAME_PRIMARY דורש Android 3.0 (גרסת API 11) או גרסה מתקדמת יותר, הגדרת minSdkVersion של האפליקציה ל-10 או לערך נמוך יותר יוצרת אזהרת Android Lint ב-Android Studio. כדי להשבית את האזהרה הזו, צריך להוסיף את ההערה @SuppressLint("InlinedApi") לפני ההגדרה של FROM_COLUMNS.

הפעלת ה-Fragment

מפעילים את Fragment. מוסיפים את ה-constructor הריק והציבורי שנדרש על ידי מערכת Android, ויוצרים את ממשק המשתמש של האובייקט Fragment בשיטת קריאה חוזרת onCreateView(). לדוגמה:

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

הגדרת CursorAdapter עבור ListView

מגדירים את SimpleCursorAdapter שמקשר את תוצאות החיפוש אל ListView. כדי לקבל את האובייקט ListView שמציג את אנשי הקשר, צריך לקרוא ל-Activity.findViewById() באמצעות פעילות האב של Fragment. משתמשים ב-Context של פעילות האב כשמתקשרים אל setAdapter(). לדוגמה:

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

הגדרת מאזין לאיש הקשר שנבחר

כשמציגים את תוצאות החיפוש, בדרך כלל רוצים לאפשר למשתמש לבחור איש קשר אחד לעיבוד נוסף. לדוגמה, כשמשתמש לוחץ על איש קשר, אפשר להציג את הכתובת שלו במפה. כדי לספק את התכונה הזו, קודם הגדרתם את Fragment הנוכחי כרכיב click listener על ידי ציון שהמחלקה מטמיעה את AdapterView.OnItemClickListener, כפי שמוצג בקטע הגדרת Fragment שמציג את רשימת אנשי הקשר.

כדי להמשיך בהגדרת ה-listener, צריך לקשר אותו ל-ListView על ידי קריאה למתודה setOnItemClickListener() ב-onActivityCreated(). לדוגמה:

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

מכיוון שציינתם שה-Fragment הנוכחי הוא ה-OnItemClickListener של ה-ListView, עכשיו אתם צריכים להטמיע את ה-method הנדרשת שלו, onItemClick(), שמטפלת באירוע מסוג קליק. התיאור מופיע בקטע הבא.

הגדרת תחזית

מגדירים קבוע שמכיל את העמודות שרוצים להחזיר מהשאילתה. כל פריט ב-ListView מציג את השם המוצג של איש הקשר, שמכיל את הצורה העיקרית של שם איש הקשר. ב-Android 3.0 (גרסת API‏ 11) ואילך, שם העמודה הזו הוא Contacts.DISPLAY_NAME_PRIMARY. בגרסאות קודמות, השם שלה הוא Contacts.DISPLAY_NAME.

העמודה Contacts._ID משמשת את תהליך הקישור של SimpleCursorAdapter. ‫Contacts._ID ו-LOOKUP_KEY משמשים יחד כדי ליצור URI של תוכן לאיש הקשר שהמשתמש בוחר.

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

        };

הגדרת קבועים לאינדקסים של עמודות הסמן

כדי לקבל נתונים מעמודה ספציפית ב-Cursor, צריך לציין את האינדקס של העמודה ב-Cursor. אפשר להגדיר קבועים לאינדקסים של העמודות של Cursor, כי האינדקסים זהים לסדר של שמות העמודות בהיטל. לדוגמה:

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;

מציינים את הקריטריונים לבחירה

כדי לציין את הנתונים הרצויים, יוצרים שילוב של ביטויי טקסט ומשתנים שאומרים לספק באילו עמודות נתונים לחפש ואילו ערכים למצוא.

בביטוי הטקסט, מגדירים קבוע שמפרט את עמודות החיפוש. למרות שהביטוי הזה יכול להכיל גם ערכים, השיטה המועדפת היא לייצג את הערכים באמצעות פלייסהולדר "?". במהלך השליפה, הפלייסהולדר מוחלף בערכים ממערך. שימוש בסימן '?' כ-placeholder מבטיח שמפרט החיפוש ייווצר על ידי איגוד ולא על ידי קומפילציה של SQL. השיטה הזו מבטלת את האפשרות להזרקת SQL זדונית. לדוגמה:

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

הגדרה של השיטה onItemClick()‎

בקטע הקודם הגדרתם את רכיב click listener עבור ListView. עכשיו מטמיעים את הפעולה של המאזין על ידי הגדרת ה-method‏ 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.
         */
    }

מאתחלים את ה-loader

מכיוון שאתם משתמשים ב-CursorLoader כדי לאחזר נתונים, אתם צריכים לאתחל את ה-thread ברקע ואת המשתנים האחרים ששולטים באחזור אסינכרוני. מבצעים את האיניציאליזציה ב-onCreate(), כמו בדוגמה הבאה:

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

הטמעה של onCreateLoader()‎

מטמיעים את ה-method‏ onCreateLoader(), שמופעל על ידי framework הטעינה מיד אחרי שמפעילים את initLoader().

ב-onCreateLoader(), מגדירים את התבנית של מחרוזת החיפוש. כדי להפוך מחרוזת לתבנית, מוסיפים את התו '%' כדי לייצג רצף של אפס תווים או יותר, או את התו '_' כדי לייצג תו בודד, או את שניהם. לדוגמה, התבנית ‎ "%Jefferson%"‎ תתאים גם ל-‎ "Thomas Jefferson"‎ וגם ל-‎ "Jefferson Davis"‎.

מחזירים CursorLoader חדש מהשיטה. בשדה Content URI, מזינים Contacts.CONTENT_URI. ה-URI הזה מתייחס לטבלה כולה, כמו בדוגמה הבאה:

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

מטמיעים את onLoadFinished()‎ ואת onLoaderReset()‎

מטמיעים את ה-method‏ onLoadFinished(). מסגרת הטעינה קוראת ל-‎ onLoadFinished() כשניהול אנשי הקשר מחזיר את תוצאות השאילתה. בשיטה הזו, צריך להציב את התוצאה Cursor ב-SimpleCursorAdapter. הפעולה הזו מעדכנת באופן אוטומטי את 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);
    }

השיטה onLoaderReset() מופעלת כשמסגרת ה-loader מזהה שהתוצאה Cursor מכילה נתונים לא עדכניים. מוחקים את ההפניה של SimpleCursorAdapter ל-Cursor הקיים. אם לא תעשו זאת, מסגרת הטעינה לא תבצע מחזור של Cursor, מה שיגרום לדליפת זיכרון. לדוגמה:

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

    }

עכשיו יש לכם את החלקים העיקריים של אפליקציה שמתאימה בין מחרוזת חיפוש לשמות של אנשי קשר ומחזירה את התוצאה ב-ListView. המשתמש יכול ללחוץ על שם של איש קשר כדי לבחור אותו. הפעולה הזו מפעילה מאזין, שבו אפשר להמשיך לעבוד עם הנתונים של איש הקשר. לדוגמה, אפשר לאחזר את הפרטים של איש הקשר. כדי ללמוד איך עושים את זה, ממשיכים לשיעור הבא, אחזור פרטים של איש קשר.

מידע נוסף על ממשקי משתמש לחיפוש זמין במדריך ה-API בנושא יצירת ממשק חיפוש.

החלקים הבאים בשיעור הזה מציגים דרכים אחרות למציאת אנשי קשר בספק אנשי הקשר.

התאמה של איש קשר לפי סוג נתונים ספציפי

השיטה הזו מאפשרת לכם לציין את סוג הנתונים שאתם רוצים להתאים. אחזור לפי שם הוא דוגמה ספציפית לסוג כזה של שאילתה, אבל אפשר לעשות את זה גם לכל אחד מסוגי הנתונים המפורטים שמשויכים לאיש קשר. לדוגמה, אתם יכולים לאחזר אנשי קשר שיש להם מיקוד ספציפי. במקרה כזה, מחרוזת החיפוש צריכה להתאים לנתונים שמאוחסנים בשורת מיקוד.

כדי להטמיע את סוג האחזור הזה, קודם מטמיעים את הקוד הבא, כמו שמופיע בקטעים הקודמים:

  • בקשת הרשאה לקריאת הספק.
  • מגדירים את הפריסות של ListView ושל הפריטים.
  • מגדירים Fragment שמציג את רשימת אנשי הקשר.
  • הגדרת משתנים גלובליים.
  • מפעילים את ה-Fragment.
  • מגדירים את CursorAdapter עבור ListView.
  • הגדרת המאזין לאיש הקשר שנבחר.
  • מגדירים קבועים לאינדקסים של עמודות הסמן.

    למרות שמאחזרים נתונים מטבלה אחרת, סדר העמודות בהקרנה זהה, ולכן אפשר להשתמש באותם אינדקסים עבור הסמן.

  • מגדירים את השיטה onItemClick()‎.
  • מאתחלים את ה-loader.
  • מטמיעים את onLoadFinished()‎ ואת onLoaderReset()‎.

בשלבים הבאים מוסבר איך להוסיף קוד כדי להתאים מחרוזת חיפוש לסוג מסוים של נתוני פרטים ולהציג את התוצאות.

בחירת סוג הנתונים והטבלה

כדי לחפש סוג מסוים של נתוני פרטים, צריך לדעת את ערך סוג ה-MIME המותאם אישית של סוג הנתונים. לכל סוג נתונים יש ערך ייחודי של סוג MIME שמוגדר על ידי קבוע CONTENT_ITEM_TYPE במחלקת המשנה של ContactsContract.CommonDataKinds שמשויכת לסוג הנתונים. למחלקות המשנה יש שמות שמציינים את סוג הנתונים שלהן. לדוגמה, מחלקת המשנה של נתוני אימייל היא ContactsContract.CommonDataKinds.Email, וסוג ה-MIME המותאם אישית של נתוני אימייל מוגדר על ידי הקבוע Email.CONTENT_ITEM_TYPE.

משתמשים בטבלה ContactsContract.Data כדי לחפש. כל הקבועים שדרושים לכם להקרנה, לסעיף הבחירה ולסדר המיון מוגדרים בטבלה הזו או עוברים אליה בירושה.

הגדרת תחזית

כדי להגדיר הקרנה, בוחרים אחת או יותר מהעמודות שהוגדרו ב-ContactsContract.Data או את המחלקות שמהן היא יורשת. ניהול אנשי הקשר מבצע צירוף מרומז בין ContactsContract.Data לבין טבלאות אחרות לפני שהוא מחזיר שורות. לדוגמה:

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

הגדרת קריטריונים לחיפוש

כדי לחפש מחרוזת בסוג מסוים של נתונים, יוצרים משפט בחירה מהאפשרויות הבאות:

  • שם העמודה שמכילה את מחרוזת החיפוש. השם הזה משתנה בהתאם לסוג הנתונים, לכן צריך למצוא את מחלקת המשנה של ContactsContract.CommonDataKinds שמתאימה לסוג הנתונים ואז לבחור את שם העמודה ממחלקת המשנה הזו. לדוגמה, כדי לחפש כתובות אימייל, משתמשים בעמודה Email.ADDRESS.
  • מחרוזת החיפוש עצמה, שמיוצגת על ידי התו "?" בסעיף הבחירה.
  • שם העמודה שמכילה את הערך של סוג ה-MIME המותאם אישית. השם הזה תמיד Data.MIMETYPE.
  • ערך סוג ה-MIME המותאם אישית של סוג הנתונים. כפי שמתואר למעלה, זהו הקבוע CONTENT_ITEM_TYPE במחלקת המשנה ContactsContract.CommonDataKinds. לדוגמה, ערך סוג ה-MIME של נתוני אימייל הוא Email.CONTENT_ITEM_TYPE. תוחמים את הערך במירכאות בודדות על ידי שרשור של התו "'" (מירכאות בודדות) בתחילת הקבוע ובסופו. אחרת, הספק מפרש את הערך כשם של משתנה ולא כערך של מחרוזת. אין צורך להשתמש בפלייסהולדר לערך הזה, כי אתם משתמשים בקבוע ולא בערך שהמשתמש מספק.

לדוגמה:

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 + "'";

בשלב הבא, מגדירים משתנים שיכילו את הארגומנט של הבחירה:

Kotlin

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

Java

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

הטמעה של onCreateLoader()

אחרי שציינתם את הנתונים שאתם רוצים ואת הדרך למצוא אותם, אתם צריכים להגדיר שאילתה בהטמעה של onCreateLoader(). מחזירים CursorLoader חדש מהשיטה הזו, ומשתמשים בהיטל, בביטוי הטקסט של הבחירה ובמערך הבחירה כארגומנטים. ל-URI של תוכן, משתמשים ב-Data.CONTENT_URI. לדוגמה:

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

קטעי הקוד האלה הם הבסיס לחיפוש הפוך בסיסי שמבוסס על סוג ספציפי של נתונים מפורטים. זו השיטה הכי טובה אם האפליקציה מתמקדת בסוג מסוים של נתונים, כמו כתובות אימייל, ואתם רוצים לאפשר למשתמשים לקבל את השמות שמשויכים לנתון מסוים.

התאמת איש קשר לפי כל סוג של נתונים

אם מאחזרים איש קשר על סמך כל סוג של נתונים, אנשי הקשר יוחזרו אם אחד מהנתונים שלהם תואם למחרוזת החיפוש, כולל שם, כתובת אימייל, כתובת למשלוח דואר, מספר טלפון וכו'. כתוצאה מכך, מתקבלת קבוצה רחבה של תוצאות חיפוש. לדוגמה, אם מחפשים את המחרוזת 'Doe', חיפוש של כל סוג נתונים יחזיר את איש הקשר 'John Doe', וגם אנשי קשר שגרים ברחוב Doe.

כדי להטמיע סוג כזה של אחזור, קודם מטמיעים את הקוד הבא, כמו שמופיע בקטעים הקודמים:

  • בקשת הרשאה לקריאת הספק.
  • מגדירים את הפריסות של ListView ושל הפריטים.
  • מגדירים Fragment שמציג את רשימת אנשי הקשר.
  • הגדרת משתנים גלובליים.
  • מפעילים את ה-Fragment.
  • מגדירים את CursorAdapter עבור ListView.
  • מגדירים את מאזין אנשי הקשר שנבחרו.
  • הגדרת הקרנה.
  • מגדירים קבועים לאינדקסים של העמודות של הסמן.

    כדי לאחזר את המידע הזה, משתמשים באותה טבלה שבה השתמשתם בקטע התאמה של איש קשר לפי שם ורישום התוצאות. צריך להשתמש גם באותם אינדקסים של עמודות.

  • מגדירים את השיטה onItemClick()‎.
  • מאתחלים את ה-loader.
  • מטמיעים את onLoadFinished()‎ ואת onLoaderReset()‎.

בשלבים הבאים מוסבר איך להוסיף קוד כדי להתאים מחרוזת חיפוש לכל סוג של נתונים ולהציג את התוצאות.

הסרת קריטריונים שמשפיעים על הבחירה

אל תגדירו את הקבועים SELECTION או את המשתנה mSelectionArgs. הם לא משמשים לאחזור מהסוג הזה.

הטמעה של onCreateLoader()

מטמיעים את השיטה onCreateLoader() ומחזירים CursorLoader חדש. לא צריך להמיר את מחרוזת החיפוש לתבנית, כי ניהול אנשי הקשר עושה את זה באופן אוטומטי. משתמשים ב-Contacts.CONTENT_FILTER_URI בתור ה-URI הבסיסי, ומוסיפים לו את מחרוזת החיפוש באמצעות קריאה ל-Uri.withAppendedPath(). השימוש ב-URI הזה מפעיל באופן אוטומטי חיפוש של כל סוג נתונים, כמו בדוגמה הבאה:

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

קטעי הקוד האלה הם הבסיס לאפליקציה שמבצעת חיפוש רחב של ניהול אנשי הקשר. השיטה הזו שימושית לאפליקציות שרוצות להטמיע פונקציונליות שדומה למסך רשימת אנשי הקשר של אפליקציית "אנשי קשר".