联系人详情Recent列表浅析
需求:修改联系人详情界面中的CallLog记录,使其能够指示该呼叫产生于哪一张SIM卡。
原生效果.png
在Android M中,联系人详情统一到了QuickContactActivity.java这个类中。详情界面涉及到联系人的信息,Recent列表还包含CallLog,SMS,Calendar事件信息。
CallLog,SMS,Calendar事件的加载显示流程大同小异,因为本文需求是修改CallLog的图标,因此以CallLog为例。
这些数据肯定是要在Activity启动时加载的,我们在onResume中找到了startInteractionLoaders这么个方法,部分实现如下:
getLoaderManager().initLoader( LOADER_CALL_LOG_ID, phonesExtraBundle, mLoaderInteractionsCallbacks);
启动了一个Loader,ID为LOADER_CALL_LOG_ID,回调MLoaderInteractionsCallbacks,没啥说的,去回调中看看再说:
loader = new CallLogInteractionsLoader(
QuickContactActivity.this,
args.getStringArray(KEY_LOADER_EXTRA_PHONES),
MAX_CALL_LOG_RETRIEVE);
好了,这里找到了Loader的具体实现类。CallLogInteractionsLoader的后两个参数分别是电话号码和查询的最大条数。我们很关心这个Loader到底会加载哪些数据,因为要实现需求,我们需要知道呼叫记录产生的SIM卡。看看它的loadInBackground的实现:
for (String number : mPhoneNumbers) {
interactions.addAll(getCallLogInteractions(number));
}
继续查看getCallLogInteractions实现:
final Cursor cursor = getContext().getContentResolver().query(uri, null, null, null,
orderByAndLimit);
query的参数都是null。Good!要啥有啥!
我们回过头来,继续看Loader的onLoadFinished回调:
mRecentLoaderResults.put(loader.getId(), data);
if (isAllRecentDataLoaded()) {
bindRecentData();
}
嗯,把数据存储在mRecentLoaderResults中,然后bindRecentData,准备绑定view啦!
mRecentDataTask = new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
…………
// Wrap each interaction in its own list so that an icon is displayed for each entry
for (Entry contactInteraction : contactInteractionsToEntries(allInteractions)) {
List<Entry> entryListWrapper = new ArrayList<>(1);
entryListWrapper.add(contactInteraction);
interactionsWrapper.add(entryListWrapper);
}
Trace.endSection();
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
Trace.beginSection("initialize recents card");
if (allInteractions.size() > 0) {
mRecentCard.initialize(interactionsWrapper,
/* numInitialVisibleEntries = */ MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN,
/* isExpanded = */ mRecentCard.isExpanded(), /* isAlwaysExpanded = */ false,
mExpandingEntryCardViewListener, mScroller);
mRecentCard.setVisibility(View.VISIBLE);
}
…………
}
};
在bindRecentData中是用Asynctask先对查找结果按日期排序了,将排序结果从contactInteractions转换成了Entry,存入interactionWrapper中,然后在onPostExecute中来初始化mRecentCard。初始话的工作无非就是各种赋初值,然后设置监听啊等。在上面新建Loader实力的时候,是传入了一个context的,使用这个context创建了一个LayoutInflater,这里才是view真正产生的地方,代码如下:
entryViewList.add(createEntryView(layoutInflater, entryList.get(0), /* showIcon = */ View.VISIBLE));
createEntryView这个方法比较长,都是view里的内容的设置,包括icon,header等等。这里猜测我们要改的就是Icon了。为了验证,我们用HierarchyViewer来查看一下:
ExpandingEntryCardView.png
嗯,没毛病,看来修改icon的资源文件就行了。来看看开始是怎么样的:
final ImageView icon = (ImageView) view.findViewById(R.id.icon);
icon.setVisibility(iconVisibility);
if (entry.getIcon() != null) {
icon.setImageDrawable(entry.getIcon());
}
是从entry获取的。这个Entry是ExpandingEntryCardView的一个内部类。这里我们又要回头去看看这个entry是何时用了啥资源创建出来的了。前文在bindRecentData的时候有提到过从contactInteractions转为Entry,没有细看,这里我们要看看他的实现了:
private List<Entry> contactInteractionsToEntries(List<ContactInteraction> interactions) {
final List<Entry> entries = new ArrayList<>();
for (ContactInteraction interaction : interactions) {
if (interaction == null) {
continue;
}
entries.add(new Entry(/* id = */ -1,
interaction.getIcon(this),
interaction.getViewHeader(this),
interaction.getViewBody(this),
interaction.getBodyIcon(this),
interaction.getViewFooter(this),
interaction.getFooterIcon(this),
interaction.getContentDescription(this),
interaction.getIntent(),
/* alternateIcon = */ null,
/* alternateIntent = */ null,
/* alternateContentDescription = */ null,
/* shouldApplyColor = */ true,
/* isEditable = */ false,
/* EntryContextMenuInfo = */ null,
/* thirdIcon = */ null,
/* thirdIntent = */ null,
/* thirdContentDescription = */ null,
/* thirdAction = */ Entry.ACTION_NONE,
/* thirdActionExtras = */ null,
interaction.getIconResourceId()));
}
return entries;
}
发现就是重新把数据换个类存放而已。这里Interaction是接口,CallLog,SMS,Calendar有各自的实现,CallLogInteraction的getIcon实现如下:
return context.getResources().getDrawable(CALL_LOG_ICON_RES);
如此直白。依据需求我们根据SIM卡号设置对应资源文件就行了。
if (mValues.getAsInteger(Calls.SLOT_ID) == 0)
return context.getResources().getDrawable(CALL_LOG_ICON_RES_SIM1);
else
return context.getResources().getDrawable(CALL_LOG_ICON_RES_SIM2);
修改后效果.png