Android Browser学习十二 UrlInputView

这段时间忙的要死, 但是还是不能忘掉自己的学习啊. 虽然读谷歌的代码确实有时候累觉不爱,但是现在却发现其实自己的不知不觉中进步, 现在基本上什么代码拿到手都不会心慌了.有时候还能把自己学习到的东西应用到项目中去, 这确实很美妙.


废话说到这里, 今天分享的是Browser的TitleBar模块, 也就是框计算模块,和chrome一样, android的浏览器的地址栏同样也是一个搜索框, 在UC和百度就应该叫做框计算了吧.其实看似很简单的需求,里面的内容还是很多的.

浏览器框计算的图片如下

Android Browser学习十二 UrlInputView_第1张图片

读者可以一看就知道使用AutoCompleteTextView就可以实现, 确实是这样, android的chrome qq浏览器 都是类似的控件, 

UC使用的其实是一个edittext + listview 可以更好的扩展各种业务, 删除等操作了.

我们还是回来看browser的实现也就是UrlInputView.java

UML

Android Browser学习十二 UrlInputView_第2张图片


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从而实现了搜索联想词功能, 我们会在下一章做介绍

你可能感兴趣的:(Android Browser学习十二 UrlInputView)