这段时间忙的要死, 但是还是不能忘掉自己的学习啊. 虽然读谷歌的代码确实有时候累觉不爱,但是现在却发现其实自己的不知不觉中进步, 现在基本上什么代码拿到手都不会心慌了.有时候还能把自己学习到的东西应用到项目中去, 这确实很美妙.
废话说到这里, 今天分享的是Browser的TitleBar模块, 也就是框计算模块,和chrome一样, android的浏览器的地址栏同样也是一个搜索框, 在UC和百度就应该叫做框计算了吧.其实看似很简单的需求,里面的内容还是很多的.
浏览器框计算的图片如下
读者可以一看就知道使用AutoCompleteTextView就可以实现, 确实是这样, android的chrome qq浏览器 都是类似的控件,
UC使用的其实是一个edittext + listview 可以更好的扩展各种业务, 删除等操作了.
我们还是回来看browser的实现也就是UrlInputView.java
UML
public class UrlInputView extends AutoCompleteTextView implements OnEditorActionListener, CompletionListener, OnItemClickListener, TextWatcher
果然是继承了这个AutoCT
他的初始化过程:
private void init(Context ctx) { mInputManager = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE); setOnEditorActionListener(this); //对于AutoCompleteTextView, 只要设置了他的Adapter 当用户输入某些字符的时候,他就会自己进行匹配了 //缺点是可能不好自己定制一些东西 mAdapter = new SuggestionsAdapter(ctx, this); setAdapter(mAdapter); setSelectAllOnFocus(true); onConfigurationChanged(ctx.getResources().getConfiguration()); setThreshold(1);//输入一个字符就可以联想 setOnItemClickListener(this); mNeedsUpdate = false; addTextChangedListener(this); mState = StateListener.STATE_NORMAL; }
这个UrlInputView有三种状态
static interface StateListener { static final int STATE_NORMAL = 0; static final int STATE_HIGHLIGHTED = 1; static final int STATE_EDITED = 2; public void onStateChanged(int state); }
在焦点发生变化, 触摸, 和输入状态发送变化的时候, 会更改UrlInputView的状态,
protected void onFocusChanged(boolean focused, int direction, Rect prevRect) { super.onFocusChanged(focused, direction, prevRect); int state = -1; if (focused) { if (hasSelection()) { state = StateListener.STATE_HIGHLIGHTED; } else { state = StateListener.STATE_EDITED; } } else { // reset the selection state state = StateListener.STATE_NORMAL; } final int s = state; post(new Runnable() { public void run() { changeState(s); } }); }
@Override public boolean onTouchEvent(MotionEvent evt) { boolean hasSelection = hasSelection(); boolean res = super.onTouchEvent(evt); // //Return the masked action being performed, without pointer index information. Use getActionIndex() to return the index associated with pointer actions. // 翻译意思大概:返回经过掩码的action,没有触控点索引信息. 通过getActionIndex()来得到触控操作点的索引. if ((MotionEvent.ACTION_DOWN == evt.getActionMasked()) && hasSelection) { postDelayed(new Runnable() { public void run() { changeState(StateListener.STATE_EDITED); }}, POST_DELAY); } return res; }
@Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (StateListener.STATE_HIGHLIGHTED == mState) { changeState(StateListener.STATE_EDITED); } }
这个状态的变化会回调到Titlebar的onStateChanged函数 , Titlebar的各个图标随之会变化
UrlInputView是一个下拉列表, 所以自然有他的Adapter也就是 SuggestionsAdapter
其初始化
public SuggestionsAdapter(Context ctx, CompletionListener listener) { mContext = ctx; mSettings = BrowserSettings.getInstance(); mListener = listener; mLinesPortrait = mContext.getResources(). getInteger(R.integer.max_suggest_lines_portrait); mLinesLandscape = mContext.getResources(). getInteger(R.integer.max_suggest_lines_landscape); //添加过滤器, AutoCompleteTextView 会通过getFilter拿到 //这个自定义的filter 这样 //这样每当textview输入文字时候 就会先 mAdapter.getItem(position); //mFilter.convertResultToString(item), //filter就会去拿相关数据了 mFilter = new SuggestFilter(); addSource(new CombinedCursor()); }
这里的Filter 是根据输入的内容, 过滤出结果列表的功能, 这是给AutoCT使用的, 我们自定义了过滤的内容
/* * 联想过滤类 根据textview的输入请求数据 * * 过滤器通过过滤模式来约束数据,通常由实现了Filterable接口的子类来生成。 * 过滤操作是通过调用 filter(CharSequence) 或者 * filter(CharSequence, android.widget.Filter.FilterListener)这些异步方法来完成的。 * 以上方法一旦被调用,过滤请求就会被递交到请求队列中等待处理, * 同时该操作会取消那些之前递交的但是还没有被处理的请求。 */ class SuggestFilter extends Filter { /*这里是点一下把下面的text 填充到edittext中*/ @Override public CharSequence convertResultToString(Object item) { if (item == null) { return ""; } SuggestItem sitem = (SuggestItem) item; if (sitem.title != null) { return sitem.title; } else { return sitem.url; } } /* 异步发送联想过滤*/ void startSuggestionsAsync(final CharSequence constraint) { if (!mIncognitoMode) { new SlowFilterTask().execute(constraint); } } /** * 是否出来空查询 默认是false */ private boolean shouldProcessEmptyQuery() { final SearchEngine searchEngine = mSettings.getSearchEngine(); return searchEngine.wantsEmptyQuery(); } /*AutoCompleteTextview mFilter.filter 最后调用到子类的这个函数*/ @Override protected FilterResults performFiltering(CharSequence constraint) { FilterResults res = new FilterResults(); if (mVoiceResults == null) { if (TextUtils.isEmpty(constraint) && !shouldProcessEmptyQuery()) { res.count = 0; res.values = null; return res; } startSuggestionsAsync(constraint); List<SuggestItem> filterResults = new ArrayList<SuggestItem>(); if (constraint != null) { for (CursorSource sc : mSources) { sc.runQuery(constraint); } /*把mSources 中的数据 放到 filterResults中*/ mixResults(filterResults); } synchronized (mResultsLock) { mFilterResults = filterResults; } SuggestionResults mixed = buildSuggestionResults(); res.count = mixed.getLineCount(); res.values = mixed; } else { res.count = mVoiceResults.size(); res.values = mVoiceResults; } return res; } /*把mSources 中的数据 放到 results中*/ void mixResults(List<SuggestItem> results) { int maxLines = getMaxLines(); for (int i = 0; i < mSources.size(); i++) { CursorSource s = mSources.get(i); int n = Math.min(s.getCount(), maxLines); maxLines -= n; boolean more = false; for (int j = 0; j < n; j++) { //取出一个item后 mCursor 下移一个 添加下一个item results.add(s.getItem()); more = s.moveToNext(); } } } @Override protected void publishResults(CharSequence constraint, FilterResults fresults) { if (fresults.values instanceof SuggestionResults) { mMixedResults = (SuggestionResults) fresults.values; notifyDataSetChanged();//最后通知list更新各种数据 } } }
这个Filter的调用来自AutoCompleteTextView : 的 TextWAtcher, 然后就开始进行对数据的过滤
/** * This is used to watch for edits to the text view. Note that we call * to methods on the auto complete text view class so that we can access * private vars without going through thunks. */ private class MyWatcher implements TextWatcher { public void afterTextChanged(Editable s) { doAfterTextChanged(); } public void beforeTextChanged(CharSequence s, int start, int count, int after) { doBeforeTextChanged(); } public void onTextChanged(CharSequence s, int start, int before, int count) { } }
void doAfterTextChanged() { if (mBlockCompletion) return; // if the list was open before the keystroke, but closed afterwards, // then something in the keystroke processing (an input filter perhaps) // called performCompletion() and we shouldn't do any more processing. if (DEBUG) Log.v(TAG, "after text changed: openBefore=" + mOpenBefore + " open=" + isPopupShowing()); if (mOpenBefore && !isPopupShowing()) { return; } // the drop down is shown only when a minimum number of characters // was typed in the text view if (enoughToFilter()) { if (mFilter != null) { //在构造器中拿到的Filter mPopupCanBeUpdated = true; performFiltering(getText(), mLastKeyCode); //至于AutoCT是继承的Edittext } } else { // drop down is automatically dismissed when enough characters // are deleted from the text view if (!mPopup.isDropDownAlwaysVisible()) { dismissDropDown(); } if (mFilter != null) { mFilter.filter(null); } } }
其实AutoCT 使用的就是一个 ListPopupWindow 来展示下拉上拉的popupwindow
public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mPopup = new ListPopupWindow(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle); mPopup.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); mPopup.setPromptPosition(ListPopupWindow.POSITION_PROMPT_BELOW);
综上, UrlInputView 是一个高度定制的AutoCT, 如果让我们去做可能用的是一个个控件去组合, 但是谷歌只用了一个AutoCT来实现, 可真的看出来Android的控件就是这群人写的. UrlInputView并且包括了联想词, 历史词等复杂的逻辑.
值得我们去学习借鉴.
UrlinputView 通过自定义的 SuggestionsAdapter, 在adapter中实现由两个Cursor组成的Filter从而实现了搜索联想词功能, 我们会在下一章做介绍