在过去,Android 应用程序的界面主要是通过编写 XML 的方式来实现的。写 XML 的好处是,我们不仅能够了解界面背后的实现原理,而且编写出来的界面还可以具备很好的屏幕适配性。等你完全掌握了使用 XML 来编写界面的方法之后,不管是进行高复杂度的界面实现,还是分析和修改当前现有的界面,对你来说都将是手到擒来。
不过最近几年,Google 又推出了一个全新的界面布局:ConstraintLayout。和以往传统的布局不同,ConstraintLayout 不是非常适合通过编写 XML 的方式来开发界面,而是更加适合在可视化编辑器中使用拖放控件的方式来进行操作,并且 Android Studio 中也提供了非常完备的可视化编辑器。
虽然现在 Google 官方更加推荐使用 ConstraintLayout 来开发程序界面,但由于 ConstraintLayout 的特殊性,很难展示如何通过可视化编辑器来对界面进行动态操作。因此本书中我们仍然采用编写 XML 的传统方式来开发程序界面,并且这也是我认为你必须掌握的基本技能。
讲了这么多理论的东西,也是时候学习一下到底如何编写程序界面了,我们就从 Android 中几种常见的控件开始吧。
Android 给我们提供了大量的 UI 控件,合理地使用这些控件就可以非常轻松地编写出相当不错的界面,下面我们就挑选几种常用的控件,详细介绍一下它们的使用方法。
首先新建一个 UIWidgetTest 项目。简单起见,我们还是允许 Android Studio 自动创建 Activity,Activity 名和布局名都使用默认值。
TextView 的基本用法很简单,这里就不讲解了。这里我们来讲解一个类 SpannableString, SpannableString 其实和 Sring 一样,Textview 可以直接通过设置 SpannableString 来显示文本,只是 SpannableString 可以显示更多的样式风格,我们来看这样一个效果,给一段文本设置下划线,只有下划线的地方可以点击,这样一个效果只用 TextView 是无法实现的,但是用 SpannableString 就很简单了。
我们来实现一下这个效果,布局代码很简单,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tv_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp" />
LinearLayout>
MainActivity.kt 的代码如下所示:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSpecialEffect()
}
private fun setSpecialEffect() {
tv_1.text = "已阅读并同意 "
val clickString = SpannableString("软件许可及服务协议")
clickString.setSpan(object : ClickableSpan() {
override fun onClick(widget: View) {
Toast.makeText(this@MainActivity, "点击了下划线部分内容", Toast.LENGTH_SHORT).show()
}
override fun updateDrawState(ds: TextPaint) {
super.updateDrawState(ds)
// 设置颜色
ds.color = resources.getColor(R.color.purple_200, null)
}
}, 0, clickString.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
tv_1.append(clickString)
// 设置点击效果
tv_1.highlightColor = Color.TRANSPARENT
// 开始响应点击事件
tv_1.movementMethod = LinkMovementMethod.getInstance()
}
}
我们来运行程序看一下效果:
当然 SpannableString 的可以设置的效果还有很多,有兴趣的可以去查看资料。
同样,Button 的基本用法很简单,我们也不讲,我们来讲如何给 Button 设置一个点击效果,我们先来看看具体是怎么个效果,如下所示,点击按钮的时候,有一个反馈:
我们来实现一下这个效果,要想实现这个效果我们只需要在 drawable 下写一个背景选择器 selector 就可以了,具体代码如下:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="@color/purple_200_alpha"/>
shape>
item>
<item>
<shape>
<solid android:color="@color/purple_200"/>
shape>
item>
selector>
然后在布局文件中设置 background 就可以实现点击效果了,具体代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:gravity="center"
android:orientation="vertical">
<Button
style="?android:attr/borderlessButtonStyle"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:background="@drawable/button_bg"
android:text="登录"
android:textSize="18sp" />
LinearLayout>
我们来运行一下看一下效果:
EditText 是程序用于和用户进行交互的另一个重要控件,它允许用户在控件里输入和编辑内容,并可以在程序中对这些内容进行处理。EditText 的应用场景应该算是非常普遍了,发短信、发微博、聊 QQ 等等,在进行这些操作时,你不得不使用到 EditText。那我们来看一看如何在界面上加入 EditText 吧,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical">
<EditText
android:id="@+id/et_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="type something here"
android:textColorHint="@color/purple_200" />
LinearLayout>
看到这里,估计你已经总结出 Android 控件的使用规律了。用法都很相似,给控件定义一个 id,指定控件的宽度和高度,然后再适当加入些控件特有的属性就差不多了,所以使用 XML 来编写界面其实一点都不难。现在运行一下程序,EditText 就已经在界面上显示出来了,并且我们是可以在里面输入内容的。
ImageView 是用于在界面上展示图片的一个控件,它可以让我们的程序界面变得更加丰富多彩。图片通常是放在以 drawable 开头的目录下的,并且要带上具体的分辨率。现在最主流的手机屏幕分辨率大多是 xxhdpi 的,所以我们在 res 目录下再新建一个 drawable-xxhdpi 目录,然后将事先准备好的图片复制到该目录当中就可以使用 ImageView 控件来将它们显示在界面上。
常用的控件肯定不止上面这几种,但是用起来都比较简单,所以这里就不讲了。
ListView 在过去绝对可以称得上是 Android 中最常用的控件之一,几乎所有的应用程序都会用到它。由于手机屏幕空间比较有限,能够一次性在屏幕上显示的内容并不多,当我们的程序中有大量的数据需要展示的时候,就可以借助 ListView 来实现。ListView 允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据会滚动出屏幕。你其实每天都在使用这个控件,比如查看 QQ 聊天记录,翻阅微博最新消息,等等。不过比起前面介绍的几种控件,ListView 的用法相对复杂了很多,因此我们就单独来对 ListView 进行非常详细的讲解。
首先新建一个 ListViewTest 项目,并让 Android Studio 自动帮我们创建好 Activity。然后修改 activity_main.xml 中的代码,如下所示:
<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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
LinearLayout>
在布局中加入 ListView 控件还算非常简单,先为 ListView 指定一个 id,然后将宽度和高度都设置为 match_parent,这样 ListView 就占满了整个布局的空间。
接下来修改 MainActivity 中的代码,如下所示:
class MainActivity : AppCompatActivity() {
private val data = listOf("Apple", "Banana", "Orange", "Watermelon",
"Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango",
"Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape",
"Pineapple", "Strawberry", "Cherry", "Mango")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data)
listView.adapter = adapter
}
}
既然 ListView 是用于展示大量数据的,那我们就应该先将数据提供好。这些数据可以从网上下载,也可以从数据库中读取,应该视具体的应用程序场景而定。这里我们就简单使用一个 data 集合来进行测试,里面包含了很多水果的名称,初始化集合的方式使用的是 listOf() 函数。
不过,集合中的数据是无法直接传递给 ListView 的,我们还需要借助适配器来完成。Android 中提供了很多适配器的实现类,其中我认为最好用的就是 ArrayAdapter。它可以通过泛型来指定要适配的数据类型,然后在构造函数中把要适配的数据传入。ArrayAdapter 有多个构造函数的重载,你应该根据实际情况选择最合适的一种。由于我们这里提供的数据都是字符串,因此将 ArrayAdapter 的泛型指定为 String,然后在 ArrayAdapter 的构造函数中依次传入 Activity 的实例、ListView 子项布局的 id,以及数据源。注意,我们使用了 android.R.layout.simple_list_item_1 作为 ListView 子项布局的 id,这是一个 Android 内置的布局文件,里面只有一个 TextView,可用于简单地显示一段文本。这样适配器对象就构建好了。
最后,还需要调用 ListView 的 setAdapter() 方法,将构建好的适配器对象传递进去,这样 ListView 和数据之间的关联就建立完成了。
现在运行一下程序,效果如下图所示,