快速联系人标记到底是什么?废话不多说,直接上图:
简单的说,就是将 QuickContactBadge 这个控件和联系人信息绑定起来,以达到点击相应联系人头像时,弹出一个 Dialog,在此 Dialog 中显示当前联系人的相关信息,同时这里的每一个信息又都是可点击的,点击之后,系统会自动匹配可以响应的 App,示例中点击了邮箱地址之后直接跳到创建新邮件界面。除此之外,点击此 Dialog 的标题栏,会直接跳到联系人详情界面。接下来,让我们看看此功能是什么实现的。
一、界面组成及原理介绍
从效果图中,我们不难看出,界面的组成相当简单:一个 ListView,一个 Dialog。ListView 中每一个 Item 中包含一个 ImageView 和 一个 TextView;Dialog 中是一个 ViewPager,ViewPager 的每一个界面元素是不同的联系人信息。
表面上看,大概就是这个样子,但实际上,有一个地方是不对的,那就是 ListView 中的每一个 Item 布局中的图片显示控件用的不是 ImageView,而是 QuickContactBadge。QuickContactBadge 是个什么鬼?想必很多人直接懵逼了,但如果我把这个控件指给你,你一定会骂娘的……
没错,就是这货!
可别小看了这个控件的功能,就是因为有了它,我们才可以省去很多工作(关联此图片对应的联系人、填充 ViewPager 每个页面的数据并决定用可以响应不同点击事件的 App,blabla……)。
QuickContactBadge 中有两个函数十分关键:
1.assignContactUri
指定关联的联系人信息
2.setImageBitmap
指定 QuickContactBadge 显示的图片资源
知道了这些之后,其实实现起来也就没什么难度了,无非就两件事:
1.获取联系人数据
2.将 QuickContactBadge 和相应的联系人数据绑定起来
二、实现
1.获取联系人数据
本例中,是通过查询匹配用户名信息的方法获取联系人列表的,主要涉及到的的知识有:
- LoaderManager.LoaderCallbacks(查询 Contact Provider 数据)
- 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;
}
}
}
三、心得
每一个知识点都有最少必要知识,找出最少必要知识才是解决问题的关键。