一蚂且、回顧
寫在前面:
距離上次更新已經(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)析以及使用。
二响迂、開始填坑
先看一下最終的效果:
那么我們對(duì)著上篇文章寫的需求來填坑吧考抄。
上篇文章傳送門:
問題 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í)行的方法分別是 animateToExpansionFraction
和 animateToExpansionFraction
项秉,感興趣的可以去源碼查看底哗。
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 的原理后再來看是怎么用的:
- 畫布局毡代,布局文件使用 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>
- 準(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)信息列表孩饼。
- 創(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);
}
- 這樣在輸入的時(shí)候就可以用啦盹憎,AutoCompleteTextView 默認(rèn)是輸入兩個(gè)字符再去查詢镰吵,通過
setThreshold(int threshold)
方法來設(shè)置輸入幾個(gè)字符進(jìn)行查詢提示盼产。比如我想要輸入 1 個(gè)字符就彈出提示:
mPhone.setThreshold(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í)用就多分享些用法,感謝肮之。