Android 基础 — 4、中级控件(二)文本输入 | 青训营笔记

208 阅读9分钟

这是我参与「第四届青训营」笔记创作的第 9 天

此篇笔记是 Android 基础 —— 中级控件 (二)文本输入

本篇笔记分为 3 部分

  1. 编辑框 EditText
  2. 焦点变更监听器
  3. 文本变化监听器

中级控件

本篇笔记将介绍如何在编辑框 EditText 上高效地输入文本

包括:

  • 如何改变编辑框的控件外观;
  • 如何利用焦点变更监听器提前校验输入位数;
  • 如何利用文本变化监听器自动关闭软键盘。

1.编辑框 EditText

编辑框 EditText 用于接收软键盘输入的文字,例如用户名、密码、评价内容等,它由文本视图派生而来,除了 TextView 已有的各种属性和方法,EditText 还支持下列 XML 属性。

  • inputType :指定输入的文本类型。输入类型的取值说明见下表,若同时使用多种文本类型,则可使用竖线 “ | ” 把多种文本类型拼接起来。
  • maxLength :指定文本允许输入的最大长度。
  • hint :指定提示文本的内容。
  • textColorHint :指定提示文本的颜色。
输入类型说明
text文本
textPassword文本密码。显示时用圆点“·”代替
number整型数
numberSigned带符号的数字。允许在开头带负号“-”
numberDecimal带小数点的数字
numberPassword数字密码。显示时用圆点“·”代替
datetime时间日期格式。除了数字外,还允许输入横线、斜杆、空格、冒号
date日期格式。除了数字外,还允许输入横线“-”和斜杆“/”
time时间格式。除了数字外,还允许输入冒号“:”

接下来通过 XML 布局观看编辑框界面效果,演示用的 XML 文件内容如下:

<?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"
    android:orientation="vertical"
    android:padding="5dp">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:gravity="center"
        android:text="下面是登录信息" />

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入用户名"
        android:inputType="text" />

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入密码"
        android:inputType="textPassword" />

</LinearLayout>

运行测试 App ,进入初始的编辑框页面如图 1 所示。然后往用户名编辑框输入文字,输满 10 个字后发现不能再输入,于是切换到密码框继续输,直到输满 8 位密码,此时编辑框页面如图 2 所示。

根据以上图示可知编辑框的各属性正常工作,不过编辑框有根下划线,未输入时显示灰色,正在输入时显示红色,这种效果是怎么实现的呢?

其实下划线没用到新属性,而用了已有的背景属性 background ; 至于未输入与正在输入两种情况的颜色差异,乃是因为使用了状态列表图形,编辑框获得焦点时(正在输入)显示红色的下划线,其余时候显示灰色下划线。

当然 EditText 默认的下划线背景不甚好看,下面将利用状态列表图形将编辑框背景改为更加美观的圆角矩形。

image.png

image.png

首先编写圆角矩形的形状图形文件,它的 XML 定义文件示例如下:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <!--    指定形状内部的填充颜色-->
    <solid android:color="@color/white" />

    <!--    指定形状轮廓的粗细和颜色-->
    <stroke
        android:width="1dp"
        android:color="#aaaaaa" />

    <!--    指定形状四个圆角的半径-->
    <corners android:radius="5dp" />

    <!--    指定形状四个方向的间距-->
    <padding
        android:bottom="2dp"
        android:left="2dp"
        android:right="2dp"
        android:top="2dp"/>

</shape>

上述的 shape_edit_normal.xml 定义了一个灰色的圆角矩形,可在未输入时展示该形状。正在输入时候的形状要改为蓝色的圆角矩形,其中轮廓线条的色值从 aaaaaa(灰色)改成 0000ff(蓝色),具体定义放在 shape_edit_focus.xml

接着编写编辑框背景的状态列表图形文件,主要在 selector 节点下添加两个 item ,一个 item 设置了获得 焦点时刻(android:state_focused="true")的图形为 @drawable/shape_edit_focus ;另一个 item 设置了图形 @drawable/shape_edit_normal 但未指定任何状态,表示其他情况都展示该图形。

然后编写测试页面的XML布局文件,一共添加3个 EditText 标签,第一个 EditText 采用默认的编辑框背景;第二个 EditTextbackground 属性值设为 @null ,此时编辑框不显示任何背景;第三个 EditTextbackground 属性值设为 @drawable/editext_selector ,其背景由 editext_selector.xml 所定义的状态列表图形决定。详细的 XML 文件内容如下所示:

<?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"
    android:orientation="vertical"
    android:padding="5dp">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="text"
        android:layout_marginTop="5dp"
        android:hint="这是默认边框"/>

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@null"
        android:hint="无边框"
        android:layout_marginTop="5dp"
        android:inputType="text" />

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="圆角边框"
        android:layout_marginTop="5dp"
        android:background="@drawable/editext_selector"
        android:inputType="text" />

</LinearLayout>

最后运行测试 App ,更换背景之后的编辑框界面如图所示,可见第三个编辑框的背景成功变为了圆角矩形边框。

image.png

2.焦点变更监听器

虽然编辑框 EditText 提供了 maxLength 属性,用来设置可输入文本的最大长度,但是它没提供对应的 minLength 属性,也就无法设置可输入文本的最小长度。

譬如手机号码为固定的11位数字,用户必须输满 11 位才是合法的,然而编辑框不会自动检查手机号码是否达到 11 位,即使用户少输一位只输入十位数字,编辑框依然认为这是合法的手机号。

比如图所示的登录页面,有手机号码编辑框,有密码编辑框,还有登录按钮。

image.png

既然编辑框不会自动校验手机号是否达到 11 位,势必要求代码另行检查。一种想法是在用户点击登录按钮时再判断,不过通常此时已经输完手机号与密码,为啥不能在输入密码之前就判断手机号码的位数呢?

