Android 項(xiàng)目?jī)?yōu)化筆記(二):登錄頁(yè)

一蚂且、回顧

寫在前面:

  • 距離上次更新已經(jīng)有將近兩個(gè)月了,一是因?yàn)楣ぷ饔行┟Γ潜疽詾轭愃七@種感覺像是追求 UI 的方向大家可能不太感興趣...
    但是經(jīng)過一段時(shí)間之后發(fā)現(xiàn)上篇文章已獲得四十多個(gè)小心心绍弟,啊這是催我更新吶。這次文章出來以后我會(huì)根據(jù)受關(guān)注情況來決定后面的坑是填或不填...
    順帶一提著洼,上文說要跟領(lǐng)導(dǎo)匯報(bào)那些優(yōu)化點(diǎn)晌柬,沒想到還沒等到匯報(bào)需求就改了:把登錄頁(yè)直接砍掉全部用短信登錄。-_-|| 但我還是選擇按照自己的理解把登錄頁(yè)進(jìn)行優(yōu)化并分享出來郭脂,畢竟別的項(xiàng)目也有登錄頁(yè)啊年碘。

  • 以上基本都是廢話與技術(shù)無關(guān),下面開始正式填坑展鸡,本文主要內(nèi)容有:

    • 登錄頁(yè)面優(yōu)化屿衅,更加人性化;
    • Material Design 相關(guān)控件莹弊,ToolBar 涤久、TextInputLayout 涡尘、AutoCompleteTextView 的原理簡(jiǎn)析以及使用。

二响迂、開始填坑

先看一下最終的效果:


效果總覽.gif

那么我們對(duì)著上篇文章寫的需求來填坑吧考抄。

上篇文章傳送門:

Android 項(xiàng)目?jī)?yōu)化筆記(一):概覽

問題 1、2:輸入框 獲取焦點(diǎn)時(shí)更新顏色蔗彤。登錄按鈕高度太低川梅。

解決問題1: 針對(duì)第一條,只要使用 Theme.AppCompat 類型的主題并指定相應(yīng)的顏色即可然遏。

比如這里的項(xiàng)目主色調(diào)是橙色的贫途,那么就指定 colorAccent 的顏色為橙色,登錄頁(yè)使用該主題就ok了:

    <style name="DarkActionBarTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- 導(dǎo)航欄顏色 -->
        <item name="colorPrimary">@color/bg_orange</item>
        <!-- 通知欄顏色 5.0 以后有效-->
        <item name="colorPrimaryDark">@color/bg_orange</item>
        <!-- 控件選中后顏色 -->
        <item name="colorAccent">@color/bg_orange</item>
    </style>

解決問題2: 第二條的話只需要注意把布局寫得緊湊一點(diǎn)待侵,最好不要超過屏幕的一半就好丢早。

問題 3. 用戶輸入賬號(hào)或密碼后添加小×圖標(biāo),一鍵刪除輸入內(nèi)容秧倾。

解決問題3: 針對(duì)這個(gè)問題怨酝,我采用了一種比較普遍的方式來處理:自定義 EditText 并在代碼中設(shè)置 rightDrawable,然后根據(jù)用戶點(diǎn)擊的位置來確認(rèn)是否點(diǎn)擊了刪除圖標(biāo)那先。

那么看核心代碼實(shí)現(xiàn):

ClearEditText # init()

