第3章 中级控件 3.3 文本输入

        本节介绍如何在编辑框EditText上高效地输入文本,包括:如何改变编辑框的控件外观,如何利用焦点变更监听器提前校验输入位数,如何利用文本变化监听器自动关闭软键盘。

3.3.1    编辑框  EditText

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

        ●  inputType:指定输入的文本类型。输入类型的取值说明见表,若同时使用多种文本类型,则可使用竖线(|)把多种文本类型拼接起来。

表3-4 输入类型的取值说明
输入类型说明
text文本
textPassword文本密码。显示时用圆点(•)代替
number整型数
numberSigned带符号的数字。允许在开头带负号(-)
numberDecimal带小数点的数字
numberPassword数字密码。显示时用圆点(•)代替
datetime时间日期格式。除了数字外,还允许输入横线(-)、斜杆(/)、空格( )、冒号(:)
date日期格式。除了数字外,还允许输入横线(-)和斜杆(/)
time时间格式。除了数字外,还允许输入冒号(:)

        ●  maxLength:指定文本允许输入的最大长度。

        ●  hint:指定提示文本的内容。

        ●  textColorHint:指定提示文本的颜色。

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:orientation="vertical"
    android:padding="5dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".EditSimpleActivity">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:text="下面是登录信息"
        android:textColor="@color/black"
        android:textSize="17sp" />
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="text"
        android:maxLength="10"
        android:hint="请输入用户名"
        android:textColor="@color/black"
        android:textSize="17sp" />
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="textPassword"
        android:maxLength="8"
        android:hint="请输入密码"
        android:textColor="@color/black"
        android:textSize="17sp" />
</LinearLayout>

        运行测试App,进入初始的编辑框页面,如图所示。

然后往用户名编辑框输入文字,输满10个字后发现不能再输入,于是切换到密码框继续输入,直到输满8位密码,此时编辑框页面如图所示。

        根据以上图示可知编辑框的各属性正常工作。不过编辑框有根下划线,未输入时显示灰色,正在输入时显示红色,这种效果是怎么实现的呢?其实下划线没用到新属性,而用了已有的背景属性background;至于未输入与正在输入两种情况的颜色差异,乃是因为使用了状态列表图形,编辑框获得焦点时(正在输入)显示红色的下划线,其余时候显示灰色下划线。当然EditText默认的下划线背景不甚好看,下面将利用状态列表图形将编辑框背景改为更加美观的圆角矩形。

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

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <!--指定了形状内部的填充颜色-->
    <solid android:color="#ffffff"/>
    <!--指定了形状轮廓的粗细与颜色-->
    <stroke
        android:width="3dp"
        android:color="#aaaaaa"/>
    <!--指定了形状4个圆角的半径-->
    <corners android:radius="10dp" />
    <!--指定了形状4个方向的间距-->
    <padding
        android:bottom="2dp"
        android:left="2dp"
        android:right="2dp"
        android:top="2dp" />
</shape>

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

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <!--指定了形状内部的填充颜色-->
    <solid android:color="#ffffff"/>
    <!--指定了形状轮廓的粗细与颜色-->
    <stroke
        android:width="3dp"
        android:color="#0000ff"/>
    <!--指定了形状4个圆角的半径-->
    <corners android:radius="10dp" />
    <!--指定了形状4个方向的间距-->
    <padding
        android:bottom="2dp"
        android:left="2dp"
        android:right="2dp"
        android:top="2dp" />
</shape>

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

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_focused="true"
        android:drawable="@drawable/shape_edit_focus"/>
    <item android:drawable="@drawable/shape_edit_normal"/>
</selector>

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".EditBorderActivity">
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="text"
        android:hint="这是默认边框" />
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="text"
        android:background="@null"
        android:hint="我的边框不见了" />
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="text"
        android:background="@drawable/editext_selector"
        android:hint="我的边框是圆角" />
</LinearLayout>

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

