拨号搜索机制分为两个部分,引导搜索和搜索。其中引导搜索是指,从用户输入到开始搜索之间的流程,而搜索部分是指,从数据库搜索字符串的过程。
一、引导搜索部分
默认的拨号界面的布局从上到下主要分为3个部分:显示列表、数字编辑框、拨号键盘。他们的作用是:用户直接在拨号键盘上输入数字,然后数字编辑框显示所输入的数字,同时在显示列表中体现此时的搜索结果。如图所示:
从流程上来讲,需要拨号键盘将用户点击转换为按键事件并传递给编辑框,然后由编辑框传递给搜索框,再由搜索框传递给列表Fragment,然后在列表所加载的Adapter中体现当前的搜索结果。
接下来我们详细分析这个过程。
1.1、从拨号键盘到编辑框
用户在拨号键盘上的点击的数字按钮,都会在编辑框中体现出来,我们先来追踪这一过程。
每个拨号键盘按钮都是DialpadKeyButton类型的View,他们继承自FrameLayout,当遇到点击事件时,就会触发setPressed()方法:
@setPressed
public void setPressed(boolean pressed) {
super.setPressed(pressed);
if (mOnPressedListener != null) {
mOnPressedListener.onPressed(this, pressed);
}
}
然后将事件转换为onPressed()发送给mOnPressedListener,这个mOnPressedListener就是DialpadFragment,然后在DialpadFragment的onPressed()中,将当前的点击事件转换为标准的按键输入:
@DialpadFragment.java
public void onPressed(View view, boolean pressed) {
if (pressed) {
switch (view.getId()) {
case R.id.one: {
//将当前点击事件转换为键盘事件
keyPressed(KeyEvent.KEYCODE_1);
break;
}
case R.id.two: {
keyPressed(KeyEvent.KEYCODE_2);
break;
}
default: {
Log.wtf(TAG, "Unexpected onTouch(ACTION_DOWN) event from: " + view);
break;
}
}
} else {
}
}
这里看到,当我们在拨号键盘上点击某个View时,将会通过onPressed()转换为标准的键盘消息,比如,在R.id.one控件上的点击,将会转换为KeyEvent.KEYCODE_1消息。然后在keyPressed()中将会把当前输入传递给编辑框:
private void keyPressed(int keyCode) {
mHaptic.vibrate();
KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
//传递给编辑框控件
mDigits.onKeyDown(keyCode, event);
// If the cursor is at the end of the text we hide it.
final int length = mDigits.length();
if (length == mDigits.getSelectionStart() && length == mDigits.getSelectionEnd()) {
mDigits.setCursorVisible(false);
}
}
上面的mDigits就是显示当前输入内容的编辑框控件。
1.2、从编辑框到搜索框
当编辑框检测到KeyDown事件后,就会将当前键盘的输入放入编辑框中,并触发TextWatcher的相关方法:
@DialpadFragment.java
public void afterTextChanged(Editable input) {
if (!mDigitsFilledByIntent && SpecialCharSequenceMgr.handleChars(getActivity(), input.toString(), mDigits)) {
mDigits.getText().clear();
}
if (isDigitsEmpty()) {
mDigitsFilledByIntent = false;
mDigits.setCursorVisible(false);
}
if (mDialpadQueryListener != null) {
//传递给mDialpadQueryListener
mDialpadQueryListener.onDialpadQueryChanged(mDigits.getText().toString());
}
updateDialAndDeleteButtonEnabledState();
}
在这里,又将当前已经输入的文本传递给mDialpadQueryListener,它是在DialtactsActivity中实现的:
@DialtactsActivity.java
public void onDialpadQueryChanged(String query) {
final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query, SmartDialNameMatcher.LATIN_SMART_DIAL_MAP);
if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) {
if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
return;
}
//传递给搜索框
mSearchView.setText(normalizedQuery);
}
}
我们看到,在onDialpadQueryChanged()中将当前编辑框的内容通过setText()方法传递给了mSearchView,也就是最上方的搜索框。
1.3、从搜索框到搜索结果列表Fragment
在搜索时,由于搜索框注册了文本监听器,所以将会触发TextWatcher,此时需要暂存当前要搜索的文本,并进入搜索模式,然后再将搜索内容交给SmartDialSearchFragment。
public void onTextChanged(CharSequence s, int start, int before, int count) {
final String newText = s.toString();
if (newText.equals(mSearchQuery)) {
return;
}
//存储当前的搜索文本
mSearchQuery = newText;
final boolean dialpadSearch = isDialpadShowing();
// Show search result with non-empty text. Show a bare list otherwise.
if (TextUtils.isEmpty(newText) && getInSearchUi()) {
//退出搜索模式
exitSearchUi();
mSearchViewCloseButton.setVisibility(View.GONE);
mVoiceSearchButton.setVisibility(View.VISIBLE);
return;
} else if (!TextUtils.isEmpty(newText)) {
final boolean sameSearchMode = (dialpadSearch && mInDialpadSearch) || (!dialpadSearch && mInRegularSearch);
if (!sameSearchMode) {
//进入搜素模式
enterSearchUi(dialpadSearch, newText);
}
if (dialpadSearch && mSmartDialSearchFragment != null) {
//将搜索文本转交给mSmartDialSearchFragment
mSmartDialSearchFragment.setQueryString(newText, false);