安卓中级控件(图形、选择按钮、文本输入、对话框)

图形定制

图形Drawable

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的手机查看该图片就会模糊,原因是Android为了让bg.png适配高分辨率的屏幕,强行把bg.png拉伸到了720×1280,拉伸的结果就是图片模糊了。

在XML布局文件中引用图片文件中引用图形文件可用"@drawable/不含扩展名的文件名称"这种形式,如各视图的background属性、ImageViewImageButton的src属性、TextViewButton四个方向的drawable***系列属性都可以引用图形文件。

形状图形

Shape图形又称形状图形,用来描述常见的几何形状,包括矩形、圆角矩形、圆形、椭圆等。用好形状图形可以让App页面不在呆板,还可以节省美工不少工作量。

形状图形的定义文件放在srawable目录下,它是以shape标签为根节点的XML描述文件。根节点下定义了6个节点,分别是size(尺寸)、stroke描边、corners(圆角)、solid(填充)、padding(间隔)、gradient(渐变),各节点的属性值主要是宽和高、半径、角度以及颜色。下面是形状各个节点及其属性的简要说明。

shape(形状)

Shape是形状图形文件的根节点,它描述了当前是哪几种几何图形。下面是shape节点的常用属性说明。

  • shape:字符串类型,表示图形的形状。形状取值说明见下表
形状类型 说明
rectangle 矩形。默认值
oval 椭圆。此时corners节点会失效
Line 直线。此时必须设置stroke节点,不然会报错
ring 圆环

size(尺寸)

size是shape的下级节点,它描述了形状图形的宽高尺寸。若无size节点,则表示宽高与宿主视图一样大小。下面是size节点的常用属性说明。

  • height:像素类型,图形高度。
  • width:像素类型,图形宽度

stroke(描边)

stroke是shape的下级节点,它描述形状图形的描边规格。若无stroke节点,则表示不存在描边。下面是stroke节点的常用属性说明。

  • color:颜色类型,描边的颜色。
  • dashGap:像素类型,每段虚线之间的间隔。
  • dashWidth:像素类型,每段虚线的宽度。若dashGap和dashWidth有一个值为0,则描边为实线。
  • width:像素类型,描边的厚度。

corners(圆角)

corners是shape的下级节点,它描述了形状图形的圆角大小。若无corners节点,则表示没有圆角。下面是corners节点的常用属性说明。

  • bottomLeftRadius:像素类型,左下圆角的半径。
  • bottomRightRadius:像素类型,右下圆角的半径。
  • topLeftRadius:像素类型,左上圆角的半径。
  • topRightRadius:像素类型,右上圆角半径。
  • radius:像素类型,4个圆角的半径(若有上面4个圆角半径的定义,则不需要radius定义)。

solid(填充)

solid是shape的下级节点,它描述了形状图形的填充色彩。若无solid节点,则表示无填充颜色。下面是solid节点的常用属性说明。

  • color:颜色类型,内部填充的颜色。

padding(间隔)

padding是shape的下级节点,它描述了形状与周围边界的间隔。若无padding节点,则表示四周不设间隔。下面是padding节点的常用属性说明。

  • top:像素类型,与上方的间隔。
  • bottom:像素类型,与下方的间隔。
  • right:像素类型,与右边的间隔。
  • left:像素类型,与左边的间隔。

gradient(渐变)

gradient是shape的下级节点,它描述了形状图形的颜色渐变。若无gradient节点,则表示没有渐变效果。下面是gradient节点的常用属性说明。

  • angle:整型,渐变的起始角度。为0时表示始终的9点位置,值增大表示往逆时针方向旋转。
  • type:字符串类型,渐变类型。渐变类型取值见下表:
渐变类型 说明
linear 线性渐变,默认值
radial 放射渐变,其实颜色就是圆心颜色
sweep 滚动渐变,即一个线段以某个断电为圆心做360°旋转
  • 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根节点的属性一般不用设置(默认矩形即可)。

接下来演示一下形状图形的界面效果,首先右击drawable目录,并依次选择右键菜单的New->Drawable resource file,在弹窗中输入文件名称再单击OK按钮,即可自动生成一个XML描述文件。往该文件填入下面的圆角矩形内容定义:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!--  指定了形状内部的填充颜色  -->
    <solid android:color="#ffdd66" />