3.3.2    焦点变更监听器

        虽然编辑框EditText提供了maxLength属性,用来设置可输入文本的最大长度,但是它没提供对应的minLength属性,也就无法设置可输入文本的最小长度。譬如手机号码为固定的11位数字,用户必须输满11位才是合法的,然而编辑框不会自动检查手机号码是否达到11位,即使用户少输入一位,编辑框依然认为这是合法的手机号。比如图示的登录页面,有手机号码编辑框,有密码编辑框,还有登录按钮。

        既然编辑框不会自动校验手机号是否达到11位,势必要求代码另行检查。一种想法是在用户点击 “登录” 按钮时再判断,不过通常此时已经输完手机号与密码了。为什么不能在输入密码之前就判断手机号码的位数呢?早点检查可以帮助用户早点发现错误,特别是表单元素较多的时候,更能改善用户的使用体验。就上面的登录例子而言,手机号编辑框下方为密码框,那么能否给密码框注册点击事件,以便在用户准备输入密码时就校验手机号的位数呢?

        然而实际运行App却发现,先输入手机号码再输入密码,一开始并不会触发密码框的点击事件,再次点击密码框才会触发点击事件,缘由是编辑框比较特殊,要点击两次后才会触发点击事件,因为第一次点击只触发焦点变更事件,第二次点击才触发点击事件。编辑框的焦点,直观上就看那个闪动的光标,哪个编辑框有光标,焦点就落在哪里。光标在编辑框之间切换,便产生了焦点变更事件,所以对于编辑框来说,应当注册焦点变更监听器,而非注册点击监听器。

        焦点变更监听器来自接口View.OnFocusChangeListener,若想注册该监听器,就要调用编辑框对象的setOnFocusChangeListener方法,即可在光标切换之时(获得光标和失去光标)触发焦点变更事件。下面是给密码框注册焦点变更监听器的代码例子:

// 从布局文件中获取名叫et_password的密码编辑框
et_password = findViewById(R.id.et_password);
// 给密码编辑框注册点击事件监听器
et_password.setOnFocusChangeListener(this);

        以上代码把焦点变更监听器设置到当前页面,则还需让活动页面实现接口View.OnFocusChangeListener,并重写该接口定义的onFocusChange方法,判断如果是密码框获得焦点,就检查输入的手机号码是否达到11位。具体的焦点变更处理方法如下:

    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        // 判断密码编辑框是否获得焦点。hasFocus为true表示获得焦点,为false表示失去焦点
        if (v.getId()==R.id.et_password && hasFocus){
            String phone = et_phone.getText().toString();
            if (TextUtils.isEmpty(phone)||phone.length()<11){// 手机号码不足11位
                // 手机号码编辑框请求焦点,也就是把光标移回手机号码编辑框
                et_phone.requestFocus();
                Toast.makeText(this, "请输入11位手机号码", Toast.LENGTH_SHORT).show();
            }
        }
    }

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

完整代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:orientation="vertical"
    android:padding="5dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".EditFocusActivity">
    <EditText
        android:id="@+id/et_phone"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:hint="请输入11位手机号码"
        android:inputType="number"
        android:maxLength="11"
        android:background="@drawable/editext_selector"
        android:textColor="@color/black"
        android:textSize="17sp" />
    <EditText
        android:id="@+id/et_password"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_marginTop="5dp"
        android:hint="请输入6位密码"
        android:inputType="numberPassword"
        android:maxLength="6"
        android:background="@drawable/editext_selector"
        android:textColor="@color/black"
        android:textSize="17sp" />
    <Button
        android:id="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="登录"
        android:textColor="@color/black"
        android:textSize="17sp" />
</LinearLayout>
package com.example.chapter05;

import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

public class EditFocusActivity extends AppCompatActivity implements View.OnClickListener,View.OnFocusChangeListener {
    private EditText et_phone;// 声明一个编辑框对象
    private EditText et_password;// 声明一个编辑框对象
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_edit_focus);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        // 从布局文件中获取名叫et_phone的手机号码编辑框
        et_phone = findViewById(R.id.et_phone);
        // 从布局文件中获取名叫et_password的密码编辑框
        et_password = findViewById(R.id.et_password);
        // 给密码编辑框注册点击事件监听器
        et_password.setOnFocusChangeListener(this);
        // 给密码编辑框注册一个焦点变化监听器,一旦焦点发生变化,就触发监听器的onFocusChange方法
        et_phone.setOnFocusChangeListener(this);
        findViewById(R.id.btn_login).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        // 编辑框比较特殊,要点击两次后才会触发点击事件,因为第一次点击只触发焦点变更事件,第二次点击才触发点击事件
        if (v.getId() == R.id.et_password) {
            String phone = et_phone.getText().toString();
            if (TextUtils.isEmpty(phone) || phone.length()<11) { // 手机号码不足11位
                // 手机号码编辑框请求焦点,也就是把光标移回手机号码编辑框
                et_phone.requestFocus();
                Toast.makeText(this, "请输入11位手机号码", Toast.LENGTH_SHORT).show();
            }
        } else if (v.getId() == R.id.btn_login) {
            String password = et_password.getText().toString();
            if (TextUtils.isEmpty(password) || password.length()<11) { // 密码不足6位
                // 密码编辑框请求焦点,也就是把光标移回密码编辑框
                et_password.requestFocus();
                Toast.makeText(this, "请输入6位密码", Toast.LENGTH_SHORT).show();
            }
        }
    }

    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        // 判断密码编辑框是否获得焦点。hasFocus为true表示获得焦点,为false表示失去焦点
        if (v.getId()==R.id.et_password && hasFocus){
            String phone = et_phone.getText().toString();
            if (TextUtils.isEmpty(phone)||phone.length()<11){// 手机号码不足11位
                // 手机号码编辑框请求焦点,也就是把光标移回手机号码编辑框
                et_phone.requestFocus();
                Toast.makeText(this, "请输入11位手机号码", Toast.LENGTH_SHORT).show();
            }
        }
    }
}
3.3.3    文本变化监听器 

        输入法的软键盘往往会遮住页面下半部分,使得 “登录” “确认” “下一步” 等按钮看不到了,用户若想点击这些按钮还得再点一次返回键才能关闭软键盘。为了方便用户操作,最好在满足特定条件时自动关闭软键盘,比如手机号码输入满11位自动关闭软键盘,又如密码输入满6位后自动关闭软键盘,等等。达到指定位数便自动关闭键盘的功能,可以再分解为两个独立的功能点:一个是如何关闭软键盘,另一个是如何判断已输入的文字是否达到指定位数。分别说明如下。

        1.  如何关闭软键盘

        诚然按下返回键就会关闭软键盘,但这是系统自己关闭的,而非开发者在代码中关闭的。因为输入法软键盘由系统服务INPUT_METHOD_SERVICE管理,所以关闭软键盘也要由该服务处理,下面是使用系统服务关闭软键盘的代码例子:

package com.example.chapter05.util;

import android.app.Activity;
import android.content.Context;
import android.view.View;
import android.view.inputmethod.InputMethodManager;

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类型才能正常关闭软键盘。

        2.  如何判断已输入的文字是否达到指定位数

        该功能点要求实时监控当前已输入的文本长度,这个监控操作用到文本监听器接口TextWatcher,该接口提供了3个监控方法,具体说明如下:

        ●  beforeTextChanged:在文本改变之前触发。

        ●  onTextChanged:在文本改变过程中触发。

        ●  afterTextChanged:在文本改变之后触发。

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

    private class HidTextWatcher implements TextWatcher {
        private EditText mView; // 声明一个编辑框对象
        private int mMaxLength; // 声明一个最大长度变量
        public HidTextWatcher(EditText etPhone, int i) {
            super();
            mView=etPhone;
            mMaxLength=i;
        }

        @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() == 11 && mMaxLength == 11)
                    || (str.length() == 6 && mMaxLength == 6)) {
                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 HidTextWatcher(et_phone,11));
        // 给密码编辑框添加文本变化监听器
        et_password.addTextChangedListener(new HidTextWatcher(et_password,6));

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

 

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

完整代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:orientation="vertical"
    android:padding="5dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".EditHideActivity">
    <EditText
        android:id="@+id/et_phone"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:hint="输入11位时自动隐藏输入法"
        android:inputType="number"
        android:maxLength="11"
        android:background="@drawable/editext_selector"
        android:textColor="@color/black"
        android:textSize="17sp" />
    <EditText
        android:id="@+id/et_password"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_marginTop="10dp"
        android:hint="输入6位时自动隐藏输入法"
        android:inputType="numberPassword"
        android:maxLength="6"
        android:background="@drawable/editext_selector"
        android:textColor="@color/black"
        android:textSize="17sp" />
</LinearLayout>
package com.example.chapter05;

import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.example.chapter05.util.ViewUtil;

public class EditHideActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_edit_hide);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        // 从布局文件中获取名叫et_phone的手机号码编辑框
        EditText et_phone = findViewById(R.id.et_phone);
        // 从布局文件中获取名叫et_password的密码编辑框
        EditText et_password = findViewById(R.id.et_password);
        // 给手机号码编辑框添加文本变化监听器
        et_phone.addTextChangedListener(new HidTextWatcher(et_phone,11));
        // 给密码编辑框添加文本变化监听器
        et_password.addTextChangedListener(new HidTextWatcher(et_password,6));
    }

    private class HidTextWatcher implements TextWatcher {
        private EditText mView; // 声明一个编辑框对象
        private int mMaxLength; // 声明一个最大长度变量
        public HidTextWatcher(EditText etPhone, int i) {
            super();
            mView=etPhone;
            mMaxLength=i;
        }

        @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() == 11 && mMaxLength == 11)
                    || (str.length() == 6 && mMaxLength == 6)) {
                ViewUtil.hideOneInputMethod(EditHideActivity.this, mView); // 隐藏输入法软键盘
            }
        }
    }
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值