3.1 首先在實(shí)例化的時(shí)候指定資源圖片并添加監(jiān)聽凫碌,當(dāng)文字變化后調(diào)用 setDrawable() 方法來設(shè)置右側(cè)圖標(biāo)。
// 指定資源圖片
mImgClear = context.getResources().getDrawable(R.drawable.ic_text_clear);
private void init() {
    addTextChangedListener(new TextWatcher() {

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }

        //在內(nèi)容改變完之后
        @Override
        public void afterTextChanged(Editable editable) {
            setDrawable();
        }
    });
}
3.2 動(dòng)態(tài)設(shè)置 rightDrawable 的方法胃榕,此方法可以設(shè)置上下左右的圖標(biāo)盛险,并且適用于 TextView等。
//繪制刪除圖片
private void setDrawable(){
    if (length() < 1){
        // 分別設(shè)置左勋又、上苦掘、右、下的圖片
        setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
    } else {
        setCompoundDrawablesWithIntrinsicBounds(null, null, mImgClear, null);
    }
}
3.3 最后楔壤,重寫 onTouchEvent() 并根據(jù)用戶手指點(diǎn)擊抬起位置來判斷是否點(diǎn)擊了刪除圖標(biāo)鹤啡。
//當(dāng)觸摸范圍在右側(cè)時(shí),觸發(fā)刪除方法蹲嚣,隱藏叉叉
@Override
public boolean onTouchEvent(MotionEvent event) {
    if(mImgClear != null && event.getAction() == MotionEvent.ACTION_UP){
        int eventX = (int) event.getRawX();
        int eventY = (int) event.getRawY();
        Rect rect = new Rect();
        // 獲取相對(duì)屏幕的可視區(qū)域
        getGlobalVisibleRect(rect);
        rect.left = rect.right - 100;
        // 如果觸摸的點(diǎn)在可視區(qū)域右側(cè) 100 以內(nèi)递瑰,視為點(diǎn)擊了清除圖標(biāo)
        if (rect.contains(eventX, eventY)){
            getText().clear();
        }
    }
    return super.onTouchEvent(event);
}
問題 4. 所有可操作按鈕 添加響應(yīng)。

這個(gè)是我一直吐槽我們項(xiàng)目的一個(gè)點(diǎn):沒有交互響應(yīng)隙畜。講道理都什么年代了抖部,整個(gè)app連個(gè)點(diǎn)擊效果都沒有。況且 Google 都給咱們?cè)O(shè)計(jì)好了好看的水波反饋效果议惰,求求你們用一下吧 Orz慎颗。

那么對(duì)照之前的登錄頁(yè),我們能看到的需要交互響應(yīng)的無非就是輸入框和按鈕,輸入框已經(jīng)搞定接下來就處理 Button 吧俯萎。

解決問題4:那么直接上代碼

4.1 創(chuàng)建按鈕背景 selector傲宜,分別設(shè)置兩個(gè)狀態(tài):當(dāng)用戶沒有輸入合法數(shù)據(jù)時(shí)按鈕為不可用狀態(tài),顏色要有區(qū)分夫啊。
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/bg_shape_orange_200" android:state_enabled="false"/>
    <item android:drawable="@drawable/selector_ripple_orange" android:state_enabled="true"/>
</selector>
4.2 不可用狀態(tài)的背景顏色應(yīng)該是較淺的函卒,以提升用戶該按鈕不可點(diǎn)擊。下面是 android:state_enabled="false" 的背景代碼:

bg_shape_orange_200

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@color/bg_orange_200"/>
    <corners android:radius="3dp"/>
</shape>

只是簡(jiǎn)單設(shè)置了一個(gè)填充色 solid 和圓角 corners报嵌。

4.3 接下來看按鈕可用狀態(tài)的背景叛本,它的顏色應(yīng)該比較深彤钟。為了適配低版本来候,分別往 drawable 和 drawable-v21 包下放置了背景逸雹。
  • 注意這個(gè)是 drawable 包下的,適用于 API 21 5.0 以下系統(tǒng):

drawable/selector_ripple_orange

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/bg_shape_orange_200" android:state_enabled="false"/>
    <item android:drawable="@drawable/bg_shape_orange_700" android:state_enabled="true"/>
</selector>

上面的代碼在按鈕被按下只變成更深的 orange_700 色梆砸。

  • 下面是適用于 API 21 5.0 以上的水波效果背景

drawable-v21/selector_ripple_orange

<ripple xmlns:android="http://schemas.android.com/apk/res/android"
     android:color="@color/bg_orange_700">  <!--被點(diǎn)擊以后的顏色-->
    <item>
        <shape xmlns:android="http://schemas.android.com/apk/res/android">
            <!--默認(rèn)填充色-->
            <solid android:color="@color/bg_orange_300" />
            <!--圓角弧度-->
            <corners android:radius="3dp" />
        </shape>
    </item>
</ripple>

drawable-v21 文件夾用來適配 API21转质、5.0 以上系統(tǒng)休蟹,系統(tǒng)會(huì)根據(jù)設(shè)備版本加載相應(yīng)包下的資源日矫。那么以上代碼有一些標(biāo)簽需要解釋下:

ripple:水波效果的標(biāo)簽哪轿,支持 5.0 以上;
android:color="@color/bg_orange_700":設(shè)置被點(diǎn)擊以后的顏色杨耙,較深飘痛;
item 中的 shape :設(shè)置了 solid 表示未點(diǎn)擊狀態(tài)的顏色宣脉,corners 圓角弧度。

