布局系统
Android 的布局系统定义了用户界面的结构。通过布局文件,我们可以声明式地定义界面元素及其排列方式,实现各种复杂的界面效果。
布局基础
视图与视图组
Android 的 UI 由 View 和 ViewGroup 两类对象构建:
- View(视图):所有 UI 组件的基类,如 TextView、Button、ImageView 等
- ViewGroup(视图组):继承自 View,可以包含其他 View,用于组织布局
XML 布局文件
布局文件存放在 res/layout/ 目录下,使用 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:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World" />
</LinearLayout>
尺寸单位
Android 支持多种尺寸单位:
| 单位 | 说明 | 使用场景 |
|---|---|---|
| dp (dip) | 密度无关像素,推荐使用 | 大部分尺寸定义 |
| sp | 缩放无关像素,随字体设置缩放 | 字体大小 |
| px | 实际像素,不推荐 | 特殊需求 |
| pt | 点,1pt = 1/72 英寸 | 打印相关 |
| mm | 毫米 | 特殊需求 |
| in | 英寸 | 特殊需求 |
宽高属性
每个视图必须定义 layout_width 和 layout_height:
- match_parent:匹配父容器的大小
- wrap_content:刚好包裹内容
- 具体数值:如
100dp
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="宽度填满,高度自适应" />
<View
android:layout_width="100dp"
android:layout_height="100dp" />
常用布局容器
LinearLayout(线性布局)
LinearLayout 按照水平或垂直方向依次排列子视图。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="第一个" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="第二个"
android:layout_marginTop="8dp" />
</LinearLayout>
关键属性:
| 属性 | 说明 | 值 |
|---|---|---|
| orientation | 排列方向 | horizontal, vertical |
| gravity | 子视图的对齐方式 | center, top, bottom, left, right 等 |
| layout_gravity | 当前视图在父容器中的对齐方式 | 同上 |
| layout_weight | 权重,分配剩余空间 | 数值 |
权重示例:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="按钮1" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="按钮2" />
</LinearLayout>
权重为 1:2,按钮2 的宽度是按钮1 的两倍。
ConstraintLayout(约束布局)
ConstraintLayout 是 Android 推荐的现代布局,功能强大,可以创建复杂的扁平化布局,减少嵌套层级。
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="标题"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/btn_confirm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="确认"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/btn_cancel"
android:layout_marginTop="16dp" />
<Button
android:id="@+id/btn_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="取消"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintStart_toEndOf="@id/btn_confirm"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
常用约束属性:
| 属性 | 说明 |
|---|---|
| layout_constraintTop_toTopOf | 顶部对齐到目标顶部 |
| layout_constraintTop_toBottomOf | 顶部对齐到目标底部 |
| layout_constraintBottom_toTopOf | 底部对齐到目标顶部 |
| layout_constraintBottom_toBottomOf | 底部对齐到目标底部 |
| layout_constraintStart_toStartOf | 起始边对齐到目标起始边 |
| layout_constraintStart_toEndOf | 起始边对齐到目标结束边 |
| layout_constraintEnd_toStartOf | 结束边对齐到目标起始边 |
| layout_constraintEnd_toEndOf | 结束边对齐到目标结束边 |
居中和填充:
<!-- 水平和垂直居中 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="居中显示"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- 填充整个父容器 -->
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#FF5722"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
Bias(偏移):
当视图在两个方向都有约束时,可以使用 bias 控制偏移:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="偏移 30%"
app:layout_constraintHorizontal_bias="0.3"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
Guideline(辅助线):
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="对齐到辅助线"
app:layout_constraintStart_toStartOf="@id/guideline" />
</androidx.constraintlayout.widget.ConstraintLayout>
FrameLayout(帧布局)
FrameLayout 将所有子视图叠加在一起,后添加的视图在上层。适合显示单个视图或叠加效果。
<FrameLayout
android:layout_width="match_parent"
android:layout_height="200dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/background"
android:scaleType="centerCrop" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="叠加文字"
android:layout_gravity="center"
android:textColor="#FFFFFF"
android:textSize="24sp" />
</FrameLayout>
RelativeLayout(相对布局)
RelativeLayout 允许子视图相对于父容器或其他子视图定位。虽然功能强大,但推荐使用 ConstraintLayout 替代。
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="标题"
android:layout_centerHorizontal="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="在标题下方"
android:layout_below="@id/tv_title"
android:layout_centerHorizontal="true" />
</RelativeLayout>
内边距与外边距
padding(内边距)
内边距是视图内容与边框之间的距离:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="内容"
android:padding="16dp" <!-- 四边相同 -->
android:paddingTop="8dp" <!-- 上 -->
android:paddingBottom="8dp" <!-- 下 -->
android:paddingStart="16dp" <!-- 起始边 -->
android:paddingEnd="16dp" /> <!-- 结束边 -->
margin(外边距)
外边距是视图与其他视图之间的距离:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="内容"
android:layout_margin="16dp" <!-- 四边相同 -->
android:layout_marginTop="8dp" <!-- 上 -->
android:layout_marginBottom="8dp" <!-- 下 -->
android:layout_marginStart="16dp" <!-- 起始边 -->
android:layout_marginEnd="16dp" /> <!-- 结束边 -->
在代码中操作布局
获取视图引用
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 传统方式
val textView = findViewById<TextView>(R.id.tv_title)
textView.text = "新标题"
// ViewBinding(推荐)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tvTitle.text = "新标题"
}
}
动态添加视图
val linearLayout = findViewById<LinearLayout>(R.id.container)
val textView = TextView(this).apply {
text = "动态添加的文本"
textSize = 16f
setPadding(16, 16, 16, 16)
}
val params = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
linearLayout.addView(textView, params)
修改布局参数
val textView = findViewById<TextView>(R.id.tv_title)
// 修改宽高
val params = textView.layoutParams
params.width = 200
params.height = 100
textView.layoutParams = params
// 或使用 ConstraintLayout.LayoutParams
val constraintParams = textView.layoutParams as ConstraintLayout.LayoutParams
constraintParams.topToBottom = R.id.header
textView.layoutParams = constraintParams
布局优化
减少布局层级
过多的嵌套会影响性能,应尽量使用 ConstraintLayout 减少层级。
<!-- 不推荐:多层嵌套 -->
<LinearLayout>
<LinearLayout>
<LinearLayout>
<TextView />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<!-- 推荐:扁平化布局 -->
<ConstraintLayout>
<TextView />
</ConstraintLayout>
使用 include 复用布局
<!-- res/layout/item_header.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="标题" />
</LinearLayout>
<!-- 使用 include 引入 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/item_header" />
</LinearLayout>
使用 ViewStub 延迟加载
ViewStub 是一个轻量级的占位视图,只有在需要时才加载实际布局。
<ViewStub
android:id="@+id/stub_progress"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/progress_overlay" />
// 需要时加载
val stub = findViewById<ViewStub>(R.id.stub_progress)
val inflatedView = stub.inflate()
// 或设置为可见时自动加载
stub.visibility = View.VISIBLE
使用 merge 标签
当根布局是 FrameLayout 或不需要根容器时,使用 merge 减少层级:
<!-- res/layout/item_merge.xml -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="文本1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="文本2" />
</merge>
ViewBinding
ViewBinding 是 Android 官方推荐的视图绑定方案,可以替代 findViewById,提供类型安全和空安全保证。
启用 ViewBinding
在模块的 build.gradle.kts 中添加:
android {
viewBinding {
enable = true
}
}
在 Activity 中使用
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 直接访问视图
binding.tvTitle.text = "Hello"
binding.btnSubmit.setOnClickListener {
// 处理点击
}
}
}
在 Fragment 中使用
class MyFragment : Fragment() {
private var _binding: FragmentMyBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentMyBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.tvTitle.text = "Fragment 内容"
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null // 避免内存泄漏
}
}
小结
本章介绍了 Android 布局系统的核心内容:
- 布局基础:View、ViewGroup、尺寸单位、宽高属性
- 常用布局容器:LinearLayout、ConstraintLayout、FrameLayout、RelativeLayout
- 边距处理:padding 和 margin 的使用
- 代码操作:动态添加视图、修改布局参数
- 布局优化:减少层级、include、ViewStub、merge
- ViewBinding:类型安全的视图绑定
下一章将学习常用的 UI 组件,如 TextView、Button、ImageView 等。