<!--  指定了形状轮廓的粗细与颜色  -->
    <stroke
        android:width="1dp"
        android:color="#aaaaaa" />
<!--  指定了形状4个圆角的半径  -->
    <corners android:radius="10dp" />
</shape>

接着再创建一个椭圆图形,代码如下:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
<!--  指定了形状内部的填充颜色  -->
    <solid android:color="#ff66aa" />
<!--  指定了形状轮廓的粗细与颜色  -->
    <stroke
        android:width="1dp"
        android:color="#aaaaaa" />
</shape>

执行代码,切换两种图形显示,就能看到如下两种效果图:

九宫格图片

将某张图片设置成视图背景时,如果图片尺寸太小,则系统会自动拉伸图片使之填满背景。但是一旦图片拉伸过大,其画面容易变得模糊,如下图:

如上图所示,上面的按钮的背景图片被拉得很宽,此时左右两边的边缘即变宽又变得模糊。需要注意的是,我们在xml文件里通过背景属性这样android:background="@drawable/button_normal_orig"直接设置背景图片可能显示不出来。这时只需要将按钮控件用<android.widget.Button代替<Button就可以了。

为了解决拉伸模糊问题,Android设计了点九图片。点九图片的扩展名是.png,文件后面常带有".9"字样。因为该图片划分了3×3的九宫格区域,所以得名点九图片,也叫做九宫格图片。如果背景是个形状图形,其stroke节点的width属性已经设置了固定数值(如1dp),那么无论该图形被拉到多大,描边宽度始终是1dp。九宫格图片的实现原理与之类似,即拉伸图形时,只拉伸内部区域,不拉伸边缘线条。

为了演示九宫格图片的展示效果,利用Android Studio制作一张点九图片。首先在drawable目录下找到待加工的原始图片button_pressed_orig.png,右击它弹出右键菜单,如下图:

选择上图右键显示菜单的Create 9-Patch file...,并在随后弹出的对话框中单击OK按钮。接着drawable目录自动生成一个名为"button_pressed_orig.9.png"的图片,双击该文件,主界面右侧弹出如下图显示的点九图片的加工窗口:

上图的左侧窗口时图片加工区域,右侧窗口是图片预览区域,从上到下依次是纵向拉伸预览,横向拉伸预览、两方向同时拉升预览。在左侧窗口图片四周的马赛克处单击会出现一个黑点,把黑点左右或上下拖动会拖出一段黑线,不同方向上的黑线表示不同效果。

下图所示,界面上的黑线指的是水平方向的拉伸区域。在水平方向拉伸图片时,只有黑线区域内的图像会被拉伸,黑线以外的图像保持原状,从而保证左右两边的边框厚度不变。

下图所示,界面左边的黑线指的是垂直方向的拉伸区域。在垂直方向拉伸图片时,只有黑线区域内的图像会被拉伸,黑线以外的图像保持原状,从而保证上下两侧的边框厚度不变。

下图所示,界面下边的黑线指的是该图片作为控件背景时,控件内部的文字左右边界只能放在黑线区域内,这里Horizontal Padding的效果就相当于android:paddingLeftandroid:paddingRight

下图所示,界面右边的黑线指的是该图片作为控件背景时,控件内部的文字上下边界只能放在黑线区域内。这里Vertical Padding的效果相当于android:paddingTopandroid:paddingBottom

注意:如果点九图片被设置为视图背景,且该图片指定了Horizontal PaddingVertical Padding,那么视图内部将一直与视图边缘保持固定间距,无论怎么调整XML文件和Java代码都无法缩小,缘由时点九图片早已在水平和垂直都设置了padding。

状态列表图形

常见的图形文件一般为静态文件,但有时会用到动态文件,比如按钮控件的背景在正常情况下是凸起的,在按下时是凹陷的,从按下到弹起的过程,用户便知道点击了该按钮。根据不同的触摸情况变更形态,这种情况用到了Drawable的一个子类StateListDrawable(状态列表图形),它在XML文件中规定了不同状态所呈现的图形列表。

接下来演示一下状态列表图形的界面效果,右击drawable目录,并依次选择右键菜单的New->Drawable resource file,在弹窗中输入文件名再单击OK按钮,即可自动生成一个XML描述文件。往该文件填入下面的状态列表图形定义:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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图像,其余情况显示button_normal图像。

