如何创建快速联系人标记

快速联系人标记到底是什么?废话不多说,直接上图:

image

简单的说,就是将 QuickContactBadge 这个控件和联系人信息绑定起来,以达到点击相应联系人头像时,弹出一个 Dialog,在此 Dialog 中显示当前联系人的相关信息,同时这里的每一个信息又都是可点击的,点击之后,系统会自动匹配可以响应的 App,示例中点击了邮箱地址之后直接跳到创建新邮件界面。除此之外,点击此 Dialog 的标题栏,会直接跳到联系人详情界面。接下来,让我们看看此功能是什么实现的。

一、界面组成及原理介绍

从效果图中,我们不难看出,界面的组成相当简单:一个 ListView,一个 Dialog。ListView 中每一个 Item 中包含一个 ImageView 和 一个 TextView;Dialog 中是一个 ViewPager,ViewPager 的每一个界面元素是不同的联系人信息。

表面上看,大概就是这个样子,但实际上,有一个地方是不对的,那就是 ListView 中的每一个 Item 布局中的图片显示控件用的不是 ImageView,而是 QuickContactBadge。QuickContactBadge 是个什么鬼?想必很多人直接懵逼了,但如果我把这个控件指给你,你一定会骂娘的……

image

没错,就是这货!

可别小看了这个控件的功能,就是因为有了它,我们才可以省去很多工作(关联此图片对应的联系人、填充 ViewPager 每个页面的数据并决定用可以响应不同点击事件的 App,blabla……)。

QuickContactBadge 中有两个函数十分关键:

1.assignContactUri
指定关联的联系人信息

2.setImageBitmap
指定 QuickContactBadge 显示的图片资源

知道了这些之后,其实实现起来也就没什么难度了,无非就两件事:

1.获取联系人数据
2.将 QuickContactBadge 和相应的联系人数据绑定起来

二、实现

1.获取联系人数据

本例中,是通过查询匹配用户名信息的方法获取联系人列表的,主要涉及到的的知识有:

  1. LoaderManager.LoaderCallbacks(查询 Contact Provider 数据)
  2. CursorAdapter (显示 LoaderManager.LoaderCallbacks 查询到的数据)

ListView 中 Item 的布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="@dimen/padding_small">

    <QuickContactBadge
        android:id="@+id/quick_contact"
        android:layout_width="@dimen/padding_item"
        android:layout_height="@dimen/padding_item"
        android:scaleType="centerCrop" />

    <TextView
        android:id="@+id/display_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="@dimen/padding_medium"
        android:layout_toRightOf="@+id/quick_contact"
        android:text="@string/null_value"
        android:textSize="@dimen/font_small" />
</RelativeLayout>

自定义 ContactsAdapter 继承自 CursorAdapter

private class ContactsAdapter extends CursorAdapter {

        private LayoutInflater mInflater;

        public ContactsAdapter(Context context) {
            super(context, null, 0);
            /*
             * Gets an inflater that can instantiate
             * the ListView layout from the file.
             */
            mInflater = LayoutInflater.from(context);
        }

        /**
         * Defines a class that hold resource IDs of each item layout
         * row to prevent having to look them up each time data is
         * bound to a row.
         */
        private class ViewHolder {
            TextView displayName;
            QuickContactBadge quickContact;
        }