早点检查可以帮助用户早点发现错误,特别是表单元素较多的时候,更能改善用户的使用体验。就上面的登录例子而言,手机号编辑框下方为密码框,那么能否给密码框注册点击事件,以便在用户准备输入密码时就校验手机号的位数呢?

然而实际运行 App 却发现,先输入手机号码再输入密码,一开始并不会触发密码框的点击事件,再次点击密码框才会触发点击事件。

缘由是编辑框比较特殊,要点击两次后才会触发点击事件,因为第一次点 击只触发焦点变更事件,第二次点击才触发点击事件。编辑框的所谓焦点,直观上就看那个闪动的光 标,哪个编辑框有光标,焦点就落在哪里。光标在编辑框之间切换,便产生了焦点变更事件,所以对于编辑框来说,应当注册焦点变更监听器,而非注册点击监听器。 焦点变更监听器来自于接口 View.OnFocusChangeListener ,若想注册该监听器,就要调用编辑框对象 的 setOnFocusChangeListener 方法,即可在光标切换之时(获得光标和失去光标)触发焦点变更事件。

下面是给密码框注册焦点变更监听器的代码例子:

@Override
public void onFocusChange(View v, boolean hasFocus) {
    if (hasFocus) {
        String phone = et_phone.getText().toString();
        // 手机号码不足11位
        if (TextUtils.isEmpty(phone) || phone.length() < 11) {
            //  手机号码编辑框请求焦点 也就是把光标移回手机号码编辑框
            et_phone.requestFocus();
            Toast.makeText(this,"请输入11位手机号码",Toast.LENGTH_SHORT).show();
        }
    }
}

改好代码重新运行 App ,当手机号不足 11 位时点击密码框,界面底部果然弹出了相应的提示文字,如图所示,并且光标仍然留在手机号码编辑框,说明首次点击密码框的确触发了焦点变更事件。

image.png

3.文本变化监听器

输入法的软键盘往往会遮住页面下半部分,使得 “登录” “确认” “下一步” 等按钮看不到了,用户若想点击这些按钮还得再点一次返回键才能关闭软键盘。

为了方便用户操作,最好在满足特定条件时自动关闭软键盘,比如手机号码输入满11位后自动关闭软键盘,又如密码输入满6位后自动关闭软键盘等。

达到指定位数便自动关闭键盘的功能,可以再分解为两个独立的功能点,一个是如何关闭软键盘,另一个是如何判断已输入的文字达到指定位数,分别说明如下。

  1. 如何关闭软键盘

诚然按下返回键就会关闭软键盘,但这是系统自己关闭的,而非开发者在代码中关闭。

因为输入法软键盘由系统服务 INPUT_METHOD_SERVICE 管理,所以关闭软键盘也要由该服务处理,下面是使用系统服务关闭软键盘的代码例子:

public class ViewUtil {
    public static void hideOneInputMethod(Activity act, View v){
        // 从系统服务获取输入法管理器
        InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE);
        // 关闭屏幕上的输入法键盘
        imm.hideSoftInputFromWindow(v.getWindowToken(),0);
    }
}

注意上述代码里面的视图对象 v ,虽然控件类型为 View ,但它必须是 EditText 类型才能正常关闭软键盘。

  1. 如何判断已输入的文字达到指定位数 该功能点要求实时监控当前已输入的文本长度,这个监控操作用到文本监听器接口 TextWatcher ,该接口提供了 3 个监控方法,具体说明如下:
  • beforeTextChanged :在文本改变之前触发。
  • onTextChanged :在文本改变过程中触发。
  • afterTextChanged :在文本改变之后触发。

具体到编码实现,需要自己写个监听器实现 TextWatcher 接口,再调用编辑框对象的 addTextChangedListener 方法注册文本监听器。监听操作建议在 afterTextChanged 方法中完成,如果同时监听11位的手机号码和6位的密码,一旦输入文字达到指定长度就关闭键盘,则详细的监听器代码如下所示:

// 定义一个编辑框监听器 再输入文本达到指定长度时自动隐藏键盘
private class HideTextWatcher implements TextWatcher {
    // 声明一个编辑框对象
    private EditText mView;
    // 声明一个最大长度变量
    private int mMaxLength;

    public HideTextWatcher(EditText v, int maxLength) {
        this.mView = v;
        this.mMaxLength = maxLength;

    }

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

    }

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

    }

    // 在编辑框的输入文本变化后触发
    @Override
    public void afterTextChanged(Editable s) {
        // 获得已输入的文本字符串
        String str =  s.toString();
        // 输入文本达到11位(如手机号码) 或者达到6位(如登陆密码)时关闭键盘
        if (str.length() == mMaxLength) {
            // 隐藏输入法键盘
            ViewUtil.hideOneInputMethod(EditHideActivity.this, mView);
        }
    }
}

写好文本监听器代码,还要给手机号码编辑框和密码编辑框分别注册监听器,注册代码示例如下:

// 从布局文件中获取名为et_phone的手机号码编辑框
EditText et_phone = findViewById(R.id.et_phone);
// 从布局文件中获取名为et_password的密码编辑框
EditText et_password = findViewById(R.id.et_password);
// 给手机号码编辑框添加文本变化监听器
et_phone.addTextChangedListener(new HideTextWatcher(et_phone, 11));
// 给密码编辑框添加文本变化监听器
et_password.addTextChangedListener(new HideTextWatcher(et_password, 6));

然后运行测试App,先输入手机号码的前10位,因为还没达到11位,所以软键盘依然展示,如图所示。

image.png

接着输入最后一位手机号,总长度达到11位,于是软键盘自动关闭,如图2所示

image.png