Android的通话,短信和联系人,每个模块都会有一个重量级的自定义控件,代码量是千行起,文件一般也有多个。通话是GlowPadView,短信就是RecipientEditTextView了,联系人的卖个关子以后会介绍。三个中最复杂的就是RecipientEditTextView,都单独编译成了一个静态库了,除了之前介绍的相关类外,RecipientEditTextView.java自身就有3000+的代码行数,mtk修改后的MtkRecipientEditTextView代码行数直逼7000行。控件使用起来并不难,不过有问题了要修改或者优化就很麻烦了。
本文从控件的各种操作角度入手来分析代码
RecipientTextWatcher
输入文字是最正常的操作,处理该操作的就是RecipientTextWatcher,该成员在构造方法中初始化。
mTextWatcher = new RecipientTextWatcher();
addTextChangedListener(mTextWatcher);
在某些方法中有处理字符串操作会先取消TextWatcher的监听,处理完毕后再恢复监听,mAddTextWatcher就是处理这个的。
private final Runnable mAddTextWatcher = new Runnable() {
@Override
public void run() {
if (mTextWatcher == null) {
mTextWatcher = new RecipientTextWatcher();
addTextChangedListener(mTextWatcher);
}
}
};
onTextChanged
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (before - count == 1) {
//按了删除键,如果之前是一个chip的话,删除整个chip
int selStart = getSelectionStart();
DrawableRecipientChip[] repl = getSpannable().getSpans(selStart, selStart,
DrawableRecipientChip.class);
if (repl.length > 0) {
// There is a chip there! Just remove it.
DrawableRecipientChip toDelete = repl[0];
Editable editable = getText();
// Add the separator token.
int deleteStart = editable.getSpanStart(toDelete);
int deleteEnd = editable.getSpanEnd(toDelete) + 1;
if (deleteEnd > editable.length()) {
deleteEnd = editable.length();
}
editable.removeSpan(toDelete);
editable.delete(deleteStart, deleteEnd);
}
} else if (count > before) {
//新添字符,当前所选chip不是联系人,且新增字符串的最后一位是分隔符,则新提交一个chip
if (mSelectedChip != null
&& isGeneratedContact(mSelectedChip)) {
if (lastCharacterIsCommitCharacter(s)) {
commitByCharacter();
return;
}
}
}
}
afterTextChanged
@Override
public void afterTextChanged(Editable s) {
// If the text has been set to null or empty, make sure we remove
// all the spans we applied.
if (TextUtils.isEmpty(s)) { //为空
// Remove all the chips spans.
Spannable spannable = getSpannable();
DrawableRecipientChip[] chips = spannable.getSpans(0, getText().length(),
DrawableRecipientChip.class);
for (DrawableRecipientChip chip : chips) {
spannable.removeSpan(chip); //删除所有的span
}
if (mMoreChip != null) {
spannable.removeSpan(mMoreChip); //删除mMoreChip
}
clearSelectedChip(); //清除mSelectedChip
return;
}
// Get whether there are any recipients pending addition to the
// view. If there are, don't do anything in the text watcher.
if (chipsPending()) { //当前有chip正在添加,返回
return;
}
// If the user is editing a chip, don't clear it.
if (mSelectedChip != null) { //当前有所选chip
if (!isGeneratedContact(mSelectedChip)) { //所选chip没有对应联系人转到编辑状态
setCursorVisible(true); //设置光标可见
setSelection(getText().length()); //设置光标在控件末尾
clearSelectedChip();//清除mSelectedChip
} else {
return;
}
}
int length = s.length();
// Make sure there is content there to parse and that it is
// not just the commit character.
if (length > 1) {//字符串长度大于1
if (lastCharacterIsCommitCharacter(s)) {//s末尾为分隔符
commitByCharacter(); //提交chip
return;
}
char last;
int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
int len = length() - 1;
if (end != len) {
last = s.charAt(end);
} else {
last = s.charAt(len);
}
if (last == COMMIT_CHAR_SPACE) { //末尾字符为空格
if (!isPhoneQuery()) { //输入为邮件地址
// Check if this is a valid email address. If it is,
// commit it.
String text = getText().toString();
int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
tokenStart));
if (isValidEmailAddress(sub)) { //判断是否为合法邮件地址
commitByCharacter(); //提交chip
}
}
}
}
}
焦点的获取与失去
public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
super.onFocusChanged(hasFocus, direction, previous);
if (!hasFocus) {
shrink();
} else {
expand();
}
}
失去焦点后如果chip较多会收缩控件显示,只显示前几个chip并显示mMoreChip;获取焦点的时候会展开显示所有chip并且去除mMoreChip。
shrink
private void shrink() {
...
long contactId = mSelectedChip != null ? mSelectedChip.getEntry().getContactId() : -1;
if (mSelectedChip != null && contactId != RecipientEntry.INVALID_CONTACT
&& (!isPhoneQuery() && contactId != RecipientEntry.GENERATED_CONTACT)) {
clearSelectedChip(); //所选chip有对应联系人,清除mSelectedChip
} else {
if (getWidth() <= 0) { //width为0,证明控件显示流程还没有走完
mHandler.removeCallbacks(mDelayedShrink);
if (getVisibility() == GONE) {
mRequiresShrinkWhenNotGone = true;
} else {
mHandler.post(mDelayedShrink); //延后运行mDelayedShrink
}
return;
}
if (mPendingChipsCount > 0) {
postHandlePendingChips(); //延后处理PendingChips
} else {
Editable editable = getText();
int end = getSelectionEnd(); //字符串末尾
int start = mTokenizer.findTokenStart(editable, end); //从末尾开始往前找token的开始位置
DrawableRecipientChip[] chips =
getSpannable().getSpans(start, end, DrawableRecipientChip.class); //寻找chips
if ((chips == null || chips.length == 0)) { //当前没有chip
Editable text = getText();
int whatEnd = mTokenizer.findTokenEnd(text, start);
if (whatEnd < text.length() && text.charAt(whatEnd) == ',') {
whatEnd = movePastTerminators(whatEnd); //已格式化字符的特殊处理
}
int selEnd = getSelectionEnd();
if (whatEnd != selEnd) {
handleEdit(start, whatEnd); //selection end和token end不等,设置selection end为token end然后提交chip
} else {
commitChip(start, end, editable); //提交chip
}
}
}
mHandler.post(mAddTextWatcher);
}
createMoreChip(); //添加mMoreChip,收缩的主要实现都在这个方法中,之前的只是些预处理
}
expand
private void expand() {
...
removeMoreChip(); //移除mMoreChip
setCursorVisible(true); //光标可见
Editable text = getText();
setSelection(text != null && text.length() > 0 ? text.length() : 0); //光标位于控件末尾
if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
new RecipientReplacementTask().execute(); //这种情况是处理收缩态可能未处理的chips,收缩态是只处理前两个chip的
mTemporaryRecipients = null;
}
}
moreChip相关
void createMoreChip() {
...
ReplacementDrawableSpan[] tempMore = getSpannable().getSpans(0, getText().length(),
MoreImageSpan.class);
if (tempMore.length > 0) {
getSpannable().removeSpan(tempMore[0]); //如果已存在morechip,移除
}
DrawableRecipientChip[] recipients = getSortedRecipients();
if (recipients == null || recipients.length <= CHIP_LIMIT) { //如果chip数目小于2,直接返回
mMoreChip = null;
return;
}
Spannable spannable = getSpannable();
int numRecipients = recipients.length; //chip 总数
int overage = numRecipients - CHIP_LIMIT; //不显示的chip总数
MoreImageSpan moreSpan = createMoreSpan(overage);
mRemovedSpans = new ArrayList<DrawableRecipientChip>();
int totalReplaceStart = 0;
int totalReplaceEnd = 0;
Editable text = getText();
for (int i = numRecipients - overage; i < recipients.length; i++) {
mRemovedSpans.add(recipients[i]); //添加要移除的chip
if (i == numRecipients - overage) {
totalReplaceStart = spannable.getSpanStart(recipients[i]);
}
if (i == recipients.length - 1) {
totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
}
if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) {
int spanStart = spannable.getSpanStart(recipients[i]);
int spanEnd = spannable.getSpanEnd(recipients[i]);
recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd)); //设置为对应的原始字符串
}
spannable.removeSpan(recipients[i]); // 清除对应span
}
if (totalReplaceEnd < text.length()) {
totalReplaceEnd = text.length();
}
int end = Math.max(totalReplaceStart, totalReplaceEnd);
int start = Math.min(totalReplaceStart, totalReplaceEnd);
SpannableString chipText = new SpannableString(text.subSequence(start, end));
chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //把要移除显示的字符串用moreChip替代
text.replace(start, end, chipText);
mMoreChip = moreSpan;
...
}
removeMoreChip就是createMoreChip的相反过程,清除moreChip并依据mRemovedSpans恢复原有的chips。
RecipientReplacementTask
继承自AsyncTask<Void, Void, Void>
private void processReplacements(final List<DrawableRecipientChip> recipients,
final List<DrawableRecipientChip> replacements)
recipients和replacements是一一对应的关系,它们不仅size相等,而且对应的通信地址也是相等的。该方法主体就是一个循环,如果replacements对应的chip是更好的匹配,就替换recipients中的对应chip。
protected void onPreExecute() {
final List<DrawableRecipientChip> originalRecipients =
new ArrayList<DrawableRecipientChip>();
final DrawableRecipientChip[] existingChips = getSortedRecipients();
Collections.addAll(originalRecipients, existingChips);
if (mRemovedSpans != null) {
originalRecipients.addAll(mRemovedSpans);
}
final List<DrawableRecipientChip> replacements =
new ArrayList<DrawableRecipientChip>(originalRecipients.size());
for (final DrawableRecipientChip chip : originalRecipients) {
if (RecipientEntry.isCreatedRecipient(chip.getEntry().getContactId())
&& getSpannable().getSpanStart(chip) != -1) {
replacements.add(createFreeChip(chip.getEntry()));
} else {
replacements.add(null);
}
}
processReplacements(originalRecipients, replacements);
}
本段代码是把某些InvisibleRecipientChip替换成VisibleRecipientChip,替换不会导致超出上限。该方法运行在UI线程,目的是先显示chips,然后再后台刷新。
protected Void doInBackground(Void... params) {
...
final BaseRecipientAdapter adapter = getAdapter();
adapter.getMatchingRecipients(addresses, new RecipientMatchCallback() {
@Override
public void matchesFound(Map<String, RecipientEntry> entries) {
...
processReplacements(recipients, replacements);
}
@Override
public void matchesNotFound(final Set<String> unfoundAddresses) {
...
processReplacements(recipients, replacements);
}
});
return null;
}
使用BaseRecipientAdapter的getMatchingRecipients查询数据库,并替换相应的chip。
RecipientReplacementTask的功能就是chip的异步更新。
触摸事件
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
...
if (mCopyAddress == null && action == MotionEvent.ACTION_UP) {
float x = event.getX();
float y = event.getY();
int offset = putOffsetInRange(x, y);
DrawableRecipientChip currentChip = findChip(offset); //找到点击点对应的chip
if (currentChip != null) {
if (mSelectedChip != null && mSelectedChip != currentChip) { //mSelectedChip 不为空的的情况下点击了另一个chip
clearSelectedChip(); //清除mSelectedChip
selectChip(currentChip); //切换到点击的chip
} else if (mSelectedChip == null) {
commitDefault(); //依据选择范围生成可能的chip
selectChip(currentChip); //切换到点击的chip
} else {
onClick(mSelectedChip); //click事件,实际就是清除mSelectedChip
}
chipWasSelected = true;
handled = true;
} else if (mSelectedChip != null && shouldShowEditableText(mSelectedChip)) {
chipWasSelected = true;
}
}
if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
clearSelectedChip();
}
return handled;
}
clearSelectedChip
public void clearSelectedChip() {
if (mSelectedChip != null) {
unselectChip(mSelectedChip);
mSelectedChip = null;
}
...
}
private void unselectChip(DrawableRecipientChip chip) {
int start = getChipStart(chip);
int end = getChipEnd(chip);
Editable editable = getText();
mSelectedChip = null;
if (start == -1 || end == -1) { //chip不存在,或者是用户正在编辑
Log.w(TAG, "The chip doesn't exist or may be a chip a user was editing");
setSelection(editable.length());
commitDefault(); //从字符串最后开始向前找第一个chip提交
} else {
getSpannable().removeSpan(chip); //删除chip
QwertyKeyListener.markAsReplaced(editable, start, end, "");
editable.removeSpan(chip);
try {
if (!mNoChips) {
editable.setSpan(constructChipSpan(chip.getEntry()),
start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //重新提交chip
}
} catch (NullPointerException e) {
Log.e(TAG, e.getMessage(), e);
}
}
...
}
unselectChip中的重新提交貌似没啥意义,因为原生代码中选中和不选中UI上没啥区别,mtk在这点上做了修改。
selectChip
private void selectChip(DrawableRecipientChip currentChip) {
if (shouldShowEditableText(currentChip)) { //当前chip无对应联系人,直接进入编辑状态
CharSequence text = currentChip.getValue();
Editable editable = getText();
Spannable spannable = getSpannable();
int spanStart = spannable.getSpanStart(currentChip);
int spanEnd = spannable.getSpanEnd(currentChip);
spannable.removeSpan(currentChip); //删除chip
// Don't need leading space if it's the only chip
if (spanEnd - spanStart == editable.length() - 1) {
spanEnd++;
}
editable.delete(spanStart, spanEnd); //删除对应内容
setCursorVisible(true); //显示光标
setSelection(editable.length()); //selection移动到末尾
editable.append(text); //添加原chip相关字符串
mSelectedChip = constructChipSpan(
RecipientEntry.constructFakeEntry((String) text, isValid(text.toString()))); //赋值新的chip到mSelectedChip
} else {
final boolean showAddress =
currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT ||
getAdapter().forceShowAddress();
if (showAddress && mNoChips) {
return;
}
mSelectedChip = currentChip;
setSelection(getText().getSpanEnd(mSelectedChip));
setCursorVisible(false);
if (showAddress) {
showAddress(currentChip, mAddressPopup); //弹出dialog,电子邮件地址可能会走这里,删除当前地址
} else {
showAlternates(currentChip, mAlternatesPopup); //弹出dialong,以便替换当前号码
}
}
}
手势操作
private GestureDetector mGestureDetector;
构造方法中
mGestureDetector = new GestureDetector(context, this);
实现手势接口的就是RecipientEditTextView类本身,实现的主要是长按事件
public void onLongPress(MotionEvent event) {
...
if (currentChip != null) {
if (mDragEnabled) {
// Start drag-and-drop for the selected chip.
startDrag(currentChip); //拖拽操作
} else {
// Copy the selected chip email address.
showCopyDialog(currentChip.getEntry().getDestination()); //显示拷贝地址的对话框
}
}
}
drag
private void startDrag(DrawableRecipientChip currentChip) {
String address = currentChip.getEntry().getDestination();
ClipData data = ClipData.newPlainText(address, address + COMMIT_CHAR_COMMA);
startDrag(data, new RecipientChipShadow(currentChip), null, 0); //启动拖拽模式
removeChip(currentChip); //删除拖拽的chip
}
@Override
public boolean onDragEvent(@NonNull DragEvent event) {
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
case DragEvent.ACTION_DRAG_ENTERED:
requestFocus();
return true;
case DragEvent.ACTION_DROP:
handlePasteClip(event.getClipData()); //粘贴拖拽的chip
return true;
}
return false;
}
paste
粘贴操作除了拖拽外,控件菜单选择粘贴选项也可以触发
void handlePasteClip(ClipData clip) {
...
final ClipDescription clipDescription = clip.getDescription();
for (int i = 0; i < clip.getItemCount(); i++) {
...
final CharSequence pastedItem = clip.getItemAt(i).getText();
if (!TextUtils.isEmpty(pastedItem)) {
final Editable editable = getText();
final int start = getSelectionStart();
final int end = getSelectionEnd();
if (start < 0 || end < 1) {
// No selection.
editable.append(pastedItem);
} else if (start == end) {
// Insert at position.
editable.insert(start, pastedItem);
} else {
editable.append(pastedItem, start, end);
}
handlePasteAndReplace();
}
}
...
}
把字符串添加到editable,然后调用handlePasteAndReplace
private void handlePasteAndReplace() {
ArrayList<DrawableRecipientChip> created = handlePaste(); //首先获得chips
if (created != null && created.size() > 0) {
IndividualReplacementTask replace = new IndividualReplacementTask(); //然后启动异步更新
replace.execute(created);
}
}
handlePaste依据selection范围,生成chips结构,不再具体分析其代码。
IndividualReplacementTask类似于RecipientReplacementTask,同样使用getMatchingRecipients方法更新数据,但是在回调中最终走的是replaceChip方法
replaceChip
replaceChip不只在IndividualReplacementTask中,在selectChip小节中替换当前地址也是用的该方法。 void replaceChip(DrawableRecipientChip chip, RecipientEntry entry) {
...
getSpannable().removeSpan(chip); //删除chip
Editable editable = getText();
CharSequence chipText = createChip(entry); //新建chip
if (chipText != null) {
if (start == -1 || end == -1) {
Log.e(TAG, "The chip to replace does not exist but should.");
editable.insert(0, chipText); //原chip不存在就直接插入
} else {
if (!TextUtils.isEmpty(chipText)) {
...
editable.replace(start, toReplace, chipText); //替换chip
}
}
}
...
}
按键事件
onKeyUp
public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_TAB:
if (event.hasNoModifiers()) {
if (mSelectedChip != null) {
clearSelectedChip();
} else {
commitDefault();
}
}
break;
}
return super.onKeyUp(keyCode, event);
}
onKeyDown
public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) { //删除键的处理
if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
mAlternatesPopup.dismiss();
}
removeChip(mSelectedChip); //删除当前chip
}
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
case KeyEvent.KEYCODE_DPAD_CENTER:
if (event.hasNoModifiers()) {
if (commitDefault()) { //回车键的处理,提交默认chip
return true;
}
if (mSelectedChip != null) {
clearSelectedChip(); //清除选择项
return true;
} else if (focusNext()) {
return true;
}
}
break;
}
return super.onKeyDown(keyCode, event);
}
removeChip
删除chip的方法之前拖拽小节也用过 /* package */void removeChip(DrawableRecipientChip chip) {
Spannable spannable = getSpannable();
...
spannable.removeSpan(chip);
if (spanStart >= 0 && toDelete > 0) {
text.delete(spanStart, toDelete);
}
...
}
太简单没啥好讲的了。
输入法确认按键
@Override
public boolean onEditorAction(TextView view, int action, KeyEvent keyEvent) {
if (action == EditorInfo.IME_ACTION_DONE) {
if (commitDefault()) { //提交默认chip
return true;
}
...
}
}
系统接口和回调
onSizeChanged
@Override
public void onSizeChanged(int width, int height, int oldw, int oldh) {
super.onSizeChanged(width, height, oldw, oldh);
if (width != 0 && height != 0) {
if (mPendingChipsCount > 0) {
postHandlePendingChips(); //延时处理PendingChips
} else {
checkChipWidths(); //处理chip宽度的变化
}
}
...
}
private void checkChipWidths() {
// Check the widths of the associated chips.
DrawableRecipientChip[] chips = getSortedRecipients();
if (chips != null) {
Rect bounds;
for (DrawableRecipientChip chip : chips) {
bounds = chip.getBounds();
if (getWidth() > 0 && bounds.right - bounds.left >
getWidth() - getPaddingLeft() - getPaddingRight()) {
// Need to redraw that chip.
replaceChip(chip, chip.getEntry()); //数据无变化,其实就是重新绘制chip
}
}
}
}
checkChipWidths的功能就是重新绘制chip
append
append是TextView定义的方法,这个是提供给控件使用者的方法,用于往控件中添加字符串。RecipientEditTextView要想保持自定义的UI当然要重写这个方法
@Override
public void append(CharSequence text, int start, int end) {
if (mTextWatcher != null) {
removeTextChangedListener(mTextWatcher); //去除TextWatcher,代码中多处用到这段代码
}
super.append(text, start, end);
if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
String displayString = text.toString();
...
if (!TextUtils.isEmpty(displayString)
&& TextUtils.getTrimmedLength(displayString) > 0) {
mPendingChipsCount++;
mPendingChips.add(displayString); //PendingChips添加任务
}
}
if (mPendingChipsCount > 0) {
postHandlePendingChips(); //处理PendingChips
}
mHandler.post(mAddTextWatcher); //添加TextWatcher
}
和onSizeChanged一样使用了postHandlePendingChips
private void postHandlePendingChips() {
mHandler.removeCallbacks(mHandlePendingChips);
mHandler.post(mHandlePendingChips);
}
postHandlePendingChips只是简单的post了个Runnable
private Runnable mHandlePendingChips = new Runnable() {
@Override
public void run() {
handlePendingChips();
}
};
最终调用handlePendingChips
void handlePendingChips() {
...
synchronized (mPendingChips) {
Editable editable = getText();
if (mPendingChipsCount <= MAX_CHIPS_PARSED) {
for (int i = 0; i < mPendingChips.size(); i++) { //循环处理mPendingChips
String current = mPendingChips.get(i);
int tokenStart = editable.toString().indexOf(current);
int tokenEnd = tokenStart + current.length() - 1;
if (tokenStart >= 0) {
...
createReplacementChip(tokenStart, tokenEnd, editable, i < CHIP_LIMIT
|| !mShouldShrink); //生成对应的chip,chip会添加到mTemporaryRecipients中
}
mPendingChipsCount--;
}
sanitizeEnd();
} else {
mNoChips = true;
}
if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0
&& mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) { //有焦点,则处理全部的chips
new RecipientReplacementTask().execute();
mTemporaryRecipients = null;
} else { //无焦点则显示moreChip
// Create the "more" chip
mIndividualReplacements = new IndividualReplacementTask();
mIndividualReplacements.execute(new ArrayList<DrawableRecipientChip>( //只处理前CHIP_LIMIT个Chip
mTemporaryRecipients.subList(0, CHIP_LIMIT)));
if (mTemporaryRecipients.size() > CHIP_LIMIT) { //不显示的chip都添加到mTemporaryRecipients中
mTemporaryRecipients = new ArrayList<DrawableRecipientChip>(
mTemporaryRecipients.subList(CHIP_LIMIT,
mTemporaryRecipients.size()));
} else {
mTemporaryRecipients = null;
}
createMoreChip();
}
} else { //数量已超限制则只添加moreChip,mTemporaryRecipients也无需赋值
mTemporaryRecipients = null;
createMoreChip();
}
mPendingChipsCount = 0;
mPendingChips.clear();
}
}
处理mTemporaryRecipients流程中的两个Task在之前的代码中都已介绍过。无焦点时的mTemporaryRecipients保留未处理的chips,这些chips在之前介绍的expand方法中再做处理。
提交Chip
commitByCharacter
private void commitByCharacter() {
// We can't possibly commit by character if we can't tokenize.
if (mTokenizer == null) {
return;
}
Editable editable = getText();
int end = getSelectionEnd();
int start = mTokenizer.findTokenStart(editable, end);
if (shouldCreateChip(start, end)) { //是否需要创建chip
commitChip(start, end, editable); //提交chip
}
setSelection(getText().length());
}
private boolean shouldCreateChip(int start, int end) {
return !mNoChips && hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
//四个条件
//1.chip数没有超出最大限制
//2.控件当前有焦点
//3.输入的字符数超过阀值
//4.start和end之间当前没有指定span
}
commitDefault
private boolean commitDefault() {
...
if (shouldCreateChip(start, end)) {
int whatEnd = mTokenizer.findTokenEnd(getText(), start);
whatEnd = movePastTerminators(whatEnd);
if (whatEnd != getSelectionEnd()) {
handleEdit(start, whatEnd); //会把selection end设置为token end,然后创建chip
return true;
}
return commitChip(start, end , editable);
}
return false;
}
和commitByCharacter类似,唯一不同的是添加了针对token end和selection end不一样时的处理。
commitChip
private boolean commitChip(int start, int end, Editable editable) {
ListAdapter adapter = getAdapter();
if (adapter != null && adapter.getCount() > 0 && enoughToFilter()
&& end == getSelectionEnd() && !isPhoneQuery()) {//控件匹配列表有值,且地址是电子邮件
if (!isValidEmailAddress(editable.toString().substring(start, end).trim())) { //当前非合法的电子邮件地址
final int selectedPosition = getListSelection();
if (selectedPosition == -1) {
// Nothing is selected; use the first item
submitItemAtPosition(0); //拿匹配列表的第一个值替换
} else {
submitItemAtPosition(selectedPosition); //拿匹配列表选中值替换
}
}
dismissDropDown();
return true;
} else {
int tokenEnd = mTokenizer.findTokenEnd(editable, start);
...
String text = editable.toString().substring(start, tokenEnd).trim();
clearComposingText();
if (text.length() > 0 && !text.equals(" ")) {
RecipientEntry entry = createTokenizedEntry(text);
if (entry != null) {
QwertyKeyListener.markAsReplaced(editable, start, end, "");
CharSequence chipText = createChip(entry); //创建chip
if (chipText != null && start > -1 && end > -1) {
editable.replace(start, end, chipText); //替换字符串
}
}
if (end == getSelectionEnd()) {
dismissDropDown();
}
sanitizeBetween(); //消除chip之间的空格
return true;
}
}
return false;
}
createChip
commitChip, handleEdit, replaceChip和submitItemAtPosition四个方法的基本流程都是类似的,都是先用createChip生成chip,然后insert或者replace字符串。有四个方法基本流程类似可以看出RecipientEditTextView的代码冗余程度挺高的,有失google的水准。 private CharSequence createChip(RecipientEntry entry) {
...
DrawableRecipientChip chip = constructChipSpan(entry);
chipText.setSpan(chip, 0, textLength,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
chip.setOriginalText(chipText.toString());
...
}
private DrawableRecipientChip constructChipSpan(RecipientEntry contact) {
...
Bitmap tmpBitmap = createChipBitmap(contact, paint); //生成chip所要显示的图片
Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
VisibleRecipientChip recipientChip =
new VisibleRecipientChip(result, contact);//创建chip
...
}
最重要的其实就是创建图片
private Bitmap createChipBitmap(RecipientEntry contact, TextPaint paint) {
paint.setColor(getDefaultChipTextColor(contact));
ChipBitmapContainer bitmapContainer = createChipBitmap(contact, paint,
getChipBackground(contact), getDefaultChipBackgroundColor(contact)); //绘制背景和名称,确认头像位置和大小
if (bitmapContainer.loadIcon) {
loadAvatarIcon(contact, bitmapContainer); //异步读取头像数据
}
return bitmapContainer.bitmap;
}
private ChipBitmapContainer createChipBitmap(RecipientEntry contact, TextPaint paint,
Drawable overrideBackgroundDrawable, int backgroundColor) {
...
canvas.drawRoundRect(new RectF(0, 0, width, height), radius, radius,
mWorkPaint); //绘制背景
...
canvas.drawText(ellipsizedText, 0, ellipsizedText.length(),
textX, getTextYOffset(height), paint); //绘制名称
int iconX = shouldPositionAvatarOnRight() ? width - backgroundPadding.right - iconWidth :
backgroundPadding.left; //设置头像位置和大小
result.left = iconX;
result.top = backgroundPadding.top;
result.right = iconX + iconWidth;
result.bottom = height - backgroundPadding.bottom;
return result;
}
loadAvatarIcon就是使用PhotoManager异步读取头像数据,不再分析
总结
RecipientEditTextView从原理上讲并不复杂,不过代码确实有问题;
1.首先大量代码集中在一个类中,很多工具方法可以新建文件来放置,例如isPhoneNumber这个方法和控件本身没有任何关系,它只是用来判断字符串是否为号码。类似的还有createChipBitmap,loadAvatarIcon。
2.chip的操作,其实和控件关联也很小,就是字符串的操作,如createMoreSpan,完全可以搞个新文件,例如ChipManager等。
Editable editable = getText();
通过上面一行语句就能和控件建立关系,无需其他关联。
3.一个类实现大量接口,到处是this,我怎么知道一个接口的方法到底是什么。把代码范围控制在最小比较好分析或查看代码,一个局部的代码被搞成this后就成了成员方法范围。
4.同一段代码多处冗余,commitChip, handleEdit, replaceChip和submitItemAtPosition四个方法的基本流程都是类似的,RecipientReplacementTask和IndividualReplacementTask其实也基本类似,createMoreChip和也createMoreChipPlainText类似。有removeChip方法,但是在onTextChanged中删除chip的时候却不用,还是把差不多的代码又重复了一遍。
5.chip操作入口太多了,就保留TextWatcher一个入口也能完成同样功能,可以看出大量代码就是为了效率。