那么上面代碼的結(jié)果就是可用且未點(diǎn)擊的顏色是 bg_orange_300介蛉,點(diǎn)擊后顏色為 bg_orange_700溶褪,松手后展示水波紋擴(kuò)展效果猿妈。

以上用到的色值,在調(diào)色板里也有鳍刷,這里貼出來:

     <!--顏色由淺到深-->
    <color name="bg_orange_200">#ffcc80</color>
    <color name="bg_orange_300">#ffb74d</color>
    <color name="bg_orange_700">#f57c00</color>
問題 5. 打開登錄頁(yè) 自動(dòng)彈出鍵盤输瓜,并鎖定賬號(hào)輸入框尤揣。

解決問題5:

  • 關(guān)于這個(gè)問題柬祠,只需要在 <manifest 中將登陸頁(yè)面的輸入法模式設(shè)置為 android:windowSoftInputMode="adjustResize"即可漫蛔。windowSoftInputMode 還有很多屬性莽龟,在此不作贅述。當(dāng)軟鍵盤彈出后,可以使用 View 的 requestFocus() 方法來獲取焦點(diǎn)迟赃。
<activity
    android:name=".ui.LoginActivity"
    android:windowSoftInputMode="adjustResize"
    android:label="@string/title_activity_login"
    android:theme="@style/DarkActionBarTheme">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
  • 除了上面那種方法捺信,還有一種通過代碼來設(shè)置鍵盤的顯示。其實(shí)這兩種方式都是通過設(shè)置 Activity 的 SoftInputMode 來實(shí)現(xiàn)的喇辽,使用代碼設(shè)置相對(duì)靈活一點(diǎn)罷了菩咨。
/**
 * EditText獲取焦點(diǎn)并顯示軟鍵盤
 */
public static void showSoftInputFromWindow(Activity activity, EditText editText) {
    editText.setFocusable(true);
    editText.setFocusableInTouchMode(true);
    editText.requestFocus();
    activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
}
問題 6. 可用 MD 控件。

說明問題6:

6.1 頂部控件 Toolbar

先說頂部的 bar云茸,這里推薦使用 Toolbar标捺,畢竟是 Google 推薦替代 ActionBar 的 View宜岛。那么這里提供一種把 Toolbar 簡(jiǎn)單封裝到 BaseActivity 的一種寫法身弊,僅供參考。如果你有更好的寫法可以分享出來凑术。

  • step1:創(chuàng)建基礎(chǔ)布局催首,里面添加 Toolbar 和 容器 FrameLayout。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">

    <!--Toolbar-->
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:theme="@style/ThemeOverlay.AppCompat.Dark"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light">

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
            android:textColor="#ffffff"
            android:layout_gravity="center"/>

    </android.support.v7.widget.Toolbar>

    <!-- Content -->
    <FrameLayout
        android:id="@+id/fl_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

Tips:Toolbar 里面可以放很多東西霉猛,可以自己去定義韩脏。比如套上布局放 EditText + 搜索按鈕 就是一個(gè)搜索功能。

  • step2:加載基礎(chǔ)布局,首先通過子 Activity 必須重寫的 setContentView() 方法拿到其布局 id刃唐,然后通過 LayoutInflater 把子 Activity 的布局添加進(jìn)容器。
setContentView(R.layout.activity_base);
// 加載內(nèi)容布局到容器布局
FrameLayout fl_container = findViewById(R.id.fl_container);
LayoutInflater.from(this).inflate(setContentView(), fl_container);

protected abstract int setContentView();
  • step3:接下來就可以拿到 Toolbar 了抖甘,有了它就可以進(jìn)行設(shè)置標(biāo)題偷办、返回按鈕柄沮、設(shè)置菜單等騷操作了。
private void initToolbar(){
    mToolbar = findViewById(R.id.toolbar);
    mToolbarTitle = findViewById(R.id.tv_title);
    if (null != mToolbar) {
        // 如果沒用到 ActionBar 的話,不需要這句代碼
        // 設(shè)置會(huì)導(dǎo)致 Toolbar 的部分功能失效纬纪,比如菜單欄
//            setSupportActionBar(mToolbar);
        setBackIcon();
    }
    if (null != mToolbarTitle) {
        //getTitle()的值是activity的android:lable屬性值
        mToolbarTitle.setText(getTitle());
        if(null != getSupportActionBar()){
            //設(shè)置默認(rèn)的標(biāo)題不顯示
            getSupportActionBar().setDisplayShowTitleEnabled(false);
        }
    }
}

