该如何编写程序界面
在过去,Android 应用的界面主要是通过编写 XML 文件完成的。XML 可以让我们了解界面背后的实现原理,并且能够帮助我们构建出具有良好的屏幕适配性的界面。
不过 Google 推出了 ConstraintLayout
这一种强大的布局方式,它既支持在可视化编辑器中通过拖拽组件来构建界面,也支持直接编写 XML 文件来构建。
本文专注于通过编写 XML 的传统方式来开发程序界面,我们先来看看常见的几种控件以及其使用方法。
常用控件的使用方法
我们新建一个名为 UIWidgetTest
的 Empty Views Activity 项目。
TextView
TextView(文本控件)可以在界面上显示一段文字信息。
我们修改 activity_main.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">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is TextView" />
</LinearLayout>
我们先只看 TextView 控件,忽略外部的 LinearLayout 布局。
在 TextView 中,我们通过 android:id
属性给当前控件设置了唯一标识符,然后通过 android:layout_width
和 android:layout_height
属性设置了控件的宽和高。
Android 中所有的控件都具有这两个属性,可选的值有三种,分别是:match_parent
、wrap_content
和固定值。
-
match_parent
表示让当前控件的尺寸和父布局的可用空间相匹配。 -
wrap_content
表示让当前控件的大小恰好能显示其内部的内容。 -
固定值就是给当前控件指定一个固定的尺寸,单位一般是
dp
(Density-independent Pixels,密度无关像素),因为这是一种与屏幕密度无关的尺寸单位,能让我们在不同分辨率的手机上显示的效果接近一致。
而上述代码中,我们让 TextView 控件的宽度和父布局一样宽,也就是手机的宽度,高度仅仅包裹住文字内容。运行程序,效果如图所示:
虽然当前文本控件在横向上占满了屏幕宽度,但是从效果上你是看不出的,因为文字内容默认向左上角对齐。所以修改文字内容的对齐方式为居中对齐,并且给文本组件添加背景颜色:
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">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#00BCD4"
android:gravity="center"
android:text="This is TextView" />
</LinearLayout>
我们通过 android:gravity
属性来指定文字在文本组件中的对齐方式。可选值有top、bottom、start、end、center_vertical、center_horizontal、center 等,并且可以使用 | 符号来同时指定多种对齐方式,比如这里的 "center",等同于 "center_vertical|center_horizontal",表示在水平和垂直方向都居中。
并且通过 android:background
属性指定了文本控件的背景颜色,它的值可以是预设值,比如 "@android:color/darker_gray"
,也可以是颜色码的十六进制形式,比如上述中的 "#00BCD4"
。
现在重新运行程序,效果如图:
这也说明了 TextView 的宽度确实是和屏幕宽度一样的。
另外,我们还可以对 TextView 中文字的大小和颜色进行修改,比如:
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">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/darker_gray"
android:gravity="center_vertical|center_horizontal"
android:text="This is TextView"
android:textColor="#00BCD4"
android:textSize="24sp" />
</LinearLayout>
在上述代码中,我们通过 android:textColor
属性指定了文字内容的颜色,通过 android:textSize
属性指定了文字的大小。文字大小的单位是 sp
(Scale-independent Pixels,可缩放像素),它会跟随系统设置的字体大小进行缩放,常用于文本。比如当用户在系统设置中修改了字体大小时,应用中的文字大小也会跟着变化。
重新运行程序,效果如图:
TextView 还有更多的属性,但就不一一看了,当需要用到时,可以查阅官方文档:TextView XML attributes
Button
Button(按钮控件)可用于与用户进行交互,它的 XML 配置和 TextView 是差不多的,我们往界面中加入一个按钮:
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">
...
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button" />
</LinearLayout>
加入了按钮后的界面:
我们可以在 MainActivity.kt
中为按钮注册点击监听器,这里依然使用的是 ViewBinding 来进行视图绑定,与 findViewById
方法相比,具有类型安全、空安全和编译时编译器还会检查的优点。
首先你要在 build.gradle.kts(:app)
配置文件中启用 ViewBinding:
arduino
android {
// ...
buildFeatures {
viewBinding true
}
}
然后在 MainActivity.kt
中使用:
kotlin
class MainActivity : AppCompatActivity() {
private lateinit var viewBinding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 使用 ViewBinding 进行视图绑定
viewBinding = ActivityMainBinding.inflate(layoutInflater)
val root = viewBinding.root
setContentView(root)
viewBinding.button.setOnClickListener {
// 添加点击逻辑
// 这里弹出一个 Toast 通知
Toast.makeText(this, "Button Clicked!", Toast.LENGTH_SHORT).show()
}
}
}
这里是通过 setOnClickListener()
方法和 Lambda 表示式来监听按钮的点击事件的,不过是利用是 Java 单抽象方法接口的特性,简化了代码。我们可以直接在方法的尾 Lambda 表达式中写点击的逻辑代码。
除了使用 Lambda 表达式的函数式API的方式来注册外,也可以通过让 MainActivity
实现接口的方式进行注册:
kotlin
class MainActivity : AppCompatActivity(),View.OnClickListener {
private lateinit var viewBinding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 使用 ViewBinding 进行视图绑定
viewBinding = ActivityMainBinding.inflate(layoutInflater)
val root = viewBinding.root
setContentView(root)
// 注册点击事件
viewBinding.button.setOnClickListener(this)
}
override fun onClick(v: View?) {
when(v?.id){
R.id.button -> {
// 添加点击逻辑
}
}
}
}
这种方式下,我们让 MainActivity
实现了 View.OnClickListener
接口,并重写 onClick
方法,最后调用按钮的 setOnClickListener
方法,并将当前 MainActivity
的实例传入即可。
在 onClick
方法中,我们是通过传入的 View
对象的 id
来区分是哪个控件触发了点击事件。
EditText
EditText(输入框)允许用户输入和编辑内容,我们可以在代码中对这些内容进行处理。
我们往界面中添加一个 EditText:
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">
...
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
重新运行程序,效果如图所示:
我们可以给输入框添加提示性的文字(hint),当用户不输入任何内容时,提示文本会出现;而一旦用户开始输入了,提示性的文本就会自动消失。
我们通过 android:hint
属性指定提示性文本的内容:
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">
...
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Type something here" />
</LinearLayout>
运行结果:
但输入的内容太多时,EditText 会被拉伸为了显示出所有行,因为它的高度是适应内容的(我们把 layout_height
设置为 wrap_content
):
我们可以通过 android:maxLines
属性来解决这个问题,它可以限制 EditText 显示的最大行数,或者你也可以通过 android:lines
属性来指定 EditText 的固定行数:
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">
...
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Type something here"
android:maxLines="2" />
</LinearLayout>
我们指定了输入框显示的最大行数为两行。这样当输入的内容超过两行后,文本会向上滚动,而 EditText 不会被拉伸。
前面说过,我们还可以在代码中对用户输入的内容进行处理。比如,我们在按钮的点击事件中,获取 EditText 的内容,并且通过 Toast 弹出提示:
kotlin
// 注册点击事件
viewBinding.button.setOnClickListener {
val text = viewBinding.editText.text.toString()
Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
}
其中输入内容的获取,看似好像是访问了 EditText 的 text
属性,实际上是调用了 EditText 的 getText
方法。这是 Kotlin 中的属性访问语法糖。
现在重新运行程序,在输入框输入 "Are you Ok?",然后点击按钮,运行效果:
可以看到下方弹出了 "Are you Ok?" 内容的 Toast。
ImageView
ImageView(图片控件)用于在界面中展示图片。
你需要准备两张图片资源(我这里准备了 white_dog.png
, red_moon.png
)。
Android 会根据设备的像素密度从最适合的 drawable-<density>
目录中加载资源,现在主流手机的分辨率是 xxhdpi,所以我们将这两张图片放在 res/drawable-xxhdpi
目录下(没有的话,要新建)。
接下来在界面中添加图片控件:
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">
...
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/white_dog"
/>
</LinearLayout>
我们通过 android:src
属性来指定了图片资源。由于不知道图片的切确尺寸,就将 ImageView 控件的layout_width
和 layout_height
属性设为 wrap_content
,这样不管图片的尺寸是多少,ImageView 会自动调整尺寸以完整地展示图片。
现在重新运行程序,运行效果:
我们还可以通过代码动态更改 ImageView 中的图片:
kotlin
// 在 MainActivity 的内部
private var isShowingWhiteDog = true
// 在 MainActivity 的 onCreate 方法中
viewBinding.button.setOnClickListener {
isShowingWhiteDog = !isShowingWhiteDog
if (isShowingWhiteDog) {
viewBinding.imageView.setImageResource(R.drawable.white_dog)
} else {
viewBinding.imageView.setImageResource(R.drawable.red_moon)
}
}
现在点击按钮后,会切换 ImageView 中显示的图片:
ProgressBar
ProgressBar(进度条控件)用于在界面中显示一个进度条,比如表示当前正在加载数据或是执行耗时的操作。
我们往界面中添加一个 ProgressBar:
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">
...
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
运行效果(这里把界面中的 IamgeView 控件移除了):
可以看到有一个圆形进度条在旋转,并且它会一直旋转。
那数据加载完毕后,该怎么让进度条消失?答案是设置控件的可见(visibility)属性。
所有 Android 控件都有这个属性,属性名是 android:visibility
,可选的值有三种,分别是:visible、invisible 和 gone。
-
visible
是默认值,表示控件可见。 -
invisible
表示控件不可见,虽然不可见,但是控件仍然占着原来的位置和大小,你可以理解为控件只是变得透明了。 -
gone
也表示控件不可见,只不过控件不再占用任何布局空间了。
我们可以在代码中通过 setVisibility()
方法设置控件的可见性,传入 View.VISIBLE
、View.INVISIBLE
和 View.GONE
。
现在我们来实现一种效果:点击按钮,进度条会消失,再次点击,进度条会显现。
代码如下:
kotlin
// 注册点击事件
viewBinding.button.setOnClickListener {
val progressBar = viewBinding.progressBar
if (progressBar.visibility == View.VISIBLE) {
progressBar.visibility = View.GONE
} else {
progressBar.visibility = View.VISIBLE
}
}
在按钮的点击事件中,我们通过 getVisibility()
方法来判断当前 ProgressBar
是否可见,如果可见就将 ProgressBar 隐藏(GONE
),如果不可见就将 ProgressBar 显示出来。
重新运行程序,然后点击按钮,你会发现进度条在显示和隐藏之间来回切换了。
另外我们还可以给进度条指定不同的样式。之前是圆形进度条(默认),现在我们通过 style
属性将它变为水平进度条,并设置最大值:
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">
...
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100" />
</LinearLayout>
我们通过 android:max
属性给进度条设置进度的最大值。然后在代码中动态地改变进度条的当前进度。
kotlin
// 注册点击事件
viewBinding.button.setOnClickListener {
val progressBar = viewBinding.progressBar
val progress = run {
var currentProgress = progressBar.progress
currentProgress += 10
val max = progressBar.max
if (currentProgress > max) {
max
} else {
currentProgress
}
}
progressBar.progress = progress
}
这样每点击一次按钮,就会增加进度条现有的进度,直到达到最大值。
运行效果:
AlertDialog
AlertDialog(警告对话框)可以在界面上弹出一个对话框,这个对话框位于所有界面元素的最顶层。它出现时,会屏蔽应用内其他控件的交互能力,所以 AlertDialog 一般用于显示重要消息、提示警告信息、进行确认操作(在删除某个内容时,弹出确认删除的对话框)。
比如,我们在按钮的点击事件中弹出一个 AlertDialog,询问用户是否确认:
kotlin
viewBinding.button.setOnClickListener {
AlertDialog.Builder(this).apply {
setTitle("This is Dialog")
setMessage("Something important.")
setCancelable(false)
setPositiveButton("OK") { dialog, which ->
}
setNegativeButton("Cancel") { dialog, which ->
}
show()
}
}
在代码中,我们先通过 AlertDialog.Builder(this)
构建了一个对话框的构建器,然后设置了标题、内容、是否可用返回键或是点击对话框外部关闭对话框。然后调用 setPositiveButton()
方法为对话框设置了确定按钮以及 点击事件,调用 setNegativeButton()
方法设置了取消按钮以及 点击事件,最后调用 show()
方法将对话框显示出来。
点击按钮后的运行效果:
总结
最后,Android 中常用控件的使用就这么多了,当然,Android 提供的控件远不止这些,你也不要想一时就学会所有控件的使用,学习是一个漫长的过程。你可以通过查阅官方文档、优秀的教程和以及实践来学习更多控件的更多用法。