接下来做个验证,首先将"定制样式的按钮"这一按钮控件的android:background属性设置为@drawable/btn_nine_selector,然后再屏幕上点击按钮,观察发现按下按钮时的界面如下图所示:

未点击时的状态如下:

状态列表图形不仅用于按钮控件,还可用于其他拥有多种状态的控件,这取决于开发者在XML文件中指定了哪种状态类型。各种状态类型取值说明取值如下表:

状态类型的属性名称 说明 适用的控件
state_pressed 是否按下 按钮Button
state_checked 是否勾选 复选框CheckBox、单选按钮RadioButton
state_foused 是否获取焦点 文本编辑框EditText
state_selected 是否选中 各控件通用

选择按钮

复选框CheckBox

在学习复选框之前,先了解一下CompoundButton类。在Android体系中,CompoundButton类是抽象的符合按钮,因为是抽象类,所以它不能直接使用。在实际开发中用的是CompoundButton的几个派生类,主要有复选框CheckBox、单选按钮RadioButton以及开关按钮Switch,这些派生类均可使用CompoundButton的属性和方法。加之CompoundButton本身继承了Button类,故以上几种按钮同时具备Button的属性和方法,它们之间的继承关系如下图所示:

CompoundButton在XML文件中主要使用下面两个属性。

  • checked:指定按钮的勾选状态,true表示勾选,false则表示未勾选。默认为未勾选。
  • button:指定左侧勾选图标的图形资源,如果不指定就使用系统的默认图标。

CompoundButton在Java代码中主要使用下列4个方法。

  • setChecked:设置按钮的勾选状态。
  • setButtonDrawable:设置左侧勾选图标的图形资源。
  • setOnCheckedChangeListener:设置勾选状态变化的监听器。
  • isChecked:判断按钮是否勾选。

复选框CheckBox是CompoundButton一个最简单的实现控件,点击复选框将它勾选,再次点击取消勾选。复选框对象调用setOnCheckedListener方法设置勾选监听器,这样在勾选和取消勾选时就会触发监听器的勾选事件。

为了验证,页面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">
    <CheckBox
        android:id="@+id/ck_system"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:checked="false"
        android:text="这是系统的CheckBox"
        android:textColor="@color/black"
        android:textSize="17sp" />
    <android.widget.CheckBox
        android:id="@+id/ck_custom"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:button="@drawable/checkbox_selector"
        android:padding="5dp"
        android:checked="false"
        android:text="这个CheckBox换了图标"
        android:textColor="@color/black"
        android:textSize="17sp" />
</LinearLayout>

复选框的状态有选中和没选中两种,图形XML文件checkbox_selector编写如下:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true" android:drawable="@drawable/check_choose" />
    <item android:drawable="@drawable/check_unchoose" />
</selector>

页面对应的java代码编写如下:

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设置勾选监听器,一旦用户点击复选框,就触发监听器的onCheckedChanged方法
        ((CheckBox)findViewById(R.id.ck_system)).setOnCheckedChangeListener(this);
        // 给ck_custom设置勾选监听器,一旦用户点击复选框,就触发监听器的onCheckedChanged方法
        ((CheckBox)findViewById(R.id.ck_custom)).setOnCheckedChangeListener(this);
    }
    @Override
    public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
        String desc = String.format("您%s了这个CheckBox", b? "勾选":"取消勾选");
        compoundButton.setText(desc);
    }
}

运行App,不点击,两个复选框显示如下:

点击时,复选框变化如下:

开关按钮Switch

Switch是开关按钮,它像是一个高级版本的CheckBox,在选中与取消中时可展现的界面元素比复选框丰富。Switch控件新添加的XML属性说明如下:

  • textOn:设置右侧开启时的文本。
  • textOff:设置左侧关闭时的文本。
  • track:设置开关轨道的背景。
  • thumb:设置开关标识的图标。

虽然开关按钮时升级版的复选框,但它在实际开发中用的不多。原因之一是大家觉得Switch的默认界面不够美观,图标感觉有点小巧,效果如下:

不过我们可以使用CheckBox实现一个大气一点的开关按钮,通过借助状态列表图形,创建一个图形专用的XML文件,给状态列表指定选中与未选中时的开关图标,代码如下:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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标签内容如下:

xml 复制代码
 <CheckBox
	android:id="@+id/ck_status"
	android:layout_width="60dp"
	android:layout_height="30dp"
	android:background="@drawable/switch_selector"
	android:button="@null" />

这里解释一下,为什么状态图片设置到android:background属性而不是android:button属性。因为android:button有局限性,无论图片多大,都只显示一个和我们设置的宽高大小的图片部分,因此我们在这还是设置图片到android:background。最后的效果如下图:

单选按钮RadioButton

单选按钮,指的是在一组按钮中选择一项不能多选。通过RadioGroup这个容器确定单选组的范围,同一组RadioButton都放在同一个RadioGroup下。可以通过RadioGroup的android:orientation属性指定下级控件的排列方向,该属性的值为horizontal时,单选按钮在水平方向排列;该属性的值为vertical时,单选按钮在垂直方向排列。

其实RadioGroup下面除了放RadioButton还可以放其他子控件(如TextView、ImageView等)。如此看来,单选按钮相当于特殊的线性布局,他们主要有以下两个区别:

  • 单选组多了管理单选按钮的功能,而线性布局不具备该功能。
  • 如果不指定orientation属性,那么单选组默认垂直排列,而线性布局默认水平排列

下面是RadioGroup在Java代码中3个常用的方法:

  • check:选中指定资源编号的单选按钮
  • getCheckedRadioButtonId:获取已经选中的单选按钮资源编号
  • setOnCheckedChangedListener:设置单选按钮勾选变化的监听器

与CheckBox不同的是,RadioButton默认未选中,点击后才会显示选中,再次点击不会取消选中。只有点击其他同组单选按钮时,原来选中的单选按钮才会取消选中。另外单选按钮的选中事件不是由RadioButton处理,而是由RadioGroup处理。

为了显示效果,我们活动页面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="请选择您的性别"
        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代码编写如下:

java 复制代码
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);
        // 注册监听器,一旦点击组内单选按钮,就触发监听器的onCheckChanged方法
        ((RadioGroup)findViewById(R.id.rg_sex)).setOnCheckedChangeListener(this);
    }
    @Override
    public void onCheckedChanged(RadioGroup radioGroup, int checkedId) {
        if (R.id.rb_male == checkedId) {
            tv_sex.setText("性别为男");
        } else if (R.id.rb_female == checkedId) {
            tv_sex.setText("性别为女");
        }
    }
}

运行App,一开始没选中时页面显示如下,此时两个单选按钮均未选中:

单击单选按钮,则界面变化如下:

文本输入

编辑框EditText

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

  • inputType:指定输入的文本类型。输入类型的取值说明见下表,若同时使用多种类型,则可使用竖线(|)把多种文本类型拼接起来。
输入类型 说明
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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="5dp"
    android:orientation="vertical">
    <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" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="下面是手机信息"
        android:textColor="@color/black"
        android:textSize="17sp" />
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="number"
        android:maxLength="11"
        android:hint="请输入11位手机号码"
        android:textColor="@color/black"
        android:textSize="17sp" />
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="numberPassword"
        android:maxLength="11"
        android:hint="请输入6位服密码"
        android:textColor="@color/black"
        android:textSize="17sp" />
</LinearLayout>

运行App后,进入初始的编辑界面如下图:

在XML文件中通过inputType属性对各个输入框的输入内容进行了限制,输入内容后,界面显示如下:

在实际开发中,我们可能有自定义输入框边框的需求,在输入与为输入时可能会显示两种不同边框以表示不同状态。那么此时就可以通过background属性,对两种状态进行修改。下面将演示默认边框、无边框和圆角矩形边框三种形态。

首先编写未输入(为获取焦点)时圆角矩形的形状图形文件,该文件显示灰色圆角矩形轮廓,对应的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>

然后编写的是输入(获取焦点)时圆角矩形的形状图形文件,该文件显示蓝色圆角矩形轮廓,对应的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:color="#0000ff"
        android:width="1dp" />
    <!--  指定了形状四个圆角的半径  -->
    <corners android:radius="5dp" />
    <!--  指定了圆角四个方向的间距  -->
    <padding
        android:top="2dp"
        android:right="2dp"
        android:left="2dp"
        android:bottom="2dp" />