那么更多的具體功能以及實(shí)現(xiàn)可以到源碼鏈接查看。

6.2 輸入控件父布局:TextInputLayout

TextInputLayout 是一個(gè)自定義的 LinerLayout,大致原理是內(nèi)部可嵌套 EditText,當(dāng) EditText 文字變化時(shí)再進(jìn)行動(dòng)畫把提示文字滑上去或滑下來。

  • TextInputLayout 原理簡(jiǎn)析

TextInputLayout # setEditText

該方法是給 TextInputLayout 設(shè)置 EditText 并添加文字變化監(jiān)聽,當(dāng)文字變化時(shí)進(jìn)行 Hint 變化動(dòng)畫。以下是源碼片段刃泌,很好理解鲤遥。

private void setEditText(EditText editText) {
    // 不能重復(fù)添加 EditText
    if (mEditText != null) {
        throw new IllegalArgumentException("We already have an EditText, can only have one");
    }

    if (!(editText instanceof TextInputEditText)) {
        Log.i(LOG_TAG, "EditText added is not a TextInputEditText. Please switch to using that"
                + " class instead.");
    }

    mEditText = editText;
    ...

    // 添加監(jiān)聽,當(dāng)文字改變時(shí)進(jìn)行動(dòng)畫
    mEditText.addTextChangedListener(new TextWatcher() {
        @Override
        public void afterTextChanged(Editable s) {
            updateLabelState(!mRestoringSavedState);
            if (mCounterEnabled) {
                updateCounter(s.length());
            }
        }
        ...
    });
    ...
}

TextInputLayout # updateLabelState

下面的方法是根據(jù)狀態(tài)進(jìn)行動(dòng)畫爹凹,當(dāng)獲取焦點(diǎn)時(shí)折疊 Hint 文字禾酱,反之展開陷遮。

void updateLabelState(boolean animate) {
    updateLabelState(animate, false);
}

void updateLabelState(final boolean animate, final boolean force) {
    ...

    if (hasText || (isEnabled() && (isFocused || isErrorShowing))) {
        // We should be showing the label so do so if it isn't already
        if (force || mHintExpanded) {
            // 動(dòng)畫折疊 Hint
            collapseHint(animate);
        }
    } else {
        // We should not be showing the label so hide it
        if (force || !mHintExpanded) {
            // 展開 Hint
            expandHint(animate);
        }
    }
}

TextInputLayout # collapseHint expandHint

展開或折疊的動(dòng)畫比吭,動(dòng)畫執(zhí)行的方法分別是 animateToExpansionFractionanimateToExpansionFraction项秉,感興趣的可以去源碼查看底哗。

private void collapseHint(boolean animate) {
    if (mAnimator != null && mAnimator.isRunning()) {
        mAnimator.cancel();
    }
    if (animate && mHintAnimationEnabled) {
        // 執(zhí)行動(dòng)畫
        animateToExpansionFraction(1f);
    } else {
        mCollapsingTextHelper.setExpansionFraction(1f);
    }
    mHintExpanded = false;
}

private void expandHint(boolean animate) {
    if (mAnimator != null && mAnimator.isRunning()) {
        mAnimator.cancel();
    }
    if (animate && mHintAnimationEnabled) {
        // 執(zhí)行動(dòng)畫
        animateToExpansionFraction(0f);
    } else {
        mCollapsingTextHelper.setExpansionFraction(0f);
    }
    mHintExpanded = true;
}

TextInputLayout 除了 Hint 動(dòng)畫外還有很多功能:比如 彈出錯(cuò)誤提示展示文本框輸入文字?jǐn)?shù)量,同時(shí)也可以設(shè)置當(dāng) EditText 輸入文字超過限制后改變字體顏色以更好的提示用戶等功能只搁。

  • TextInputLayout 使用
    TextInputLayout 作為一個(gè) ViewGroup,需要嵌套 EditText 及其子類使用。且 Google 推薦嵌套 TextInputEditText,本文因?yàn)橐獙?shí)現(xiàn)自動(dòng)補(bǔ)全所以嵌套 AutoCompleteTextView的妖。
<android.support.design.widget.TextInputLayout
    android:orientation="horizontal"
    app:counterEnabled="true"
    app:counterMaxLength="11"
    app:counterOverflowTextAppearance="@style/HintError"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <AutoCompleteTextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</android.support.design.widget.TextInputLayout>
