第5章 中级控件
本章介绍App开发常见的几类中级控件的用法,主要包括:如何定制几种简单的图形、如何使用几种选择按钮、如何高效地输入文本、如何利用对话框获取交互信息等,然后结合本章所学的知识,演示了一个实战项目“找回密码”的设计与实现。
📖5.1图形定制
本节介绍Android图形的基本概念和几种常见图形的使用办法,包括:形状图形的组成结构及其具体用法、九宫格图片(点九图片)的制作过程及其适用场景、状态列表图形的产生背景及其具体用法。
✅图形-Drawable
一句话总结Drawable:Drawable是用XML文件绘制图形、图像、背景,来装饰UI元素或实现视觉效果,是Android处理图形和图像的关键工具。
Android
把所有能够显示的图形都抽象为Drawable
类(可绘制的)。这里的图形不止是图片,还包括色块、画板、背景等。包含图片在内的图形文件放在res
目录的各个drawable
目录下,其中drawable
目录一般保存描述性的XML文件,而图片文件一般放在具体分辨率的drawable
目录下。例如:
drawable-ldpi
:里面存放低分辨率的图片(如240×320),现在基本没有这样的智能手机了。drawable-mdpi
:里面存放中等分辨率的图片(如320×480),这样的智能手机已经很少了。drawable-hdpi
:里面存放高分辨率的图片(如480×800),一般对应 4 英寸~4.5英寸的手机(但不绝对,同尺寸的手机有可能分辨率不同,手机分辨率就高不就低,因为分辨率低了屏幕会有模糊的感觉)。drawable-xhdpi
:里面存放加高分辨率的图片(如720×1280),一般对应 5 英寸~5.5英寸的手机。drawable-xxhdpi
:里面存放超高分辨率的图片(如1080×1920),一般对应 6 英寸~6.5英寸的手机。drawable-xxxhdpi
:里面存放超超高分辨率的图片(如1440×2560),一般对应 7 英寸以上的平板电脑。
基本上,分辨率每加大一级,宽度和高度就要增加二分之一或三分之一像素。如果各目录存在同名图片,Android
就会根据手机的分辨率分别适配对应文件夹里的图片。
在开发App时,为了兼容不同的手机屏幕,在各目录存放不同分辨率的图片,才能达到最合适的显示效果。
例如,在drawable-hdpi
放了一张背景图片bg.png(分辨率为480×800),其他目录没放,使用分辨率为480×800的手机查看该App界面没有问题,但是使用分辨率为720×1280的手机查看该App会发现背景图片有点模糊,原因是Android为了让bg.png适配高分辨率的屏幕,强行把bg.png拉伸到了720×1280,拉伸的后果是图片变模糊了。
在XML布局文件中使用“@drawable/不含扩展名的文件名称”这种形式来引用图形文件。
比如各视图的background
属性、ImageView
和ImageButton
的src
属性、TextView
和Button
四个方向的drawable系列属性都可以引用图形文件。
✅形状图形-Shape
一句话总结Shape:用于创建简单几何形状的工具,可用于装饰UI元素的背景和边框,在xml文件中使用。
创建一个shape文件:
首先右击drawable
目录,并依次选择右键菜单的New
→Drawable resource file
,在弹窗中输入文件名称和根元素shape即可自动生成一个XML描述文件。
Shape图形又称形状图形,它用来描述常见的几何形状,包括矩形、圆角矩形、圆形、椭圆等。
形状图形的定义文件放在
drawable
目录下,它是以shape
标签为根节点的XML描述文件。在shape中,有一个根节点,根节点下又定义了 6 个节点,分别是**:size(尺寸)、stroke(描边)、corners(圆角)、solid(填充)、padding(间隔)、gradient(渐变)**,各节点的属性值主要是长宽、半径、角度以及颜色等。下面是形状图形各个节点及其属性的简要说明。
🚩1. shape-形状(根节点)
shape是形状图形文件的根节点,它描述了当前是哪种几何图形。下面是shape节点的常用属性说明。
形状类型 | 说明 |
---|---|
rectangle | 矩形,默认值 |
oval | 椭圆,此时corners节点会失效 |
line | 直线,此时必须设置stroke节点,不然会报错 |
ring | 圆环 |
比如设置为椭圆,只需在shape标签中设置android:shape="oval"
,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
</shape>
🚩2. size-尺寸
size是shape的下级节点,它描述了形状图形的宽高尺寸。若无size节点,则表示宽高与宿主视图一样大小。下面是size节点的常用属性说明。
- height:像素类型,图形高度。
- width:像素类型,图形宽度。
比如设置图像的宽200dp,高100dp,代码如下:
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!--设置图形size尺寸大小-->
<size android:height="200dp" android:width="100dp"></size>
</shape>
🚩3.stroke-描边
stroke是shape的下级节点,说白它就是在描述边框border的厚度、颜色等。下面是stroke节点的常用属性说明。
- color:颜色类型,描边的颜色。
- dashGap:像素类型,控制两个线段之间的空白间隔。
- dashWidth:像素类型, 控制每段实线的宽度。若dashGap和dashWidth有一个值为 0 ,则描边为实线。
- width:像素类型,描边的厚度。
通过dashGap
和dashWidth
两个属性可以创建具有不同虚线样式的边框或形状,用于装饰UI元素。
比如制作一个厚度为10dp、颜色为白色、间隔为10dp、每段实线宽度为30dp的虚线:
<?xml version="1.0" encoding="utf-8"?>
<!--定义line直线-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="line" >
<!--定义厚度、颜色、间距、长度-->
<stroke
android:width="10dp"
android:color="#ffffff"
android:dashGap="10dp"
android:dashWidth="30dp"/>
</shape>
🚩4.corners-圆角
corners是shape的下级节点,它描述了形状图形的圆角大小。若无corners节点,则表示没有圆角。下面是corners节点的常用属性说明。
bottomLeftRadius
:像素类型,左下圆角的半径。bottomRightRadius
:像素类型,右下圆角的半径。topLeftRadius
:像素类型,左上圆角的半径。topRightRadius
:像素类型,右上圆角的半径。radius
:像素类型, 4 个圆角的半径(若有上面 4 个圆角半径的定义,则不需要radius定义)。
🚩5. solid-填充
solid是shape的下级节点,它描述了形状图形的填充色彩。若无solid节点,则表示无填充颜色。
下面是solid节点的常用属性说明。
- color:颜色类型,内部填充的颜色。
🚩6. padding-间隔
padding是shape的下级节点,它描述了形状图形与周围边界的间隔。若无padding节点,则表示四周不设间隔。下面是padding节点的常用属性说明。
top:像素类型,与上方的间隔。
bottom:像素类型,与下方的间隔。
left:像素类型,与左边的间隔。
right:像素类型,与右边的间隔。
🚩7 . gradient-渐变
gradient是shape的下级节点,它描述了形状图形的颜色渐变。若无gradient节点,则表示没有渐变效果。下面是gradient节点的常用属性说明。
angle:整型,渐变的起始角度。为 0 时表示时钟的 9 点位置,值增大表示往逆时针方向旋转。例如,值为 90 表示 6 点位置,值为 180 表示 3 点位置,值为 270 表示 0 点/12点位置。
表5-2 渐变类型的取值说明
渐变类型 | 说明 |
---|---|
linear | 线性渐变,默认值 |
radial | 放射渐变,起始颜色就是圆心颜色 |
sweep | 滚动渐变,即一个线段以某个端点为圆心做 360 度旋转 |
- type:字符串类型,渐变类型。
- centerX:浮点型,圆心的X坐标。当android:type="linear"时不可用。
- centerY:浮点型,圆心的Y坐标。当android:type="linear"时不可用。
- gradientRadius:整型,渐变的半径。当android:type="radial"时需要设置该属性。
- centerColor:颜色类型,渐变的中间颜色。
- startColor:颜色类型,渐变的起始颜色。
- endColor:颜色类型,渐变的终止颜色。
- useLevel:布尔类型,设置为true为无渐变色、false为有渐变色。
在实际开发中,形状图形主要使用 3 个节点**:stroke(描边)、corners(圆角)和solid(填充)**。至于shape根节点的属性一般不用设置(默认矩形即可)。
🚩案例1:几何背景切换
第一步: 创建一个圆形矩形背景shape
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 填充:形状内部颜色 -->
<solid android:color="#cb771c" />
<!-- 描边:指定轮廓的粗细与颜色 -->
<stroke
android:width="1dp"
android:color="#ffffff" />
<!--圆角: 指定圆角的半径 -->
<corners android:radius="10dp" />
</shape>
第二步: 创建一个椭圆背景shape
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<!-- 填充:指定形状内部的颜色 -->
<solid android:color="#1ccb5f"/>
<!-- 描边:指定轮廓的粗细与颜色 -->
<stroke android:width="1dp" android:color="#aaaaaa"/>
</shape>
第三步: 接着创建一个测试视图View来存放圆形矩形背景shape,Java代码设置代码:
// v_content视图
View v_content = findViewById(R.id.v_content);
// 给v_content设置shape图形
v_content.setBackgroundResource(R.drawable.shape_rect_gold);
第四步: 创建两个按钮,并添加点击事件,点击时设置成对于的shape
//按钮
findViewById(R.id.btn_rect).setOnClickListener(this);
findViewById(R.id.btn_oval).setOnClickListener(this);
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_rect) {
v_content.setBackgroundResource(R.drawable.shape_rect_orange);
} else if (v.getId() == R.id.btn_oval) {
v_content.setBackgroundResource(R.drawable.shape_oval_green);
}
}
✅九宫格图片
Android的九宫图是一种用于处理图片拉伸和缩放的技术,旨在确保在不失真的情况下适应不同屏幕尺寸和分辨率。
将某张图片设置成视图背景时,如果图片尺寸太小,则系统会自动拉伸图片使之填满背景。可是一旦图片拉得过大,其画面容易变得模糊,如图5-3所示,上面按钮的背景图片被拉得很宽,此时左右两边的边缘线既变宽又变模糊了。
为了解决这个问题,Android专门设计了点九图片。点九图片的扩展名是png,文件名后面常带有“.9”字样。因为该图片划分了3×3的九宫格区域,所以得名点九图片,也叫九宫格图片。
**创建方式:**右击图片,选择Create 9-Patch files..
即可
左侧窗口是图片加工区域,右侧窗口是图片预览区域,从上到下依次是纵向拉伸预览、横向拉伸预览、两方向同时拉伸预览。
一共有上下左右四个黑线操作,不同方向上的黑线表示不同的效果。
界面上边: 黑线指的是水平方向的拉伸区域。水平方向拉伸图片时,只有黑线区域内的图像会拉伸,黑线以外的图像保持原状,从而保证左右两侧的边框厚度不变。
界面左边: 黑线指的是垂直方向的拉伸区域。垂直方向拉伸图片时,只有黑线区域内的图像会拉伸,黑线以外的图像保持原状,从而保证上下两侧的边框厚度不变。
界面下边: 黑线表示Horizontal Padding,设置左右的padding,文字只能放在黑线区域内。
界面右边: 黑线表示Vertical Padding,设置上下的padding,文字上下边界只能放在黑线区域内。
尤其注意,如果点九图片被设置为视图背景,且该图片指定了Horizontal Padding
和Vertical Padding
,那么视图内部将一直与视图边缘保持固定间距,无论怎么调整XML文件和Java代码都无法缩小间隔,缘由是点九图片早已在水平和垂直方向都设置了padding
。
创建完九宫图后改下名字,不然会报错Duplicate resources,说文件名重复。
✅状态列表图形选择器-selector
用于在不同视图状态下指定不同的可绘制图像,常用于按钮等控件的状态切换和交互效果。
比如按钮在按下时是凹陷的,从按下到弹起的过程,用户便晓得点击了该按钮。
创建selector:
右击drawable目录,并依次选择右键菜单的New→Drawable resource file,在弹窗中输入文件名称和根元素selector
即可自动生成一个XML
描述文件。
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/button_pressed" />
<item android:drawable="@drawable/button_normal" />
</selector>
上述XML文件的关键点是state_pressed
属性,该属性表示按下状态,值为true
表示按下时显示button_pressed
图像,
为false时,则是按下时显示另一个。
状态列表图形不仅用于按钮控件,还可用于其他拥有多种状态的控件,这取决于开发者在XML文件中指定了哪种状态类型。各种状态类型的取值说明详见表5-3。
状态类型的属性名称 | 说明 | 适用的控件 |
---|---|---|
state_pressed | 是否按下 | 按钮Button |
state_checked | 是否勾选 | 复选框checkBox,单选按钮RadioButton |
state_focused | 是否获取焦点 | 文本编辑框EditText |
state_selected | 是否选中 | 各控件通用 |
📖5.2 选择按钮
本节介绍几个常用的特殊控制按钮,包括:如何使用复选框CheckBox及其勾选监听器、如何使用开关按钮Switch、如何借助状态列表图形实现仿iOS的开关按钮、如何使用单选按钮RadioButton和单选组RadioGroup及其选中监听器。
✅复选框CheckBox
- checked:指定按钮的勾选状态,true表示勾选,false则表示未勾选。默认为未勾选。
- button:指定左侧勾选图标的图形资源,如果不指定就使用系统的默认图标。
🚩扩展
在学习复选框之前,先了解一下CompoundButton。在Android体系中,CompoundButton类是抽象的复合按钮,因为是抽象类,所以它不能直接使用。实际开发中用的是CompoundButton的几个派生类,主要有复选框CheckBox、单选按钮RadioButton以及开关按钮Switch,这些派生类均可使用CompoundButton的属性和方法。加之CompoundButton本身继承了Button类,故以上几种按钮同时具备Button的属性和方法,它们之间的继承关系如图5-12所示。
CompoundButton在XML文件中主要使用下面两个属性。
- checked:指定按钮的勾选状态,true表示勾选,false则表示未勾选。默认为未勾选。
- button:指定左侧勾选图标的图形资源,如果不指定就使用系统的默认图标。
CompoundButton在Java代码中主要使用下列 4 种方法。
setChecked:设置按钮的勾选状态。
setButtonDrawable:设置左侧勾选图标的图形资源。
setOnCheckedChangeListener:设置勾选状态变化的监听器。
isChecked:判断按钮是否勾选。
🚩案例:监听复选框CheckBox
复选框CheckBox是CompoundButton一个最简单的实现控件,点击复选框将它勾选,再次点击取消勾选。复选框对象调用setOnCheckedChangeListener方法设置勾选监听器,这样在勾选和取消勾选时就会触发监听器的勾选事件。
<?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">
<CheckBox
android:id="@+id/ck_system"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="这是系统的CheckBox" />
<CheckBox
android:id="@+id/ck_custom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:button="@drawable/checkbox_selector"
android:checked="true"
android:padding="5dp"
android:text="这个CheckBox换了图标" />
</LinearLayout>
接着编写对应的Java代码,主要是如何处理勾选监听器,具体代码如下所示:
// 该页面实现了接口OnCheckedChangeListener,意味着要重写勾选监听器的onCheckedChanged方法
public class CheckBoxActivity extends AppCompatActivity
implements CompoundButton.OnCheckedChangeListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_check_box);
// 从布局文件中获取名叫ck_system的复选框
CheckBox ck_system = findViewById(R.id.ck_system);
// 从布局文件中获取名叫ck_custom的复选框
CheckBox ck_custom = findViewById(R.id.ck_custom);
// 给ck_system设置勾选监听器,一旦用户点击复选框,就触发监听器的onCheckedChanged方法
ck_system.setOnCheckedChangeListener(this);
// 给ck_custom设置勾选监听器,一旦用户点击复选框,就触发监听器的onCheckedChanged方法
ck_custom.setOnCheckedChangeListener(this);
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
String desc = String.format("您%s了这个CheckBox", isChecked ? "勾选" : "取消勾选");
buttonView.setText(desc);
}
}
此时复选框默认未勾选。首次点击复选框,此时复选框的图标及文字均发生变化,如图5-14所示;
再次点击复选框,此时复选框的图标及文字又发生变化,如下图所示;可见先后触发了勾选与取消勾选事件。
✅开关按钮Switch
Switch是开关按钮,它像一个高级版本的CheckBox,在选中与取消选中时可展现的界面元素比复选框丰富。Switch控件新添加的XML属性说明如下:
- textOn:设置右侧开启时的文本。
- textOff:设置左侧关闭时的文本。
- track:设置开关轨道的背景。
- thumb:设置开关标识的图标。
🚩案例:定制仿IOS开关
主要思路是借助状态列表图形选择器selector
和CheckBox,首先创建一个图形专用的XML文件,给状态列表指定选中与未选中时候的开关图标,如下所示:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:drawable="@drawable/switch_on"/>
<item android:drawable="@drawable/switch_off"/>
</selector>
然后把CheckBox标签的background属性设置为@drawable/switch_selector,同时将button属性设置为@null。完整的CheckBox标签内容示例如下:
<CheckBox
android:id="@+id/ck_status"
android:layout_width="60dp"
android:layout_height="30dp"
android:background="@drawable/switch_selector"
android:button="@null" />
为什么这里修改background属性,而不直接修改button属性呢?因为button属性有局限,无论多大的 图片,都只显示一个小小的图标,可是小小的图标一点都不大气,所以这里必须使用background属性,要它有多大就能有多大,这才够炫够酷。
✅单选按钮RadioButton
所谓单选按钮RadioGroup
。单选组实质上是个布局,同一组RadioButton
都要放在同一个RadioGroup节点下。RadioGroup提供了orientation属性指定下级控件的排列方向,该属性为horizontal时,单选按钮在水平方向排列;该属性为vertical时,单选按钮在垂直方向排列。
RadioGroup
下面除了RadioButton
,还可以挂载其他子控件(如TextView
、ImageView
等)。如此看来,单选组相当于特殊的线性布局,它们主要有以下两个区别:
- 单选组多了管理单选按钮的功能,而线性布局不具备该功能。
- 如果不指定orientation属性,那么单选组默认垂直排列,而线性布局默认水平排列。
下面是RadioGroup在Java代码中的 3 个常用方法。
check
:选中指定资源编号的单选按钮。getCheckedRadioButtonId
:获取已选中单选按钮的资源编号。setOnCheckedChangeListener
:设置单选按钮勾选变化的监听器。
与CheckBox不同的是,RadioButton默认未选中,点击后显示选中,但是再次点击不会取消选中。只有点击同组的其他单选按钮时,原来选中的单选按钮才会取消选中。另需注意,单选按钮的选中事件不是由RadioButton处理,而是由RadioGroup处理。
接下来演示单选按钮的操作过程,首先编写活动页面的XML文件如下所示:
(完整代码见chapter05\src\main\res\layout\ activity_radio_horizontal.xml)
<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:text="请选择您的性别"
android:textColor="@color/black"
android:textSize="17sp" />
<RadioGroup
android:id="@+id/rg_sex"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<RadioButton
android:id="@+id/rb_male"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:checked="false"
android:text="男"
android:textColor="@color/black"
android:textSize="17sp" />
<RadioButton
android:id="@+id/rb_female"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:checked="false"
android:text="女"
android:textColor="@color/black"
android:textSize="17sp" />
</RadioGroup>
<TextView
android:id="@+id/tv_sex"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="17sp" />
</LinearLayout>
接着编写对应的Java代码,主要是如何处理选中监听器,具体代码如下所示:
// 该页面实现了接口OnCheckedChangeListener,意味着要重写选中监听器的onCheckedChanged方法
public class RadioHorizontalActivity extends AppCompatActivity
implements RadioGroup.OnCheckedChangeListener {
private TextView tv_sex; // 声明一个文本视图对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_radio_horizontal);
// 从布局文件中获取名叫tv_sex的文本视图
tv_sex = findViewById(R.id.tv_sex);
// 从布局文件中获取名叫rg_sex的单选组
RadioGroup rg_sex = findViewById(R.id.rg_sex);
// 设置单选监听器,一旦点击组内的单选按钮,就触发监听器的onCheckedChanged方法
rg_sex.setOnCheckedChangeListener(this);
}
// 在用户点击组内的单选按钮时触发
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (checkedId == R.id.rb_male) {
tv_sex.setText("哇哦,你是个帅气的男孩");
} else if (checkedId == R.id.rb_female) {
tv_sex.setText("哇哦,你是个漂亮的女孩");
}
}
}
然后运行测试App,一开始的演示界面如图5-20所示,此时两个单选按钮均未选中。先点击左边的单选按钮,此时左边按钮显示选中状态,如图5-21所示;再点击右边的单选按钮,此时右边按钮显示选中状态,同时左边按钮取消选中,如图5-22所示;可见果然实现了组内只能选中唯一按钮的单选功能。
📖5.3 文本输入
本节介绍如何在编辑框EditText上高效地输入文本,包括:如何改变编辑框的控件外观,如何利用焦点变更监听器提前校验输入位数,如何利用文本变化监听器自动关闭软键盘。
✅编辑框EditText
编辑框EditText
用于接收软键盘输入的文字,类型html
的input
。例如用户名、密码、评价内容等,它由文本视图派生而来,除了TextView
已有的各种属性和方法,EditText还支持下列XML属性。
inputType
:指定输入的文本类型。输入类型的取值说明见表5-4,若同时使用多种文本类型,则可使用竖线“|”把多种文本类型拼接起来。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: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,进入初始的编辑框页面如图5-23所示。然后往用户名编辑框输入文字,输满 10 个字后发现不能再输入,于是切换到密码框继续输,直到输满 8 位密码,此时编辑框页面如图5-24所示。
🚩案例:状态列表图形selector定制
通过状态列表图形selector实现编辑框获得焦点时(正在输入)显示红色的下划线,其余时候显示灰色下划线。
首先编写圆角矩形的形状图形文件shape_edit_normal.xml
,它的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="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
。
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 指定了形状内部的填充颜色 -->
<solid android:color="#ffffff" />
<!-- 指定了形状轮廓的粗细与颜色 -->
<stroke
android:width="1dp"
android:color="#0000ff" />
<!-- 指定了形状四个圆角的半径 -->
<corners android:radius="5dp" />
<!-- 指定了形状四个方向的间距 -->
<padding
android:bottom="2dp"
android:left="2dp"
android:right="2dp"
android:top="2dp" />
</shape>
接着编写编辑框背景的状态列表图形文件editext_selector.xml
,主要在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:drawable="@drawable/shape_edit_focus" android:state_focused="true" />
<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"
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:hint="这是默认边框"
android:inputType="text" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:background="@null"
android:hint="我的边框不见了"
android:inputType="text" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:background="@drawable/editext_selector"
android:hint="我的边框是圆角"
android:inputType="text" />
</LinearLayout>
最后运行测试App,更换背景之后的编辑框界面如图5-25所示,可见第三个编辑框的背景成功变为了圆角矩形边框。
✅焦点变更监听器
虽然编辑框EditText提供了
maxLength
属性,用来设置可输入文本的最大长度,但是它没提供对应的minLength
属性,也就无法设置可输入文本的最小长度。譬如手机号码为固定的 11 位数字,用户必须输满 11 位才是合法的,然而编辑框不会自动检查手机号码是否达到 11 位,即使用户少输一位只输入十位数字,编辑框依然认为这是合法的手机号。
可以通过焦点变更监听器来实现这一功能。
焦点变更监听器来自于接口View.OnFocusChangeListener
,若想注册该监听器,就要调用编辑框对象的setOnFocusChangeListener方法,即可在光标切换之时(获得光标和失去光标)触发焦点变更事件。
下面是给密码框注册焦点变更监听器的代码例子:
// 从布局文件中获取名为et_password的编辑框
EditText et_password = findViewById(R.id.et_password);
// 给编辑框注册一个焦点变化监听器,一旦焦点发生变化,就触发监听器的onFocusChange方法
et_password.setOnFocusChangeListener(this);
以上代码把焦点变更监听器设置到当前页面,则需让活动页面实现接口View.OnFocusChangeListener
,并重写该接口定义的onFocusChange
方法,判断如果是密码框获得焦点,就检查输入的手机号码是否达到 11 位。具体的焦点变更处理方法如下所示:
// 焦点变更事件的处理方法,hasFocus表示当前控件是否获得焦点。
// 为什么光标进入事件不选onClick?因为要点两下才会触发onClick动作(第一下是切换焦点动作)
@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 位时点击密码框,界面底部果然弹出了相应的提示文字,如图5-27所示,并且光标仍然留在手机号码编辑框,说明首次点击密码框的确触发了焦点变更事件。
✅文本变化监听器
案例:满足输入条件后操作新事件,比如密码输入满 6 位后自动关闭软键盘
输入法的软键盘往往会遮住页面下半部分,使得“登录”“确认”“下一步”等按钮看不到了,用户若想点击这些按钮还得再点一次返回键才能关闭软键盘。为了方便用户操作,最好在满足特定条件时自动关闭软键盘,比如手机号码输入满 11 位后自动关闭软键盘,又如密码输入满 6 位后自动关闭软键盘,等等。达到指定位数便自动关闭键盘的功能,可以再分解为两个独立的功能点,一个是如何关闭软键盘,另一个是如何判断已输入的文字达到指定位数,分别说明如下。
🚩文本变化监听器的用法
该功能点要求实时监控当前已输入的文本长度,这个监控操作用到文本监听器接口TextWatcher,该接口提供了 3 个监控方法,具体说明如下:
beforeTextChanged
:在文本改变之前触发。onTextChanged
:在文本改变过程中触发。afterTextChanged
:在文本改变之后触发。
🚩案例:输入文字达到指定长度就关闭键盘
实现同时监听 11 位的手机号码和 6 位的密码,一旦输入文字达到指定长度就关闭键盘
第一步:创建一个EditText组件
<EditText
android:id="@+id/et_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入11位时自动隐藏输入法"
android:inputType="text"
android:background="@drawable/editext_selector"
android:maxLength="11"
/>
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入6位时自动隐藏输入法"
android:inputType="textPassword"
android:background="@drawable/editext_selector"
android:maxLength="6"
/>
第二步:添加addTextChangedListener事件
// 从布局文件中获取名为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));
第二步:创建内部类编辑框监听器
private class HideTextWatcher implements TextWatcher {
private EditText mView; // 声明一个编辑框对象
private int mMaxLength; // 声明一个最大长度变量
public HideTextWatcher(EditText v, int maxLength) {
super();
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() == 11 && mMaxLength == 11) || (str.length() == 6 && mMaxLength == 6)) {
ViewUtil.hideOneInputMethod(PatchActivity.this, mView); // 隐藏输入法软键盘
}
}
}
第四步:创建ViewUtil工具,来实现关闭输入法软键盘
诚然按下返回键就会关闭软键盘,但这是系统自己关闭的,而非开发者在代码中关闭。因为输入法软键盘由系统服务
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类型才能正常关闭软键盘。
最后:运行测试App
先输入手机号码的前 10 位,因为还没达到 11 位,所以软键盘依然展示,如图5-28所示。接着输入最后一位手机号,总长度达到 11 位,于是软键盘自动关闭,如图5-29所示。
📖5.4 对话框
本节介绍几种常用的对话框控件:
- 如何使用提醒对话框处理不同的选项
- 如何使用日期对话框获取用户选择的日期
- 如何使用时间对话框获取用户选择的时间
✅提醒对话框AlertDialog
AlertDialog
名为提醒对话框,它是Android中最常用的对话框,可以完成常见的交互操作,例如提示、确认、选择等功能。由于AlertDialog没有公开的构造方法,因此必须借助建造器AlertDialog.Builder才能完成参数设置,AlertDialog.Builder的常用方法说明如下。
setIcon
:设置对话框的标题图标。setTitle
:设置对话框的标题文本。setMessage
:设置对话框的内容文本。setPositiveButton
:设置肯定按钮的信息,包括按钮文本和点击监听器。setNegativeButton
:设置否定按钮的信息,包括按钮文本和点击监听器。setNeutralButton
:设置中性按钮的信息,包括按钮文本和点击监听器,该方法比较少用。
通过AlertDialog.Builder设置完对话框参数,还需调用建造器的create方法才能生成对话框实例。最后调用对话框实例的show方法,在页面上弹出提醒对话框。
下面是构建并显示提醒对话框的Java代码例子:
// 创建提醒对话框的建造器
AlertDialog.Builder builder = new AlertDialog.Builder(this);
// 设置对话框的标题文本
builder.setTitle("尊敬的用户");
// 设置对话框的内容文本
builder.setMessage("你真的要卸载我吗?");
// 设置对话框的肯定按钮文本及其点击监听器
builder.setPositiveButton("残忍卸载", (dialog, which) -> {
tv_alert.setText("虽然依依不舍,但是只能离开了");
});
// 设置对话框的否定按钮文本及其点击监听器
builder.setNegativeButton("我再想想", (dialog, which) -> {
tv_alert.setText("让我再陪你三百六十五个日夜");
});
// 根据建造器构建提醒对话框对象
AlertDialog dialog = builder.create();
// 显示提醒对话框
dialog.show();
提醒对话框的弹窗效果如图5-30所示,可见该对话框有标题和内容,还有两个按钮。
点击不同的对话框按钮会触发不同的处理逻辑。例如,图5-31为点击“我再想想”按钮后的页面,图5-32为点击“残忍卸载”按钮后的页面。
✅日期对话框DatePickerDialog
虽然EditText
提供了inputType="date"的日期输入,但是很少有人会手工输入完整日期,况且EditText还不支持“ 年 - 月 -日”这样的中文日期,所以系统提供了专门的日期选择器DatePicker
,供用户选择具体的年月日。
<!--日期选择-->
<DatePicker
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:datePickerMode="spinner"
android:calendarViewShown="false"/>
<!--日历-->
<DatePicker
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
不过,DatePicker
并非弹窗模式,而是在当前页面占据一块区域,并且不会自动关闭。按习惯来说,日期控件应该弹出对话框,选择完日期就要自动关闭对话框。因此,很少直接在界面上显示DatePicker
,而是利用已经封装好的日期选择对话框DatePickerDialog
。 DatePickerDialog
相当于在AlertDialog
上装载了DatePicker
,编码时只需调用构造方法设置当前的年、月、日,然后调用show方法即可弹出日期对话框。
日期选择事件则由监听器OnDateSetListener
负责响应,在该监听器的onDateSet
方法中,开发者获取用户选择的具体日期,再做后续处理。特别注意onDateSet的月份参数,它的起始值不是 1 而是 0 。也就是说,一月份对应的参数值为 0 ,十二月份对应的参数值为 11 ,中间月份的数值以此类推。
在界面上内嵌显示DatePicker的效果如图5-33所示,其中,年、月、日通过上下滑动选择。单独弹出日期对话框的效果如图5-34所示,其中年、月、日按照日历风格展示。
🚩案例1:DatePicker实现日期选择
第一步:创建样式
<!--日期选择-->
<DatePicker
android:id="@+id/dp_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:datePickerMode="spinner"
android:calendarViewShown="false"/>
<Button
android:id="@+id/bnt_selectDate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/button_10_patch"
android:text="确定"
/>
<TextView
android:id="@+id/tv_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="日期:"
android:textSize="30dp" />
第二步:获取DatePicker对象,触发点击事件并获取日期
//获取DatePicker
private DatePicker dp_date = findViewById(R.id.dp_date);
private TextView tv_date = findViewById(R.id.tv_date);
//按钮点击事件
findViewById(R.id.bnt_selectDate).setOnClickListener(this);
@Override
public void onClick(View v) {
if (v.getId() == R.id.bnt_selectDate) {
String date = String.format("日期: %d年%d月%d日", dp_date.getYear(), dp_date.getMonth() + 1, dp_date.getDayOfMonth());
tv_date.setText(date);
}
}
🚩案例2:DatePickerDialog实现日期选择
第一步:创建样式
<Button
android:id="@+id/bnt_selectDate2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/button_10_patch"
android:text="请选择日期"
/>
<TextView
android:id="@+id/tv_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="日期:"
android:textSize="30dp" />
第二步:触发点击事情,并创建DatePickerDialog
private TextView tv_date = findViewById(R.id.tv_date);
findViewById(R.id.bnt_selectDate2).setOnClickListener(this);
@Override
public void onClick(View v) {
if (v.getId() == R.id.bnt_selectDate2) {
// 获取日历的一个实例,里面包含了当前的年月日
Calendar calendar = Calendar.getInstance();
// 构建一个日期对话框,该对话框已经集成了日期选择器。
// DatePickerDialog的第二个构造参数指定了日期监听器
DatePickerDialog dialog = new DatePickerDialog(this, this,
calendar.get(Calendar.YEAR), // 年份
calendar.get(Calendar.MONTH), // 月份
calendar.get(Calendar.DAY_OF_MONTH)); // 日子
dialog.show(); // 显示日期对话框
}
}
第三步:创建日期对话框监听器
@Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
// 获取日期对话框设定的年月份
String desc = String.format("日期: %d年%d月%d日",
year, month + 1, dayOfMonth);
tv_date.setText(desc);
}
✅时间对话框TimePickerDialog
既然有了日期选择器,还得有对应的时间选择器。同样,实际开发中也很少直接用TimePicker,而是用封装好的时间选择对话框TimePickerDialog。该对话框的用法类似DatePickerDialog,不同之处主要有两个:
( 1 )构造方法传的是当前的小时与分钟,最后一个参数表示是否采取 24 小时制,一般为true表示小时的数值范围为 0 ~ 23 ;若为false则表示采取 12 小时制。
( 2 )时间选择监听器为OnTimeSetListener,对应需要实现onTimeSet方法,在该方法中可获得用户选择的小时和分钟。
在界面上内嵌显示TimePicker的效果如图5-35所示,其中,小时与分钟可通过上下滑动选择。单独弹出时间对话框的效果如图5-36所示,其中小时与分钟按照钟表风格展示。
下面是使用时间对话框的Java代码例子,包括弹出时间对话框和处理时间监听事件:
(完整代码见chapter05\src\main\java\com\example\chapter05\TimePickerActivity.java)
// 该页面类实现了接口OnTimeSetListener,意味着要重写时间监听器的onTimeSet方法
public class TimePickerActivity extends AppCompatActivity implements
View.OnClickListener, TimePickerDialog.OnTimeSetListener {
private TextView tv_time; // 声明一个文本视图对象
private TimePicker tp_time; // 声明一个时间选择器对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_time_picker);
tv_time = findViewById(R.id.tv_time);
// 从布局文件中获取名叫tp_time的时间选择器
tp_time = findViewById(R.id.tp_time);
findViewById(R.id.btn_time).setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_time) {
// 获取日历的一个实例,里面包含了当前的时分秒
Calendar calendar = Calendar.getInstance();
// 构建一个时间对话框,该对话框已经集成了时间选择器。
// TimePickerDialog的第二个构造参数指定了时间监听器
TimePickerDialog dialog = new TimePickerDialog(this, this,
calendar.get(Calendar.HOUR_OF_DAY), // 小时
calendar.get(Calendar.MINUTE), // 分钟
true); // true表示24小时制,false表示12小时制
dialog.show(); // 显示时间对话框
}
}
// 一旦点击时间对话框上的确定按钮,就会触发监听器的onTimeSet方法
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
// 获取时间对话框设定的小时和分钟
String desc = String.format("您选择的时间是%d时%d分", hourOfDay, minute);
tv_time.setText(desc);
}
}
📖5.5 实战项目:找回密码
在移动互联网时代,用户是每家IT企业最宝贵的资源,对于App而言,吸引用户注册并登录是万分紧要之事,因为用户登录之后才有机会产生商品交易。登录校验通常是用户名+密码组合,可是每天总有部分用户忘记密码,为此要求App提供找回密码的功能,如何简化密码找回步骤,同时兼顾安全性,就是一个值得认真思考的问题。
✅需求描述
各家电商App的登录页面大同小异,要么是用户名与密码组合登录,要么是手机号码与验证码组合登录,若是做好一点的,则会提供找回密码与记住密码等功能。先来看一下登录页面是什么样,因为有两种组合登录方式,所以登录页面也分成两个效果图。如图5-37所示,这是选中密码登录时的界面;如图5-38所示,这是选中验证码登录时的界面。
从以上两个登录效果图可以看到,密码登录与验证码登录的界面主要存在以下几点区别:
( 1 )密码输入框和验证码输入框的左侧标题以及输入框内部的提示语各不相同。
( 2 )如果是密码登录,则需要支持找回密码;如果是验证码登录,则需要支持向用户手机发送验证码。
( 3 )密码登录可以提供记住密码功能,而验证码的数值每次都不一样,无须也没法记住验证码。
对于找回密码功能,一般直接跳到找回密码页面,在该页面输入和确认新密码,并校验找回密码的合法性(通过短信验证码检查),据此勾勒出密码找回页面的轮廓概貌,如图5-39所示。
在找回密码的操作过程当中,为了更好地增强用户体验,有必要在几个关键节点处提醒用户。比如成功发送验证码之后,要及时提示用户注意查收短信,这里暂且做成提醒对话框的形式,如图5-40所示。又比如密码登录成功之后,也要告知用户已经修改成功登录,注意继续后面的操作,登录成功的提示弹窗如图5-41所示。
真是想不到,原来简简单单的一个登录功能,就得考虑这么多的需求场景。可是仔细想想,这些需求场景都是必要的,其目的是为了让用户能够更加便捷地顺利登录。正所谓“台上十分钟,台下十年功”,每个好用的App背后,都离不开开发者十年如一日的辛勤工作。
✅界面设计
用户登录与找回密码界面看似简单,用到的控件却不少。按照之前的界面效果图,大致从上到下、从左到右分布着下列Android控件:
- 单选按钮RadioButton:用来区分是密码登录还是验证码登录。
- 文本视图TextView:输入框左侧要显示此处应该输入什么信息。
- 编辑框EditText:用来输入手机号码、密码和验证码。
- 复选框CheckBox:用于判断是否记住密码。
- 按钮Button:除了“登录”按钮,还有“忘记密码”和“获取验证码”两个按钮。
- 线性布局LinearLayout:整体界面从上往下排列,用到了垂直方向的线性布局。
- 相对布局RelativeLayout:忘记密码的按钮与密码输入框是叠加的,且“忘记密码”与上级视图右对齐。
- 单选组RadioGroup:密码登录和验证码登录这两个单选按钮,需要放在单选组之中。
- 提醒对话框AlertDialog:为了演示方便,获取验证码与登录成功都通过提醒对话框向用户反馈结果。
另外,由于整个登录模块由登录页面和找回密码页面组成,因此这两个页面之间需要进行数据交互,也就是在页面跳转之时传递参数。譬如,从登录页面跳到找回密码页面,要携带唯一标识的手机号码作为请求参数,不然密码找回页面不知道要给哪个手机号码修改密码。同时,从找回密码页面回到登录页面,也要将修改之后的新密码作为应答参数传回去,否则登录页面不知道密码被改成什么了。
✅关键代码
为了方便读者更好更快地完成登录页面与找回密码页面,下面列举几个重要功能的代码片段:
1 .关于自动清空错误的密码
这里有个细微的用户体验问题:用户会去找回密码,肯定是发现输入的密码不对;那么修改密码后回到登录页面,如果密码框里还是刚才的错误密码,用户只能先清空错误密码,然后才能输入新密码。一个App要想让用户觉得好用,就得急用户之所急,想用户之所想,像刚才那个错误密码的情况,应当由App在返回登录页面时自动清空原来的错误密码。
自动清空密码框的操作,放在onActivityResult方法中处理是个办法,但这样有个问题,如果用户直接按返回键回到登录页面,那么onActivityResult方法发现数据为空便不做处理。因此应该这么处理:判断当前是否为返回页面动作,只要是从找回密码页面返回到当前页面,则不管是否携带应答参数,都要自动清空密码输入框。对应的Java代码则为重写登录页面的onRestart方法,在该方法中强制清空密码。这样一来,不管用户是修改密码完成回到登录页,还是点击返回键回到登录页,App都会自动清空密码框了。
下面是重写onRestart方法之后的代码例子:
(完整代码见chapter05\src\main\java\com\example\chapter05\LoginMainActivity.java)
// 从修改密码页面返回登录页面,要清空密码的输入框
@Override
protected void onRestart() {
super.onRestart();
et_password.setText("");
}
2 .关于自动隐藏输入法面板
在输入手机号码或者密码的时候,屏幕下方都会弹出输入法面板,供用户按键输入数字和字母。但是输入法面板往往占据屏幕下方大块空间,很是碍手碍脚,用户输入完 11 位的手机号码时,还得再按一下返回键来关闭输入法面板,接着才能继续输入密码。理想的做法是:一旦用户输完 11 位手机号码,App就要自动隐藏输入法。同理,一旦用户输完 6 位密码或者 6 位验证码,App也要自动隐藏输入法。要想让App具备这种智能的判断功能,就得给文本编辑框添加监听器,只要当前编辑框输入文本长度达到 11 位或者和 6 位,App就自动隐藏输入法面板。
下面是实现自动隐藏软键盘的监听器代码例子:
(完整代码见chapter05\src\main\java\com\example\chapter05\LoginMainActivity.java)
// 定义一个编辑框监听器,在输入文本达到指定长度时自动隐藏输入法
private class HideTextWatcher implements TextWatcher {
private EditText mView; // 声明一个编辑框对象
private int mMaxLength; // 声明一个最大长度变量
public HideTextWatcher(EditText v, int maxLength) {
super();
mView = v;
mMaxLength = maxLength;
}
// 在编辑框的输入文本变化前触发
public void beforeTextChanged(CharSequence s, int start, int count, int
after) {}
// 在编辑框的输入文本变化时触发
public void onTextChanged(CharSequence s, int start, int before, int count)
{}
// 在编辑框的输入文本变化后触发
public void afterTextChanged(Editable s) {
String str = s.toString(); // 获得已输入的文本字符串
// 输入文本达到11位(如手机号码),或者达到6位(如登录密码)时关闭输入法
if ((str.length() == 11 && mMaxLength == 11)
|| (str.length() == 6 && mMaxLength == 6)) {
ViewUtil.hideOneInputMethod(LoginMainActivity.this, mView); // 隐藏输入法软键盘
}
}
}
3 .关于密码修改的校验操作
由于密码对于用户来说是很重要的信息,因此必须认真校验新密码的合法性,务必做到万无一失才行。具体的密码修改校验可分作下列 4 个步骤:
- 新密码和确认输入的新密码都要是 6 位数字。
- 新密码和确认输入的新密码必须保持一致。
- 用户输入的验证码必须和系统下发的验证码一致。
- 密码修改成功,携带修改后的新密码返回登录页面。
根据以上的校验步骤,对应的代码逻辑示例如下:
(完整代码见chapter05\src\main\java\com\example\chapter05\LoginForgetActivity.java)
String password_first = et_password_first.getText().toString();
String password_second = et_password_second.getText().toString();
if (password_first.length() < 6 || password_second.length() < 6) {
Toast.makeText(this, "请输入正确的新密码", Toast.LENGTH_SHORT).show();
return;
}
if (!password_first.equals(password_second)) {
Toast.makeText(this, "两次输入的新密码不一致", Toast.LENGTH_SHORT).show();
return;
}
if (!et_verifycode.getText().toString().equals(mVerifyCode)) {
Toast.makeText(this, "请输入正确的验证码", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "密码修改成功", Toast.LENGTH_SHORT).show();
// 以下把修改好的新密码返回给上一个页面
Intent intent = new Intent(); // 创建一个新意图
intent.putExtra("new_password", password_first); // 存入新密码
setResult(Activity.RESULT_OK, intent); // 携带意图返回上一个页面
finish(); // 结束当前的活动页面
}
📖5.6 小结
本章主要介绍了App开发的中级控件的相关知识,包括:定制简单的图形(图形的基本概念、形状图形、九宫格图片、状态列表图形)、操纵几种选择按钮(复选框CheckBox、开关按钮Switch、单选按钮RadioButton)、高效地输入文本(编辑框EditText、焦点变更监听器、文本变化监听器)、获取对话框的选择结果(提醒对话框AlertDialog、日期对话框DatePickerDialog、时间对话框TimePickerDialog)。最后设计了一个实战项目“找回密码”,在该项目的App编码中用到了前面介绍的大部分控件,从而加深了对所学知识的理解。
通过本章的学习,我们应该能掌握以下 4 种开发技能:
( 1 )学会定制几种简单的图形。
( 2 )学会操纵常见的选择按钮。
( 3 )学会高效且合法地输入文本。
( 4 )学会通过对话框获取用户选项。