</shape>

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

xml 复制代码
<?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控件:第一个采用默认编辑框;第二个将编辑框的background属性设置为@null表示不显示边框;第三个将background属性设置为@drawable/editext_selector,其背景由editext_selector.xml文件定义的状态列表图形决定。页面布局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:padding="5dp"
    android:orientation="vertical">
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="text"
        android:hint="这是默认边框"
        android:textColor="@color/black"
        android:textSize="17sp" />
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:inputType="text"
        android:hint="我的边框不见了"
        android:background="@null"
        android:textColor="@color/black"
        android:textSize="17sp" />
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="text"
        android:hint="我的边框是圆角"
        android:background="@drawable/editext_selector"
        android:textColor="@color/black"
        android:textSize="17sp" />
</LinearLayout>

运行App,更换边框背景的编辑框界面显示如下:

焦点变更监听

当点击编辑框后,EditText当即获取焦点,此时就会发生焦点状态变更触发onFocusChange方法。编辑框的焦点,直观地看就是编辑框地光标闪动,哪个编辑框有光标,焦点就落在哪里。需要注意的是,编辑框比较特殊,要点击两次才会触发点击事件,因为第一次点击触发地是焦点变更。因此对于编辑框来说,一般都注册焦点监听器,而非点击监听器。

下面是以输入手机号码以及密码为例,给密码框注册焦点变更监听器地代码例子:

java 复制代码
// 从布局文件中获取密码编辑框et_password
EditText et_password = findViewById(R.id.et_password);
// 给密码编辑框注册一个焦点变化监听器,一旦焦点发生变化,就触发onFocusChange方法
et_password.setOnFocusChangeListener(this);

为了能成功注册焦点变更监听器,还需要让活动实现接口View.OnFocusChangeListener,并重写该接口方法。我们的代码逻辑是,当密码输入框获取焦点时,判断手机号长度是否正确,不正确则将焦点重新设置到手机号输入框。焦点变更监听器的代码如下:

java 复制代码
// 焦点变更事件的处理方法,hasFocus表示当前控件是否获得焦点。
// 为什么光标进入事件不选onClick?因为要点两下才会触发onClick动作(第一下是切换焦点动作)
@Override
public void onFocusChange(View view, boolean hasFocus) {
    // 判断密码编辑框是否获得焦点。hasFocus为true表示获得焦点,为false表示失去焦点
    if (view.getId()==R.id.et_password && hasFocus) {
        String phone = et_phone.getText().toString();
        if (TextUtils.isEmpty(phone) || phone.length()<11) {
            // 手机号码编辑框请求焦点,也就是把光标移回手机号码编辑框
            et_phone.requestFocus();
            Toast.makeText(this, "请输入11位手机号码", Toast.LENGTH_SHORT).show();
        }
    }
}

运行App,当手机输入框数据长度不够时,点击密码输入框弹出提示如下:

文本变化监听器

有时我们在输入账号密码时,可能在我们输入内容后想直接点击登录而不必点击键盘的Enter键收起键盘后再点击登录。那么我们只需要在满足某些条件后关闭软键盘即可,例如手机号码输入满11位或者密码输入满6位,等等。

现在明确需求条件后,达到指定长度关闭软键盘功能,现在我们可以分解为功能点:一个是如何关闭软键盘;另一个是如何判断已输入的文字是否达到指定字数。分别说明如下:

  1. 如何关闭软键盘
    输入法键盘由系统服务Context.INPUT_METHOD_SERVICE管理,所以关闭键盘也要由该服务处理,下面是使用系统服务关闭软键盘代码样例:
java 复制代码
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类型才能正常关闭软键盘。

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

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

java 复制代码
// 定义一个编辑框监听器,在输入文本达到指定长度时自动隐藏输入法
private class HideTextWatcher implements TextWatcher {
    private EditText mView; // 声明一个编辑框对象
    private int mMaxLength; // 声明一个最大长度变脸
    HideTextWatcher(EditText editText, int maxLength) {
        super();
        mView = editText;
        mMaxLength = maxLength;
    }

    // 在编辑框的输入文本变化前触发
    @Override
    public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
    }

    // 在编辑框的输入文本变化时触发
    @Override
    public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
    }

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

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