<style name="HintError" parent="TextAppearance.AppCompat">
    <item name="android:textColor">@color/md_red_a700</item>
</style>

說明一下上面有的幾個(gè)屬性:

app:counterEnabled:是否展示輸入超標(biāo)提示
app:counterMaxLength:設(shè)置超過多少個(gè)字符展示提示
app:counterOverflowTextAppearance:設(shè)置字符超標(biāo)的提示主題墨缘,字體大小顏色等

6.3 自動(dòng)補(bǔ)全控件 AutoCompleteTextView

AutoCompleteTextView 顧名思義是一個(gè)自動(dòng)補(bǔ)全的 View宽涌,內(nèi)部維護(hù)了一個(gè) ListPopupWindow玩裙。當(dāng)文字變化時(shí)通過 Filter 從數(shù)據(jù)源循環(huán)匹配與之相關(guān)的字段并返回溶诞。

  • AutoCompleteTextView 原理簡(jiǎn)析

AutoCompleteTextView # addTextChangedListener

// 添加監(jiān)聽
addTextChangedListener(new MyWatcher());
// 內(nèi)部維護(hù) TextWatcher 
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() {
    ...
    // 判斷能夠觸發(fā)Filter條件
    if (enoughToFilter()) {
        if (mFilter != null) {
            mPopupCanBeUpdated = true;
            // 執(zhí)行 Filter 功能
            performFiltering(getText(), mLastKeyCode);
        }
    }
    ...
}

protected void performFiltering(CharSequence text, int keyCode) {
    mFilter.filter(text, this);
}

那么這個(gè) mFilter 是哪里來的呢枉圃?mFilter.filter(text, this) 又做了什么呢?那么一起來看一下:

AutoCompleteTextView # setAdapter

public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
    ...
    if (mAdapter != null) {
        //noinspection unchecked
        mFilter = ((Filterable) mAdapter).getFilter();
        adapter.registerDataSetObserver(mObserver);
    } else {
        mFilter = null;
    }

    mPopup.setAdapter(mAdapter);
}

原來是在設(shè)置 adapter 的時(shí)候?qū)嵗?Filter赁酝,且這個(gè) Filter 是 adapter 里面的 Filter衡载。那么就去 adapter 里面找找看弃榨。

LoginActivity # addEmailsToAutoComplete

AutoCompleteTextView 里面的 ListPopupWindow 需要設(shè)置數(shù)據(jù)源以及 adapter坡贺,看下面的方法,暫時(shí)不要管數(shù)據(jù)從哪里來愿伴。

/**
 * 用來給 AutoCompleteTextView 設(shè)置數(shù)據(jù)源
 * @param emailAddressCollection 數(shù)據(jù)列表
 */
private void addDatasToAutoComplete(List<String> emailAddressCollection) {
    ArrayAdapter<String> adapter =
            new ArrayAdapter<>(LoginActivity.this,
                    android.R.layout.simple_dropdown_item_1line, emailAddressCollection);

    mPhone.setAdapter(adapter);
}

那么回到剛才的 Filter 問題,可以看到上面 set 的 adapter 是一個(gè) ArrayAdapter,那么就去看看它的 Filter 是個(gè)什么實(shí)現(xiàn):

ArrayAdapter # ArrayFilter

ArrayAdapter implements Filterable 重寫 getFilter() 方法,創(chuàng)建 ArrayFilter:

@Override
public @NonNull Filter getFilter() {
    if (mFilter == null) {
        mFilter = new ArrayFilter();
    }
    return mFilter;
}

private class ArrayFilter extends Filter {
    @Override
    protected FilterResults performFiltering(CharSequence prefix) {
        final FilterResults results = new FilterResults();

       ...
        final String prefixString = prefix.toString().toLowerCase();

        final ArrayList<T> values;
        synchronized (mLock) {
            values = new ArrayList<>(mOriginalValues);
        }

        final int count = values.size();
        final ArrayList<T> newValues = new ArrayList<>();

        // 遍歷數(shù)據(jù)源的數(shù)據(jù)阀圾,也就是 adapter 維護(hù)的數(shù)據(jù)
        for (int i = 0; i < count; i++) {
            final T value = values.get(i);
            final String valueText = value.toString().toLowerCase();

            // 如果數(shù)據(jù)源的數(shù)據(jù) startsWith 傳來的數(shù)據(jù),那么就把改數(shù)據(jù)放到一個(gè)新的集合里返回
            if (valueText.startsWith(prefixString)) {
                newValues.add(value);
            } else {
                final String[] words = valueText.split(" ");
                for (String word : words) {
                    if (word.startsWith(prefixString)) {
                        newValues.add(value);
                        break;
                    }
                }
            }
        }

        results.values = newValues;
        results.count = newValues.size();
    }
    return results;
}

