MTKRecipientEditTextView很明显就是拿原生的RecipientEditTextView修改而成,加入了不少功能,代码量直接翻倍,6263行。原理和原生的差不多,本文只整理新增的代码。
新增的两个大功能
构造方法中会调用configFeatures。
/// M: for chip watcher. @{
configFeatures(context);
/// @}
添加了两个新功能
private void configFeatures(Context context) {
if ("com.android.mms".equals(context.getPackageName()) ||
"com.mediatek.rcs.message".equals(context.getPackageName())) { //短信和融合短信应用的话启动新的特性
mFeatureSet |= F_CHIP_AUTO_UPDATE | F_CHIP_WATCHER;
}
if ((mFeatureSet & F_CHIP_AUTO_UPDATE) != 0) {
if (sContactObserver == null) {
sContactObserver = new MTKContactObserver(getContext()); //联系人更新
}
}
if ((mFeatureSet & F_CHIP_WATCHER) != 0) {
mLastStringChanged = false;
mChoreographer = Choreographer.getInstance();
mChoreographer.postCallback(
Choreographer.CALLBACK_INPUT, notifyChipChangedRunnable, null); // 依据垂直同步信号通知chip数据变化
isRegisterVSync = true;
}
}
联系人更新
添加了对联系人数据库变化的监听扥Observer
static private MTKContactObserver sContactObserver = null; //mtk新增类,监听联系人数据库变化
还有回调
private MTKContactObserver.ContactListener mContactListener = new MTKContactObserver.ContactListener() {
@Override
public void onContactChange(Set s) {
Log.d("client", s.toString());
handleContactChange(s);
}
};
在onAttachedToWindow和onDetachedFromWindow中完成回调的注册和卸载
private void handleContactChange(Set s) {
...
i = s.iterator();
e = (MTKContactObserver.DirtyContactEvent) i.next();
switch(e.eventType) {
case MTKContactObserver.DirtyContactEvent.DELETE :
postHandleContactDelete(deletedIDs);
break;
case MTKContactObserver.DirtyContactEvent.ADD :
postHandleContactAdd(chips);
break;
case MTKContactObserver.DirtyContactEvent.UPDATE :
postHandleContactUpdate(chips);
break;
}
}
处理变化时会分为三种情况(删除、新加和更新)分别处理,后续不再继续分析,这部分代码是够长的
对外通知chips数据变化
private Runnable notifyChipChangedRunnable = new Runnable() {
@Override
public void run() {
if (changedChipAddresses.size() != 0 || mLastStringChanged == true) {
notifyChipChanged();
...
}
...
}
};
notifyChipChanged最终会触发已注册的回调,回调是ChipWatcher
public interface ChipWatcher {
public void onChipChanged(ArrayList<RecipientEntry> allChips, ArrayList<String> changedChipAddresses, String lastString);
}
private ArrayList<ChipWatcher> mChipChangedListeners;
public void addChipChangedListener(ChipWatcher watcher) { //注册
if (null == mChipChangedListeners) {
mChipChangedListeners = new ArrayList<ChipWatcher>();
}
mChipChangedListeners.add(watcher);
}
public void removeChipChangedListener(ChipWatcher watcher) { //卸载
if (mChipChangedListeners != null) {
int index = mChipChangedListeners.indexOf(watcher);
if (mChipChangedListeners.indexOf(watcher) >= 0) {
mChipChangedListeners.remove(index);
}
}
}
除了在构造方法中,还有registerVSync,该方法是在chip数据有变化的时候调用,让通知事件在同步信号到来时触发
private void registerVSync() {
if (false == isRegisterVSync && mChoreographer != null) {
mChoreographer.postCallback(
Choreographer.CALLBACK_INPUT, notifyChipChangedRunnable, null);
isRegisterVSync = true;
}
}
其它的变化
其它的变化比较零碎,没法系统整理
配置变化事件处理
protected void onConfigurationChanged(Configuration newConfig) {
if (isPhoneQuery()) {
registerGlobalLayoutListener();
}
...
}
该方法中主要处理了横屏和竖屏的变化
private void registerGlobalLayoutListener() {
ViewTreeObserver viewTreeObs = getViewTreeObserver();
if (mGlobalLayoutListener == null) {
mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
...
boolean isPortrait = (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT);
if (isPortrait) {
rotateToPortrait();
} else {
rotateToLandscape();
}
requestLayout();
...
}
};
viewTreeObs.addOnGlobalLayoutListener(mGlobalLayoutListener);
}
}
中文分隔符
private static final char COMMIT_CHAR_CHINESE_COMMA = '\uFF0C'; /// M: Support chinese comma as seperator
private static final char COMMIT_CHAR_CHINESE_SEMICOLON = '\uFF1B'; /// M: Support chinese semicolon as seperator
中文的逗号和分号也能作为分隔符了,妈妈不怕我打错半角还是全角符号了...
onRestoreInstanceState
onRestoreInstanceState中加入了恢复chips的方法。
public void onRestoreInstanceState(Parcelable state) {
...
String text = getText().toString();
...
MTKRecipientList recipientList = new MTKRecipientList(); //mtk新加的类,就是个List容器
...
while ((tokenEnd = mTokenizer.findTokenEnd(text, tokenStart)) < text.length()) {
String destination = text.substring(tokenStart, tokenEnd);
tokenStart = tokenEnd + 2;
recipientList.addRecipient(tokenizeName(destination), isPhoneNumber(destination) ? destination : tokenizeAddress(destination));
x++;
}
appendList(recipientList); //依据列表恢复UI
...
}
public void appendList(MTKRecipientList recipientList) {
...
for (int x = 0; x < recipientCnt; x++) {
MTKRecipient recipient = recipientList.getRecipient(x);
String text = recipient.getFormatString();
...
if (!TextUtils.isEmpty(displayString)
&& TextUtils.getTrimmedLength(displayString) > 0) {
mPendingChipsCount++;
mPendingChips.add(text.toString()); //使用google原生的成员
}
}
...
}
...
if (mPendingChipsCount > 0) {
postHandlePendingChips(); //就是使用google原生的方法恢复UI
}
mHandler.post(mAddTextWatcher);
}
createSelectedChip
注意这个返回的是Bitmap,名字命名的有点问题
private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint)
private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint,
boolean leaveBlankIconSpacer)
这样就选中和未选中的chip显示上区别就很大了
宽松号码匹配
commitChip方法中,加入了号码宽松匹配的逻辑,
for (int itemCnt = 0; itemCnt < adapterCount; itemCnt++) {
RecipientEntry entry = (RecipientEntry) getAdapter().getItem(itemCnt);
String displayName = entry.getDisplayName().toLowerCase();
String destination = entry.getDestination();
if (entry.getDestinationKind() == RecipientEntry.ENTRY_KIND_PHONE) {
String currentNumber = PhoneNumberUtils.normalizeNumber(text);
String queryNumber = PhoneNumberUtils.normalizeNumber(destination);
if (PhoneNumberUtils.compare(currentNumber, queryNumber)) {
printDebugLog(TAG, "[commitChip] match normalized destination. submit item: " + itemCnt);
submitItemAtPosition(itemCnt);
dismissDropDown();
return true;
}
}
}
bringPointIntoView
重写了bringPointIntoView,这个方法可以让多行的TextView滚动到指定位置,mtk添加了强制和禁止使用该方法的逻辑 public boolean bringPointIntoView(int offset) {
Log.d(TAG, "bringPointIntoView = " + offset);
if (mForceEnableBringPointIntoView) {
/// M: This case is for during expand or handlePendingChips
/// force to scroll to botton since and temporary disable the chip touching functionality
return super.bringPointIntoView(offset);
} else if (mDisableBringPointIntoView || mSelectedChip != null) {
return false;
} else {
return super.bringPointIntoView(offset);
}
}
chip显示调整
相关的函数如下:
private void tryToAdjustChips() //联系人添加或者删除的时候使用
private void replaceChipOnSameTextRange(DrawableRecipientChip currentChip, int newChipWidth) //替换同一个chip,但是宽度有变化
replaceChipOnSameTextRange只处理第一个chip,如果宽度有限制的话,会缩短第一个chip的宽度并显示省略号,以容纳后续的chip显示
AsyncTask
新增了4个新的AsyncTask
private class PhoneNumberQueryAndReplacementTask extends AsyncTask<RecipientEntry, Void, Void> //commitChip中使用,匹配列表为0个时依据号码创建可能的chip
private class DuplicateContactReplacementTask extends AsyncTask<Object, Void, Void> //删除重复联系人
private class DeleteContactTask extends AsyncTask<List<Long>, Object, HashMap<DrawableRecipientChip, DrawableRecipientChip>> //处理数据库删除联系人的情况
private class PreloadPhotoTask extends AsyncTask<Collection<RecipientEntry>, Void, Void> //预读多个联系人的头像