java 复制代码
// 获取编辑框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,输入手机号,但手机号长度没达到11位时软键盘依旧显示,如下图:

当输入总长度达11位后,软键盘收起,如下图:

对话框

提醒对话框AlertDialog

AlertDialog名为提醒对话框,它是Android中最常用的对话框,可以完成常见的交互操作,例如提示、确认、选择等功能。由于AlertDialog没有公开的构造方法,因此必须借助建造器AlertDialog.Builder才能完成参数设置,AlertDialog.Builder常用方法如下:

  • setIcon:设置对话框的标题图标。
  • setTitle:设置对话框的标题文本。
  • setMessage:设置对话框的内容文本。
  • setPositiveButton:设置肯定按钮的信息,包括按钮文本和监听器。
  • setNegativeButton:设置否定按钮的信息,包括按钮文本和监听器。
  • setNeturalButton:设置中性按钮的信息,包括按钮文本和点击监听器,该方法比较少用。

通过AlertDialog.Builder设置完成对话框参数,还需要调用建造器的create方法才能生成对话框实例。最后调用对话框实例的show方法,在页面上弹出提醒对话框。

下面是构建并显示提醒对话框的Java代码例子:

java 复制代码
// 创建提示对话框的建造器
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("尊敬的用户"); // 设对话框标题
builder.setMessage("你真的要卸载我吗?"); // 设置对话框的提示内容
// 设置对话框的肯定按钮文本及其监听器
builder.setPositiveButton("残忍卸载", new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialogInterface, int which) {
        tv_alert.setText("虽然依依不舍,但是只能离开了");
    }
});
// 设置对话框的否定按钮文本及其监听器
builder.setNegativeButton("我再想想", new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialogInterface, int which) {
        tv_alert.setText("让我再陪你三百六十五个日夜");
    }
});
AlertDialog alert = builder.create(); // 根据构造器构建提醒对话框对象
alert.show();

提示对话框如下图所示,可以看见对话框由标题和内容,还有两个按钮:

日期对话框DatePickerDialog

系统专门提供了日期选择器DatePicker ,供用户选择具体的年、月、日。不过,DatePicker并非弹窗模式,而是在当前页面占据一块区域,并且不会自动关闭。使用习惯上,日期控件应该是需要时弹出,使用完毕后关闭。因此,界面上很少直接显示DatePicker,而是通过系统封装好的DatePickerDialog实现。
DatePickerDialog相当于在AlertDialog上装载了DatePicker,编码时只需要调用构造方法设置当前的年、月、日,然后调用show方法即可弹出日期对话框。日期选择事件则由监听器DatePickerDialog.OnDateSetListener负责响应,在该监听器的onDateSet方法中,开发者获取用户选择的具体日期,再做后续处理。需要注意的是,onDateSet方法的月份参数,它的起始值是从0开始的,也就是说一月份对应的数值是0而十二月份对应的数值是11,中间值以此类推。

在界面上嵌入DatePicker的效果如下图所示:

单独弹出日期对话框的效果如下图所示效果:

下面是使用日期对话框的Java代码例子,包括弹出日期对话框和处理日期监听事件:

java 复制代码
// 该页面类实现了接口OnDateSetListener,意味着要重写日期监听器的onDateSet方法
public class DatePickerActivity extends AppCompatActivity implements
        View.OnClickListener, DatePickerDialog.OnDateSetListener {
    private TextView tv_date; // 声明一个文本控件
    private DatePicker dp_date; // 声明一个日期选择器对象
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_date_picker);
        // 从布局文件中获取文本控件tv_date
        tv_date = findViewById(R.id.tv_date);
        // 从布局文件中获取日期选择器dp_date
        dp_date = findViewById(R.id.dp_date);
        findViewById(R.id.btn_date).setOnClickListener(this);
        findViewById(R.id.btn_ok).setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        if (R.id.btn_date == view.getId()) {
            // 获取日历的一个实例,里面包含了当前的年月日
            Calendar calendar = Calendar.getInstance();
            // 构建一个日期对话框,该对话框已经集成了日期选择器
            // DatePickerDialog的第二个参数指定了日期监听器
            DatePickerDialog dialog = new DatePickerDialog(this, this,
                    calendar.get(Calendar.YEAR),  // 年份
                    calendar.get(Calendar.MONTH), // 月份
                    calendar.get(Calendar.DATE)); // 日子
            dialog.show(); // 显示日期对话框
        } else if (R.id.btn_ok == view.getId()) {
            // 获取日期选择器dp_date设定的年月份
            String desc = String.format("您选择的日期是%d年%d月%d日",
                    dp_date.getYear(), dp_date.getMonth() + 1, dp_date.getDayOfMonth());
            tv_date.setText(desc);
        }
    }

    @Override
    public void onDateSet(DatePicker datePicker, int year, int monthOfYear, int dayOfMonth) {
        // 获取日期对话框设定的年月份
        String desc = String.format("您选择的日期是%d年%d月%d日",
                year, monthOfYear + 1, dayOfMonth);
        tv_date.setText(desc);
    }
}