        @Override
        public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
            /* Inflates the item layout. Stores resource IDs in a
             * in a ViewHolder class to prevent having to look
             * them up each time bindView() is called.
             */
            final ViewHolder viewHolder;
            View itemView = null;
            if(itemView == null){
                itemView = mInflater.inflate(R.layout.item_display_quick_contact_badge, viewGroup, false);
                viewHolder = new ViewHolder();
                viewHolder.displayName = (TextView) itemView.findViewById(R.id.display_name);
                viewHolder.quickContact = (QuickContactBadge) itemView.findViewById(R.id.quick_contact);
                itemView.setTag(viewHolder);
            }else{
                viewHolder = (ViewHolder) itemView.getTag();
            }
            return itemView;
        }

        /**
         * Des: 将数据源绑定到视图
         *
         * 由 bindView 方法调用的次数可以知道:在此处不用迭代 cursor,因为每一次 Android Framework 都为我们做好了
         *
         * Time: 2017/6/6 下午6:55
         */
        @Override
        public void bindView(View view, Context context, Cursor cursor) {
            final ViewHolder holder = (ViewHolder) view.getTag();
            final String displayName = cursor.getString(mDisplayNameIndex);
            final String photoData = cursor.getString(mPhotoDataIndex);

            // Sets the display name in the layout
            holder.displayName.setText(displayName);

            /*
             * Generates a contact URI for the QuickContactBadge.
             */
            final Uri contactUri = Contacts.getLookupUri(cursor.getLong(mIdIndex), cursor.getString(mLookupKeyIndex));
            holder.quickContact.assignContactUri(contactUri);

            /*
             * Decodes the thumbnail file to a Bitmap.
             * The method loadContactPhotoThumbnail() is defined
             * in the section "Set the Contact URI and Thumbnail"
             */
            Bitmap thumbnailBitmap = null;
            if(!TextUtils.isEmpty(photoData)){
                thumbnailBitmap = loadContactPhotoThumbnail(photoData);
            }else{
                thumbnailBitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher);
            }
            /*
             * Sets the image in the QuickContactBadge
             * QuickContactBadge inherits from ImageView
             */
            holder.quickContact.setImageBitmap(thumbnailBitmap);
        }

        /**
         * Load a contact photo thumbnail and return it as a Bitmap,
         * resizing the image to the provided image dimensions as needed.
         * @param photoData photo ID Prior to Honeycomb, the contact's _ID value.
         * For Honeycomb and later, the value of PHOTO_THUMBNAIL_URI.
         * @return A thumbnail Bitmap, sized to the provided width and height.
         * Returns null if the thumbnail is not found.
         */
        private Bitmap loadContactPhotoThumbnail(String photoData) {
            // Creates an asset file descriptor for the thumbnail file.
            AssetFileDescriptor afd = null;
            // try-catch block for file not found
            try {
                // Creates a holder for the URI.
                Uri thumbUri;
                // If Android 3.0 or later
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                    // Sets the URI from the incoming PHOTO_THUMBNAIL_URI
                    thumbUri = Uri.parse(photoData);
                } else {
                    // Prior to Android 3.0, constructs a photo Uri using _ID
                /*
                 * Creates a contact URI from the Contacts content URI
                 * incoming photoData (_ID)
                 */
                    final Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_URI, photoData);
                /*
                 * Creates a photo URI by appending the content URI of
                 * Contacts.Photo.
                 */
                    thumbUri = Uri.withAppendedPath(contactUri, Contacts.Photo.CONTENT_DIRECTORY);
                }

                /*
                 * Retrieves an AssetFileDescriptor object for the thumbnail
                 * URI
                 * using ContentResolver.openAssetFileDescriptor
                 */
                    afd = DisplayingQuickContactBadgeActivity.this.getContentResolver().openAssetFileDescriptor(thumbUri, "r");

                /*
                 * Gets a file descriptor from the asset file descriptor.
                 * This object can be used across processes.
                 */
                    FileDescriptor fileDescriptor = afd.getFileDescriptor();

                // Decode the photo file and return the result as a Bitmap
                // If the file descriptor is valid
                if (fileDescriptor != null) {
                    // Decodes the bitmap
                    return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, null);
                }
                // If the file isn't found
            } catch (FileNotFoundException e) {
                /*
                 * Handle file not found errors
                 */
                // In all cases, close the asset file descriptor
            } finally {
                if (afd != null) {
                    try {
                        afd.close();
                    } catch (IOException e) {}
                }
            }
            return null;
        }
    }

实现 LoaderManager.LoaderCallbacks 接口,并定义查询条件

public class DisplayingQuickContactBadgeActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor>{
    ...
    /*
     * Defines a projection based on platform version. This ensures
     * that you retrieve the correct columns.
     */
    private static final String[] PROJECTION =
            {
                    ContactsContract.Contacts._ID,
                    Contacts.LOOKUP_KEY,
                    (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME,
                    (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ? Contacts.PHOTO_THUMBNAIL_URI : Contacts._ID
                        /*
                         * Although it's not necessary to include the
                         * column twice, this keeps the number of
                         * columns the same regardless of version
                         */
            };

    // Defines the text expression
    @SuppressLint("InlinedApi")
    private static final String SELECTION =
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
                    ContactsContract.Contacts.DISPLAY_NAME_PRIMARY + " LIKE ?" :
                    ContactsContract.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 };

    /*
     * As a shortcut, defines constants for the
     * column indexes in the Cursor. The index is
     * 0-based and always matches the column order
     * in the projection.
     */
    // Column index of the _ID column
    private int mIdIndex = 0;
    // Column index of the LOOKUP_KEY column
    private int mLookupKeyIndex = 1;
    // Column index of the display name column
    private int mDisplayNameIndex = 2;
    /*
     * Column index of the photo data column.
     * It's PHOTO_THUMBNAIL_URI for Honeycomb and later,
     * and _ID for previous versions.
     */
    private int mPhotoDataIndex = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? 3 : 0;
    ...

    //以下是实现 LoaderManager.LoaderCallbacks<Cursor> 接口必须实现的三个方法
        @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        /*
         * Makes search string into pattern and
         * stores it in the selection array
         */
        mSelectionArgs[0] = "%" + mSearchString + "%";
        // Starts the query
        return new CursorLoader(
                this,
                ContactsContract.Contacts.CONTENT_URI,
                PROJECTION,
                SELECTION,
                mSelectionArgs,
                null
        );
    }

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

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

将 ListView 和自定义 ContactsAdapter 关联起来并开始查询数据