額,這個(gè)注釋怎么顯示是斜體啊,根本看不清啊...算了典阵,看我解釋吧:

  • step1: 首先傳過來的參數(shù) CharSequence prefix嫉鲸,就是用戶輸入的字符染突。轉(zhuǎn)換成小寫為 prefixString 也榄,用它來跟 adapter 里面的數(shù)據(jù)進(jìn)行匹配骂远。
  • step2: values 里面放的是 mOriginalValues(adapter 里面的數(shù)據(jù))激才,接著進(jìn)行遍歷。
  • step3: 如果 values 里面是數(shù)據(jù)轉(zhuǎn)換成小寫以后压固,startsWith 輸入的 prefixString,就把這個(gè)數(shù)據(jù)放到一個(gè)新的容器 newValues 里面拦键,最后進(jìn)行返回。
    用新的數(shù)據(jù)再去給 adapter 設(shè)置碳柱,這樣就實(shí)現(xiàn)了 Filter 過濾匹配字段的功能涎拉。

總結(jié):其實(shí) AutoCompleteTextView 的 setAdapter() 方法是給其內(nèi)部的 ListPopupWindow 設(shè)置 adapter半火,這樣數(shù)據(jù)有了就可以展示列表了。當(dāng)用戶輸入字符后,調(diào)用 adapter 的 Filter 對(duì)象進(jìn)行循環(huán)對(duì)比,如果存在起始字符相同的數(shù)據(jù)就作為一個(gè)新的列表返回并展示。這樣就完成了整個(gè)過程稠歉。

  • AutoCompleteTextView 使用

了解 AutoCompleteTextView 的原理后再來看是怎么用的:

  1. 畫布局毡代,布局文件使用 TextInputLayout + AutoCompleteTextView:
<android.support.design.widget.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <AutoCompleteTextView 
        android:id="@+id/atv_phone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/prompt_email"
        android:inputType="phone"
        android:maxLines="1"
        android:singleLine="true" />

</android.support.design.widget.TextInputLayout>
  1. 準(zhǔn)備數(shù)據(jù)源,也就是給 AutoCompleteTextView 設(shè)置數(shù)據(jù),好讓它展示 popList看尼□锝幔看下面的例子:
/**
 * CursorLoader加載完成回調(diào)
 * @param cursorLoader
 * @param cursor
 */
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
    List<String> emails = new ArrayList<>();
    cursor.moveToFirst();
    while (!cursor.isAfterLast()) {
        // 獲取備注名
        String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
        // 獲取電話號(hào)碼
        String phone = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
        emails.add(phone);
        cursor.moveToNext();
    }

    addDatasToAutoComplete(emails);
}

onLoadFinished()方法是使用 CursorLoader 加載數(shù)據(jù)的回調(diào)屈溉,CursorLoader 相關(guān)的東西這里不聊了。本例中是使用 CursorLoader 加載聯(lián)系人的手機(jī)號(hào)作為 AutoCompleteTextView 的數(shù)據(jù)源,你可以在初始化的時(shí)候加載其它任何地方的數(shù)據(jù)作為數(shù)據(jù)源缠导,比如加載存在本地的登錄過的賬號(hào)信息列表孩饼。

  1. 創(chuàng)建 adapter。上面最后的 addDatasToAutoComplete() 方法用的是自帶的 ArrayAdapter宝泵。你可以自己創(chuàng)建 adapter 以便維護(hù)鳄抒,然后調(diào)用你的 AutoCompleteTextView 的 setAdapter() 方法完成數(shù)據(jù)綁定瓤鼻。
/**
 * 用來給 AutoCompleteTextView 設(shè)置數(shù)據(jù)源
 * @param emailAddressCollection 數(shù)據(jù)列表
 */