时间对话框TimePickerDialog

安卓同样给开发者提供了时间选择。同样的,在实际开发中也不常直接用TimePicker,而是用封装好的时间选择器对话框TimePickerDialog。该对话框的用法类似DatePickerDialog,不同之处主要有以下两点:

  1. 构造方法传入的是当前的小时和分钟,最后一个参数表示是否采取24小时制,true表示小时的数值取值0~23;若为false则表示采取的12小时制。
  2. 时间选择监听器为TimePickerDialog.OnTimeSetListener,对应的实现方法为onTimeSet,在该方法中可获得用户选择得小时和分钟。

在界面上显示的TimePicker效果如下图所示:

单独弹出的小时和分钟按照钟表的形式显示如下:

下面是使用时间对话框的Java代码,包括弹出时间对话框和处理时间监听事件:

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
        tv_time = findViewById(R.id.tv_time);
        // 从布局文件中获取时间选择器对象tp_time
        tp_time = findViewById(R.id.tp_time);
        findViewById(R.id.btn_time).setOnClickListener(this);
        findViewById(R.id.btn_ok).setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        if (R.id.btn_time == view.getId()) {
            // 获取日历的一个实例,里面包含当前的分秒
            Calendar calendar = Calendar.getInstance();
            // 创建一个对话框,该对话框已经集成了时间选择器
            // TimePickerDialog的第二个参数指定了时间监听器
            TimePickerDialog dialog = new TimePickerDialog(this, this,
                    calendar.get(Calendar.HOUR_OF_DAY), // 小时
                    calendar.get(Calendar.HOUR_OF_DAY), // 分钟
                    true);  // 使用24小时制;false表示12小时制
            dialog.show(); // 显示时间对话框
        } else if (R.id.btn_ok == view.getId()) {
            // 获取定时器tp_time的时间
            String desc = String.format("您选择的时间是%d时%d分",
                    tp_time.getHour(), tp_time.getMinute());
            tv_time.setText(desc);
        }
    }

    // 一旦点击时间对话框上的确定按钮,就会触发监听器的onTimeSet方法
    @Override
    public void onTimeSet(TimePicker timePicker, int hourOfDay, int minute) {
        // 获取时间对话框设定的小时和分钟
        String desc = String.format("您选择的时间是%d时%d分", hourOfDay, minute);
        tv_time.setText(desc);
    }
}

工程源码

所有样例代码都可点击工程源码下载。

相关推荐
守护者1701 分钟前
JAVA学习-练习试用Java实现“使用Arrays.toString方法将数组转换为字符串并打印出来”
java·学习
源码哥_博纳软云3 分钟前
JAVA同城服务场馆门店预约系统支持H5小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台
禾高网络4 分钟前
租赁小程序成品|租赁系统搭建核心功能
java·人工智能·小程序
学会沉淀。10 分钟前
Docker学习
java·开发语言·学习
如若12311 分钟前
对文件内的文件名生成目录,方便查阅
java·前端·python
吃着火锅x唱着歌25 分钟前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习
初晴~41 分钟前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
黑胡子大叔的小屋1 小时前
基于springboot的海洋知识服务平台的设计与实现
java·spring boot·毕业设计
ThisIsClark2 小时前
【后端面试总结】深入解析进程和线程的区别
java·jvm·面试
_Shirley2 小时前
鸿蒙设置app更新跳转华为市场
android·华为·kotlin·harmonyos·鸿蒙