    ...
    // Defines a ListView
    private ListView mListView;
    // Defines a ContactsAdapter
    private ContactsAdapter mAdapter;
    ...

    mAdapter = new ContactsAdapter(this);
    // Sets up the adapter for the ListView
    mListView.setAdapter(mAdapter);

    // Initializes the loader
    // 开始查询
    getLoaderManager().initLoader(0, null, this);

2.在适配器中将 QuickContactBadge 和查询到的数据关联起来

...
holder.quickContact.assignContactUri(contactUri);
holder.quickContact.setImageBitmap(thumbnailBitmap);
...

我们需要做的就这么多,最近实现有点忙,文章没有经过好好整理,见谅,最后,将完整代码贴出(其实就一个 Activity)

public class DisplayingQuickContactBadgeActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor>{

    // Defines a ListView
    private ListView mListView;
    // Defines a ContactsAdapter
    private ContactsAdapter mAdapter;

    /*
     * Defines a projection based on platform version. This ensures
     * that you retrieve the correct columns.
     */
    private static final String[] PROJECTION =
            {
                    ContactsContract.Contacts._ID,
                    Contacts.LOOKUP_KEY,
                    (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME,
                    (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ? Contacts.PHOTO_THUMBNAIL_URI : Contacts._ID
                        /*
                         * Although it's not necessary to include the
                         * column twice, this keeps the number of
                         * columns the same regardless of version
                         */
            };

    // Defines the text expression
    @SuppressLint("InlinedApi")
    private static final String SELECTION =
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
                    ContactsContract.Contacts.DISPLAY_NAME_PRIMARY + " LIKE ?" :
                    ContactsContract.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 };

    /*
     * As a shortcut, defines constants for the
     * column indexes in the Cursor. The index is
     * 0-based and always matches the column order
     * in the projection.
     */
    // Column index of the _ID column
    private int mIdIndex = 0;
    // Column index of the LOOKUP_KEY column
    private int mLookupKeyIndex = 1;
    // Column index of the display name column
    private int mDisplayNameIndex = 2;
    /*
     * Column index of the photo data column.
     * It's PHOTO_THUMBNAIL_URI for Honeycomb and later,
     * and _ID for previous versions.
     */
    private int mPhotoDataIndex = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? 3 : 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initData();
    }

    private void initView(){
        mListView = (ListView)this.findViewById(R.id.lv);
    }

    private void initData(){
        mAdapter = new ContactsAdapter(this);
        // Sets up the adapter for the ListView
        mListView.setAdapter(mAdapter);

        // Initializes the loader
        getLoaderManager().initLoader(0, null, this);
    }

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

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

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

    private class ContactsAdapter extends CursorAdapter {

        private LayoutInflater mInflater;

        public ContactsAdapter(Context context) {
            super(context, null, 0);
            /*
             * Gets an inflater that can instantiate
             * the ListView layout from the file.
             */
            mInflater = LayoutInflater.from(context);
        }

        /**
         * Defines a class that hold resource IDs of each item layout
         * row to prevent having to look them up each time data is
         * bound to a row.
         */
        private class ViewHolder {
            TextView displayName;
            QuickContactBadge quickContact;
        }

