Android UI入门:XML与常用控件的使用

该如何编写程序界面

在过去,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_widthandroid:layout_height 属性设置了控件的宽和高。

Android 中所有的控件都具有这两个属性,可选的值有三种,分别是:match_parentwrap_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_widthlayout_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.VISIBLEView.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 提供的控件远不止这些,你也不要想一时就学会所有控件的使用,学习是一个漫长的过程。你可以通过查阅官方文档、优秀的教程和以及实践来学习更多控件的更多用法。

相关推荐
恋猫de小郭14 分钟前
Flutter 官方多窗口体验 ,为什么 Flutter 推进那么慢,而 CMP 却支持那么快
android·前端·flutter
yan123686 小时前
Linux 驱动之设备树
android·linux·驱动开发·linux驱动
aningxiaoxixi8 小时前
android stdio 的布局属性
android
CYRUS STUDIO9 小时前
FART 自动化脱壳框架一些 bug 修复记录
android·bug·逆向·fart·脱壳
寻找优秀的自己10 小时前
Cocos 打包 APK 兼容环境表(Android API Level 10~15)
android·cocos2d
大胃粥10 小时前
WMS& SF& IMS: 焦点窗口更新框架
android
QING61811 小时前
Gradle 核心配置属性详解 - 新手指南(二)
android·前端·gradle
QING61811 小时前
Gradle 核心配置属性详解 - 新手指南(一)
android·前端·gradle
_一条咸鱼_14 小时前
Android Runtime内存管理子系统启动流程原理(13)
android·面试·android jetpack