本节介绍了按钮控件的常见用法,包括:如何设置大小写属性与点击属性,如何响应按钮的点击事件和长按事件,如何禁用按钮又该如何启用按钮,等等。
除了文本视图之外,按钮Button也是一种基础控件。因为Button是由TextView派生而来,所以文本视图拥有的属性和方法,包括文本内容、文本大小、文本颜色等,按钮控件均能使用。
不同的是,Button拥有默认的按钮背景,而TextView默认无背景;Button的内部文本默认居中对齐,而TextView的内部文本默认靠左对齐。此外,按钮还要额外注意textAllCaps与onClick两个属性,分别介绍如下:
对于TextView来说,text属性设置了什么文本,文本视图就显示什么文本。但对于Button来说,不管text属性设置的是大写字母还是小写字母,按钮控件都默认转成大写字母显示。比如在XML文件中加入下面的Button标签:
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello World"/>
编译运行后的App界面,按钮上显示全大写的“HELLO WORLD”,而非原来大小写混合的“Hello World”。显然这个效果不符合预期,为此需要给Button标签补充textAllCaps属性,该属性默认为true表示全部转为大写,如果设置为false则表示不转为大写。于是在布局文件添加新的Button标签,该标签补充了android:textAllCaps=“false”,具体内容如下所示:
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello World"
android:textAllCaps="false"/>
再次运行App,此时包含新旧按钮的界面如图所示,可见textAllCaps属性果然能够控制大小写转换。
按钮之所以成为按钮,是因为它会响应按下动作,就手机而言,按下动作等同于点击操作,即手指轻触屏幕然后马上松开。每当点击按钮之时,就表示用户确认了某个事项,接下来轮到App接着处理了。
onClick属性便用来接管用户的点击动作,该属性的值是个方法名,也就是当前页面的Java代码存在这么一个方法:当用户点击按钮时,就自动调用该方法。
譬如下面的Button标签指定了onClick属性值为doClick,表示点击该按钮会触发Java代码中的doClick方法:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ButtonStyleActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="直接指定点击方法"
android:textColor="@color/black"
android:textSize="17sp"
android:onClick="doClick"/>
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="这里查看点击结果"
android:textColor="@color/black"
android:textSize="17sp"/>
LinearLayout>
与之相对应,页面所在的Java代码需要增加doClick方法,方法代码示例如下:
package com.example.buttoncontrols;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.example.buttoncontrols.util.DateUtil;
public class ButtonStyleActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_button_style);
textView = findViewById(R.id.tv_result);
}
public void doClick(View view){
String desc=String.format("%s 您点击了按钮:%s",DateUtil.getNowTime(),((Button)view).getText());
textView.setText(desc);
}
}
然后编译运行,并在App界面上点击新加的按钮,点击前后的界面如下面两张图所示。
点击之后效果图:
比较两张图的文字差异,可见点击按钮之后确实调用了doClick方法。
虽然按钮控件能够在XML文件中通过onClick属性指定点击方法,但是方法的名称可以随便叫,既能叫doClick也能叫doTouch,甚至叫它doA或doB都没问题,这样很不利于规范化代码,倘若以后换了别人接手,就不晓得doA或doB是干什么用的。因此在实际开发中,不推荐使用Button标签的onClick属性,而是在代码中给按钮对象注册点击监听器。
所谓监听器,意思是专门监听控件的动作行为,它平时无所事事,只有控件发生了指定的动作,监听器才会触发开关去执行对应的代码逻辑。点击监听器需要实现接口View.OnClickListener,并重写onClick方法补充点击事件的处理代码,再由按钮调用setOnClickListener方法设置监听器对象。比如下面的代码给按钮控件btn_click_single设置了一个点击监听器:
// 定义一个点击监听器,它实现了接口View.OnClickListener
class MyOnClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
// 点击事件的处理方法
String desc = String.format("%s 您点击了按钮:%s",
DateUtil.getNowTime(), ((Button)v).getText());
// 设置文本视图的文本内容
tv_result.setText(desc);
}
}
接着运行App,点击按钮之后的界面如图所示,可见点击动作的确触发了监听器的onClick方法
如果一个页面只有一个按钮,单独定义新的监听器倒也无妨,可是如果存在许多按钮,每个按钮都定义自己的监听器,那就劳民伤财了。对于同时监听多个按钮的情况,更好的办法是注册统一的监听器,也就是让当前页面实现接口View.OnClickListener,如此一来,onClick方法便写在了页面代码之内。因为是统一的监听器,所以onClick内部需要判断是哪个按钮被点击了,也就是利用视图对象的getId方法检查控件编号,完整的onClick代码举例如下:
// 公共的点击事件监听
@Override
public void onClick(View v) {
if (v.getId()==R.id.btn_click_public){
String desc=String.format("%s 您点击了按钮:%s", DateUtil.getNowTime(),((Button) v).getText());
tvResult.setText(desc);
}
}
当然该页面的onCreate内部别忘了调用按钮对象的setOnClickListener方法,把按钮的点击监听器设置成当前页面,设置代码如下所示:
// 从布局文件中获取名为btn_click_public的按钮控件
Button btn_click_public = findViewById(R.id.btn_click_public);
// 设置点击监听器,一旦用户点击按钮,就触发监听器的onClick方法
btn_click_public.setOnClickListener(this);
重新运行App,点击第二个按钮之后的界面如图所示,可见当前页面的onClick方法也正确执行了。
点击实践完整代码如下:
XML布局文件代码:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ButtonClickActivity">
<Button
android:id="@+id/btn_click_single"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="指定单独的点击监听器"
android:textColor="#000000"
android:textSize="17sp"/>
<Button
android:id="@+id/btn_click_public"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="指定公共的点击监听器"
android:textColor="#000000"
android:textSize="17sp"/>
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:textColor="@color/black"
android:textSize="17sp"
android:text="这里查看按钮点击的结果"/>
LinearLayout>
Java 逻辑代码如下:
package com.example.buttoncontrols;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.example.buttoncontrols.util.DateUtil;
/**
* @author Natural-Pride
*/
public class ButtonClickActivity extends AppCompatActivity implements View.OnClickListener {
private TextView tvResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_button_click);
Button btnClickSingle = findViewById(R.id.btn_click_single);
Button btnClickPublic = findViewById(R.id.btn_click_public);
tvResult = findViewById(R.id.tv_result);
btnClickSingle.setOnClickListener(new MyOnClickListener(tvResult));
btnClickPublic.setOnClickListener(this);
}
// 公共的点击事件监听
@Override
public void onClick(View v) {
if (v.getId()==R.id.btn_click_public){
String desc=String.format("%s 您点击了按钮:%s", DateUtil.getNowTime(),((Button) v).getText());
tvResult.setText(desc);
}
}
// 单独的点击事件监听
static class MyOnClickListener implements View.OnClickListener {
private final TextView tvResult;
public MyOnClickListener(TextView tvResult) {
this.tvResult=tvResult;
}
@Override
public void onClick(View view) {
String desc=String.format("%s 您点击了按钮:%s", DateUtil.getNowTime(),((Button)view).getText());
tvResult.setText(desc);
}
}
}
实现效果如图:
点击前:
点击单独的点击监听器:
点击公共的点击监听器
除了点击事件,Android还设计了另外一种长按事件,每当控件被按住超过500毫秒之后,就会触发该控件的长按事件。若要捕捉按钮的长按事件,可调用按钮对象的setOnLongClickListener方法设置长按监听器。具体的设置代码示例如下:
package com.example.buttoncontrols;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.example.buttoncontrols.util.DateUtil;
public class ButtonLongActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_button_long);
TextView tvResult=findViewById(R.id.tv_result);
Button btnLongClick=findViewById(R.id.btn_long_click);
btnLongClick.setOnLongClickListener(v -> {
String desc=String.format("%s 您点击了按钮:%s", DateUtil.getNowTime(),((Button)v).getText());
tvResult.setText(desc);
return true;
});
}
}
再次运行App,长按按钮之后的界面如图所示,说明长按事件果然触发了onLongClick方法。
值得注意的是,点击监听器和长按监听器不局限于按钮控件,其实它们都来源于视图基类View,凡是从View派生而来的各类控件,均可注册点击监听器和长按监听器。譬如文本视图TextView,其对象也能调用setOnClickListener方法与setOnLongClickListener方法,此时TextView控件就会响应点击动作和长按动作。因为按钮存在按下和松开两种背景,便于提示用户该控件允许点击,但文本视图默认没有按压背景,不方便判断是否被点击,所以一般不会让文本视图处理点击事件和长按事件。
尽管按钮控件生来就是给人点击的,可是某些情况希望暂时禁止点击操作,譬如用户在注册的时候,有的网站要求用户必须同意指定条款,而且至少浏览10秒之后才能点击注册按钮。那么在10秒之前,注册按钮应当置灰且不能点击,等过了10秒之后,注册按钮才恢复正常。在这样的业务场景中,按钮先后拥有两种状态,即不可用状态与可用状态,它们在外观和功能上的区别如下:
(1)不可用按钮:按钮不允许点击,即使点击也没反应,同时按钮文字为灰色。
(2)可用按钮:按钮允许点击,点击按钮会触发点击事件,同时按钮文字为正常的黑色。
从上述的区别说明可知,不可用与可用状态主要有两点差异:其一,是否允许点击;其二,按钮文字的颜色。就文字颜色而言,可在布局文件中使用textColor属性设置颜色,也可在Java代码中调用setTextColor方法设置颜色。至于是否允许点击,则需引入新属性android:enabled,该属性值为true时表示启用按钮,即允许点击按钮;该属性值为false时表示禁用按钮,即不允许点击按钮。在Java代码中,则可通过setEnabled方法设置按钮的可用状态(true表示启用,false表示禁用)。
接下来通过一个例子演示按钮的启用和禁用操作。为了改变测试按钮的可用状态,需要额外添加两个控制按钮,分别是“启用测试按钮”和“禁用测试按钮”,加起来一共3个按钮控件,注意“测试按钮”默认是灰色文本。测试界面的布局效果如图所示。
与图对应的布局文件内容如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ButtonEnableActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_enable"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="启用测试按钮"
android:textColor="#000000"
android:textSize="17sp" />
<Button
android:id="@+id/btn_disable"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="禁用测试按钮"
android:textColor="#000000"
android:textSize="17sp" />
LinearLayout>
<Button
android:id="@+id/btn_test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="测试按钮"
android:textColor="#888888"
android:textSize="17sp"
android:enabled="false"/>
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="这里查看测试按钮的点击结果"
android:textColor="@color/black"
android:textSize="17sp" />
LinearLayout>
然后在Java代码中给3个按钮分别注册点击监听器,注册代码如下所示:
package com.example.buttoncontrols;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.example.buttoncontrols.util.DateUtil;
public class ButtonEnableActivity extends AppCompatActivity implements View.OnClickListener {
private Button btnTest;
private TextView tvResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_button_enable);
Button btnEnable = findViewById(R.id.btn_enable);
Button btnDisable = findViewById(R.id.btn_disable);
btnTest = findViewById(R.id.btn_test);
tvResult = findViewById(R.id.tv_result);
btnEnable.setOnClickListener(this);
btnDisable.setOnClickListener(this);
btnTest.setOnClickListener(this);
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_enable) {
// 启用当前控件
btnTest.setEnabled(true);
// 设置按钮的文字颜色
btnTest.setTextColor(Color.BLACK);
} else if (id == R.id.btn_disable) {
// 禁用当前控件
btnTest.setEnabled(false);
btnTest.setTextColor(Color.GRAY);
} else {
String desc = String.format("%s 您点击了按钮:%s", DateUtil.getNowTime(), ((Button) v).getText());
tvResult.setText(desc);
}
}
}
最后编译运行App,点击了“启用测试按钮”之后,原本置灰的测试按钮btn_test恢复正常的黑色文本,点击该按钮发现界面有了反应,具体效果如图所示
对比两图,观察按钮启用前后的外观及其是否响应点击动作,即可知晓禁用按钮和启用按钮两种模式的差别。
本节介绍了与图像显示有关的几种控件用法,包括:专门用于显示图片的图像视图以及若干缩放类型效果,支持显示图片的按钮控件——图像按钮,如何在按钮控件上同时显示文本和图标等。
显示文本用到了文本视图TextView,显示图像则用到图像视图ImageView。由于图像通常保存为单独的图片文件,因此需要先把图片放到res/drawable目录,然后再去引用该图片的资源名称。比如现在有张图片名为laugh.png,那么XML文件通过属性android:src设置图片资源,属性值格式形如“@drawable/不含扩展名的图片名称”。添加了src属性的ImageView标签示例如下:
<ImageView
android:id="@+id/iv_scale"
android:layout_width="match_parent"
android:layout_height="220dp"
android:src="@drawable/laugh" />
若想在Java代码中设置图像视图的图片资源,可调用ImageView控件的setImageResource方法,方法参数格式形如“R.drawable.不含扩展名的图片名称”。仍以上述的苹果图片为例,给图像视图设置图片资源的代码例子如下所示:
package com.example.imagedisplay;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;
public class ImageScaleActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image_scale);
ImageView ivScale= findViewById(R.id.iv_scale);
ivScale.setImageResource(R.drawable.laugh);
}
}
运行测试App,展示图片的界面效果如图所示
观察效果图发现图片居中显示,而非文本视图里的文字那样默认靠左显示,这是怎么回事?
原来ImageView本身默认图片居中显示,不管图片有多大抑或有多小,图像视图都会自动缩放图片,使之刚好够着ImageView的边界,并且缩放后的图片保持原始的宽高比例,看起来图片很完美地占据视图中央。
这种缩放类型在XML文件中通过属性android:scaleType定义,即使图像视图未明确指定该属性,系统也会默认其值为fitCenter,表示让图像缩放后居中显示。添加了缩放属性的ImageView标签如下所示:
<ImageView
android:id="@+id/iv_scale"
android:layout_width="match_parent"
android:layout_height="220dp"
android:src="@drawable/laugh"
android:scaleType="fitCenter"/>
在Java代码中可调用setScaleType方法设置图像视图的缩放类型,其中fitCenter对应的类型为ScaleType.FIT_CENTER,设置代码示例如下:
// 将缩放类型设置为“保持宽高比例,缩放图片使其位于视图中间”
iv_scale.setScaleType(ImageView.ScaleType.FIT_CENTER);
除了居中显示,图像视图还提供了其他缩放类型,详细的缩放类型取值说明见表
常见的按钮控件Button其实是文本按钮,因为按钮上面只能显示文字,不能显示图片,ImageButton才是显示图片的图像按钮。虽然ImageButton号称图像按钮,但它并非继承Button,而是继承了ImageView,所以凡是ImageView拥有的属性和方法,ImageButton统统拿了过来,区别在于ImageButton有个按钮背景。
尽管ImageButton源自ImageView,但它毕竟是个按钮呀,按钮家族常用的点击事件和长按事件,ImageButton全都没落下。不过ImageButton和Button之间除了名称不同,还有下列差异:
Button既可显示文本也可显示图片(通过setBackgroundResource方法设置背景图片),而ImageButton只能显示图片不能显示文本。
ImageButton上的图像可按比例缩放,而Button通过背景设置的图像会拉伸变形,因为背景图采取fitXY方式,无法按比例缩放。
Button只能靠背景显示一张图片,而ImageButton可分别在前景和背景显示图片,从而实现两张图片叠加的效果。
从上面可以看出,Button与ImageButton各有千秋,通常情况使用Button就够用了。但在某些场合,比如输入法打不出来的字符,以及特殊字体显示的字符串,就适合先切图再放到ImageButton。
举个例子,数学常见的开平方运算,由输入法打出来的运算符号为“√”,但该符号缺少右上角的一横,正确的开平方符号是带横线的,此时便需要通过ImageButton显示这个开方图片。不过使用ImageButton得注意,图像按钮默认的缩放类型为center(保持原始尺寸不缩放图片),而非图像视图默认的fitCenter,倘若图片尺寸较大,那么图像按钮将无法显示整个图片。为避免显示不完整的情况,XML文件中的ImageButton标签必须指定fitCenter的缩放类型,详细的标签内容示例如下:
<ImageButton
android:layout_width="match_parent"
android:layout_height="80dp"
android:src="@drawable/sqrt"
android:scaleType="fitCenter" />
打开演示界面如图所示,可见图像按钮正确展示了开平方符号
现在有了Button可在按钮上显示文字,又有ImageButton可在按钮上显示图像,照理说绝大多数场合都够用了。然而现实项目中的需求往往捉摸不定,例如客户要求在按钮文字的左边加一个图标,这样按钮内部既有文字又有图片,乍看之下Button和ImageButton都没法直接使用。若用LinearLayout对ImageView和TextView组合布局,虽然可行,XML文件却变得冗长许多。
其实有个既简单又灵活的办法,要想在文字周围放置图片,使用按钮控件Button就能实现。原来Button悄悄提供了几个与图标有关的属性,通过这些属性即可指定文字旁边的图标,以下是有关的图标属性说明。
drawableTop:指定文字上方的图片。
drawableBottom:指定文字下方的图片。
drawableLeft:指定文字左边的图片。
drawableRight:指定文字右边的图片。
drawablePadding:指定图片与文字的间距。
譬如下面是个既有文字又有图标的Button标签例子:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ImageTextActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="图标在左"
android:drawableStart="@drawable/error"
android:background="#FFFFFF"
android:drawablePadding="5dp"/>
LinearLayout>
效果如下图:
本章节其他文章
Android App开发-简单控件(1)—— 文本显示
Android App开发-简单控件(2)—— 视图基础
Android App开发-简单控件(3)—— 常用布局
Android App开发-简单控件(4)—— 按钮触控和图像显示
本笔记参考于[B站动脑学院],仅作学习用途,方便随时查看。
参考资料:2022 最新Android基础教程,从开发入门到项目实战,看它就够了