        @Override
        public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
            /* Inflates the item layout. Stores resource IDs in a
             * in a ViewHolder class to prevent having to look
             * them up each time bindView() is called.
             */
            final ViewHolder viewHolder;
            View itemView = null;
            if(itemView == null){
                itemView = mInflater.inflate(R.layout.item_display_quick_contact_badge, viewGroup, false);
                viewHolder = new ViewHolder();
                viewHolder.displayName = (TextView) itemView.findViewById(R.id.display_name);
                viewHolder.quickContact = (QuickContactBadge) itemView.findViewById(R.id.quick_contact);
                itemView.setTag(viewHolder);
            }else{
                viewHolder = (ViewHolder) itemView.getTag();
            }
            return itemView;
        }

        /**
         * Des: 将数据源绑定到视图
         *
         * 由 bindView 方法调用的次数可以知道:在此处不用迭代 cursor,因为每一次 Android Framework 都为我们做好了
         *
         * Time: 2017/6/6 下午6:55
         */
        @Override
        public void bindView(View view, Context context, Cursor cursor) {
            final ViewHolder holder = (ViewHolder) view.getTag();
            final String displayName = cursor.getString(mDisplayNameIndex);
            final String photoData = cursor.getString(mPhotoDataIndex);

            // Sets the display name in the layout
            holder.displayName.setText(displayName);

            /*
             * Generates a contact URI for the QuickContactBadge.
             */
            final Uri contactUri = Contacts.getLookupUri(cursor.getLong(mIdIndex), cursor.getString(mLookupKeyIndex));
            holder.quickContact.assignContactUri(contactUri);

            /*
             * Decodes the thumbnail file to a Bitmap.
             * The method loadContactPhotoThumbnail() is defined
             * in the section "Set the Contact URI and Thumbnail"
             */
            Bitmap thumbnailBitmap = null;
            if(!TextUtils.isEmpty(photoData)){
                thumbnailBitmap = loadContactPhotoThumbnail(photoData);
            }else{
                thumbnailBitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher);
            }
            /*
             * Sets the image in the QuickContactBadge
             * QuickContactBadge inherits from ImageView
             */
            holder.quickContact.setImageBitmap(thumbnailBitmap);
        }

        /**
         * Load a contact photo thumbnail and return it as a Bitmap,
         * resizing the image to the provided image dimensions as needed.
         * @param photoData photo ID Prior to Honeycomb, the contact's _ID value.
         * For Honeycomb and later, the value of PHOTO_THUMBNAIL_URI.
         * @return A thumbnail Bitmap, sized to the provided width and height.
         * Returns null if the thumbnail is not found.
         */
        private Bitmap loadContactPhotoThumbnail(String photoData) {
            // Creates an asset file descriptor for the thumbnail file.
            AssetFileDescriptor afd = null;
            // try-catch block for file not found
            try {
                // Creates a holder for the URI.
                Uri thumbUri;
                // If Android 3.0 or later
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                    // Sets the URI from the incoming PHOTO_THUMBNAIL_URI
                    thumbUri = Uri.parse(photoData);
                } else {
                    // Prior to Android 3.0, constructs a photo Uri using _ID
                /*
                 * Creates a contact URI from the Contacts content URI
                 * incoming photoData (_ID)
                 */
                    final Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_URI, photoData);
                /*
                 * Creates a photo URI by appending the content URI of
                 * Contacts.Photo.
                 */
                    thumbUri = Uri.withAppendedPath(contactUri, Contacts.Photo.CONTENT_DIRECTORY);
                }

                /*
                 * Retrieves an AssetFileDescriptor object for the thumbnail
                 * URI
                 * using ContentResolver.openAssetFileDescriptor
                 */
                    afd = DisplayingQuickContactBadgeActivity.this.getContentResolver().openAssetFileDescriptor(thumbUri, "r");

                /*
                 * Gets a file descriptor from the asset file descriptor.
                 * This object can be used across processes.
                 */
                    FileDescriptor fileDescriptor = afd.getFileDescriptor();

                // Decode the photo file and return the result as a Bitmap
                // If the file descriptor is valid
                if (fileDescriptor != null) {
                    // Decodes the bitmap
                    return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, null);
                }
                // If the file isn't found
            } catch (FileNotFoundException e) {
                /*
                 * Handle file not found errors
                 */
                // In all cases, close the asset file descriptor
            } finally {
                if (afd != null) {
                    try {
                        afd.close();
                    } catch (IOException e) {}
                }
            }
            return null;
        }
    }
}

三、心得

每一个知识点都有最少必要知识,找出最少必要知识才是解决问题的关键。

猜你喜欢

转载自blog.csdn.net/zjh_1110120/article/details/72891797