private void addDatasToAutoComplete(List<String> emailAddressCollection) {
    ArrayAdapter<String> adapter =
            new ArrayAdapter<>(LoginActivity.this,
                    android.R.layout.simple_dropdown_item_1line, emailAddressCollection);
    // AutoCompleteTextView 對(duì)象牲迫,手機(jī)號(hào)輸入框
    mPhone.setAdapter(adapter);
}
  1. 這樣在輸入的時(shí)候就可以用啦盹憎,AutoCompleteTextView 默認(rèn)是輸入兩個(gè)字符再去查詢镰吵,通過 setThreshold(int threshold) 方法來設(shè)置輸入幾個(gè)字符進(jìn)行查詢提示盼产。比如我想要輸入 1 個(gè)字符就彈出提示:
mPhone.setThreshold(1);
  1. setError("錯(cuò)誤提示") 方法可以設(shè)置彈出的錯(cuò)誤提示草穆,因?yàn)樘崾靖袊@號(hào)圖標(biāo)的位置在最右側(cè)與一鍵情況圖標(biāo)沖突锋喜,所以我用的時(shí)候把相關(guān)代碼注掉了。可以通過相關(guān)方法去更改感嘆號(hào)的位置往堡,具體是什么方法可以自己去找。

三、結(jié)語

本文就此結(jié)束,寫博客的過程中也一直在思考:應(yīng)該偏向原理多一點(diǎn)還是使用多一點(diǎn)?看到這里的同學(xué)可以留言或者發(fā)簡(jiǎn)信與我聯(lián)系進(jìn)行反饋,如果喜歡了解原理就多分析一些源碼,注重實(shí)用就多分享些用法,感謝肮之。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末艰毒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌坛增,老刑警劉巖收捣,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咐蚯,死亡現(xiàn)場(chǎng)離奇詭異挎春,居然都是意外死亡直奋,警方通過查閱死者的電腦和手機(jī)弥搞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門粤铭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人凹髓,你說我怎么就攤上這事防泵∈偾矗” “怎么了咏瑟?”我有些...
    開封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵余寥,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘党远。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天郑象,我揣著相機(jī)與錄音厂榛,去河邊找鬼丽惭。 笑死责掏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的痰驱。 我是一名探鬼主播担映,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蝇完,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼四敞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤缎玫,失蹤者是張志新(化名)和其女友劉穎赃磨,沒想到半個(gè)月后邻辉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腮鞍,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡移国,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年使碾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祝懂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嫂易。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖傅事,靈堂內(nèi)的尸體忽然破棺而出蹭越,到底是詐尸還是另有隱情教届,我是刑警寧澤案训,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布强霎,位于F島的核電站城舞,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏脱柱。R本人自食惡果不足惜榨为,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一椅邓、第九天 我趴在偏房一處隱蔽的房頂上張望景馁。 院中可真熱鬧合住,春花似錦撒璧、人聲如沸卿樱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)奕翔。三九已至,卻和暖如春浩蓉,著一層夾襖步出監(jiān)牢的瞬間派继,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工捻艳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留驾窟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓认轨,卻偏偏與公主長(zhǎng)得像绅络,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子好渠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 內(nèi)容 抽屜菜單 ListView WebView SwitchButton 按鈕 點(diǎn)贊按鈕 進(jìn)度條 TabLayo...
    小狼W閱讀 1,614評(píng)論 0 10
  • 注意:現(xiàn)在比較流行使用AndroidX了,這個(gè)博客中的代碼都是使用Android.support中的類庫(kù)拳锚。但是用法...
    AxeChen閱讀 18,515評(píng)論 28 66
  • 有段時(shí)間假栓,很少會(huì)有今夜無夢(mèng)之時(shí),至今霍掺,仍然記得那一夜的夢(mèng)匾荆,很清晰,又很模糊…… 夢(mèng)中杆烁,不知出于何種緣由牙丽,要到一個(gè)女...
    唐迎宸閱讀 362評(píng)論 1 2
  • 我始終記得第一次與你約會(huì),在高鐵站等你時(shí)兔魂,迫切的心情烤芦。仿佛時(shí)間停滯,高鐵析校,為什么這么慢构罗! 直到與你見面,我才踏實(shí)...
    抓個(gè)魚閱讀 277評(píng)論 0 0
  • 清晨7點(diǎn)智玻,微信的提示音響個(gè)不停遂唧,打開手機(jī),看見七八條信息砸在臉上吊奢,蝌蚪小姐說盖彭,這幾天有沒有時(shí)間,我要來睡你。 凌晨...
    蘇小豆閱讀 418評(píng)論 0 1