4.1 UI基础:视图层次与LayoutParams
在Android中,用户界面由视图(View) 和视图组(ViewGroup) 组成。View是所有UI组件的基类,比如Button、TextView都是View的子类。ViewGroup则是一个容器,它可以包含多个View或其他的ViewGroup,用于管理子视图的布局。
视图树 的概念:一个Activity的界面就是一棵视图树,根节点通常是一个ViewGroup(如ConstraintLayout、LinearLayout等),子节点可以是ViewGroup或View。setContentView(R.layout.activity_main) 就是将布局文件解析成这棵视图树,并附加到Activity的窗口上。
LayoutParams(布局参数) :每个子视图都需要告诉父容器自己应该如何被放置。LayoutParams就是用于存储这些信息的对象,比如宽度、高度、边距等。在XML中,我们通过 android:layout_width 和 android:layout_height 来指定,常用的值有:
-
match_parent:填满父容器的可用空间。 -
wrap_content:根据视图的内容自适应大小。 -
具体数值(如
100dp):指定固定的尺寸(dp是密度无关像素,保证在不同屏幕密度下物理大小一致)。
边距(margin)与内边距(padding):
-
margin:视图与外部其他视图的距离。 -
padding:视图内部内容与视图边界的距离。
4.2 常用布局详解
布局是ViewGroup的子类,负责管理子视图的排列方式。Android提供了多种布局,每种都有其适用场景。
4.2.1 LinearLayout(线性布局)
LinearLayout将其子视图按水平或垂直方向排列。它是最简单、最常用的布局之一。
基本属性:
-
android:orientation:方向,horizontal或vertical。 -
android:gravity:控制子视图在布局中的对齐方式(如center、right、bottom等)。 -
android:layout_weight:权重,用于按比例分配剩余空间。
示例:水平线性布局,带权重:
xml
<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" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮3" />
</LinearLayout>
解释:按钮1和按钮2的宽度为0dp,并分别设置权重1和2,它们将按1:2的比例分配剩余空间(总宽度减去按钮3的宽度)。按钮3宽度为wrap_content,占据其内容所需的空间。
注意 :当在水平LinearLayout中使用权重时,子视图的宽度应设置为 0dp(垂直方向同理,高度为0dp),否则权重可能不会按预期工作。
4.2.2 RelativeLayout(相对布局)
RelativeLayout允许子视图相对于父布局或兄弟视图进行定位。它非常灵活,但嵌套过多时可能降低性能。
常用属性(以相对于父布局为例):
-
android:layout_alignParentTop:是否与父布局顶部对齐。 -
android:layout_alignParentBottom -
android:layout_alignParentLeft -
android:layout_alignParentRight -
android:layout_centerInParent:在父布局中居中。
相对于其他视图:
-
android:layout_toRightOf="@id/view_id":位于指定视图的右侧。 -
android:layout_toLeftOf -
android:layout_above -
android:layout_below -
android:layout_alignTop:与指定视图顶部对齐。 -
等等。
示例:
xml
<RelativeLayout
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="标题"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp" />
<Button
android:id="@+id/button_ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="确定"
android:layout_below="@id/title"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp" />
<Button
android:id="@+id/button_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="取消"
android:layout_toLeftOf="@id/button_ok"
android:layout_alignTop="@id/button_ok"
android:layout_marginRight="20dp" />
</RelativeLayout>
4.2.3 ConstraintLayout(约束布局)
ConstraintLayout是Google推荐的灵活布局,它通过约束关系来定位子视图,几乎可以实现所有其他布局的功能,同时保持视图层级扁平化,有利于性能。
基本约束:每个视图都需要设置水平方向和垂直方向的约束,否则在预览时会出现警告(除非指定具体位置)。常见的约束属性:
-
app:layout_constraintLeft_toLeftOf="parent":左边缘与父容器左边缘对齐。 -
app:layout_constraintRight_toRightOf="parent":右边缘与父容器右边缘对齐。 -
app:layout_constraintTop_toTopOf="parent" -
app:layout_constraintBottom_toBottomOf="parent" -
app:layout_constraintLeft_toRightOf="@id/other_view":左边缘位于其他视图的右侧。 -
等等。
偏移量 :通过 app:layout_constraintHorizontal_bias 和 app:layout_constraintVertical_bias 调整位置。例如,一个视图同时约束到父布局左右两边,并且宽度为固定值,bias可以控制它在水平方向上的偏移比例(0~1,默认0.5居中)。
链(Chain):将多个视图在水平或垂直方向上形成一组,可以通过权重分配空间,类似LinearLayout的权重。
示例:简单的居中TextView:
xml
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello ConstraintLayout!"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
示例:两个按钮水平排列,间距16dp,整体居中:
xml
<Button
android:id="@+id/button_a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮A"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/button_b"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginRight="8dp" />
<Button
android:id="@+id/button_b"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮B"
app:layout_constraintLeft_toRightOf="@id/button_a"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="8dp" />
这里通过 layout_marginRight 和 layout_marginLeft 实现了间距。
ConstraintLayout还提供了很多高级特性,比如Guideline(辅助线)、Barrier(屏障)、Group(组)等,我们将在后续专门讲解。
4.2.4 其他布局简介
-
FrameLayout:最简单的布局,所有子视图都默认放置在左上角,后添加的视图会覆盖前面的视图。常用于Fragment容器、叠加效果等。
-
GridLayout:网格布局,将子视图放置在一个网格中,类似表格。
-
TableLayout:表格布局,以行和列的方式组织视图,但在现代开发中较少使用。
4.3 常用控件详解
控件(View)是用户直接交互的元素。我们逐一介绍最常用的控件及其核心属性和用法。
4.3.1 TextView(文本显示)
TextView是最简单的控件,用于显示文本。除了基本属性外,还有一些高级属性:
-
android:textSize:文字大小,单位常用sp(缩放无关像素)。 -
android:textColor:文字颜色。 -
android:textStyle:样式,如bold(粗体)、italic(斜体)。 -
android:maxLines:最大行数,超出部分用省略号。 -
android:ellipsize:省略号位置,如end、middle、start。 -
android:autoLink:自动识别链接、电话、邮箱等,如web、phone、email、all。 -
android:drawableLeft/drawableRight等:在文本左侧/右侧显示图片。
示例:
xml
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="这是一段非常长的文本,需要被截断并显示省略号,因为可能超出显示区域,所以我们要使用maxLines和ellipsize属性来控制"
android:maxLines="2"
android:ellipsize="end"
android:textSize="16sp"
android:textColor="#333333"
android:autoLink="web"
android:drawableLeft="@drawable/ic_launcher"
android:drawablePadding="8dp" />
4.3.2 EditText(文本输入)
EditText继承自TextView,允许用户输入和编辑文本。
常用属性:
-
android:hint:输入提示。 -
android:inputType:输入类型,如text(普通文本)、textPassword(密码)、number(数字)、phone(电话号码)等。设置合适的inputType会调出对应的虚拟键盘。 -
android:maxLength:最大字符数。 -
android:lines/android:minLines:设置行数。 -
android:singleLine:是否单行(已废弃,推荐用android:maxLines="1")。
监听文本变化:
java
EditText editText = findViewById(R.id.edit_text);
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 实时获取输入内容
}
@Override
public void afterTextChanged(Editable s) { }
});
4.3.3 Button(按钮)与 ImageButton
Button用于触发操作,可以设置文本和背景。ImageButton是按钮的图片版本,没有文本属性。
常用属性:
android:onClick:在Activity中指定一个公共方法名,点击时调用。例如:android:onClick="onButtonClick",然后在Activity中定义public void onButtonClick(View view)。
注意 :使用 onClick 属性时,方法必须是 public void,参数为 View。
自定义按钮样式 :通过 android:background 可以设置颜色、形状、选择器(selector)等,实现点击效果。
示例:带selector的按钮 (需要在 res/drawable 中定义XML文件):
xml
<!-- res/drawable/button_selector.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@color/colorPressed" />
<item android:drawable="@color/colorNormal" />
</selector>
然后在按钮的 background 属性中引用该文件。
4.3.4 ImageView(图片显示)
ImageView用于显示图片,支持本地资源、文件、网络图片(需借助第三方库)。
核心属性:
-
android:src:设置图片资源。 -
android:scaleType:图片缩放类型,常用值:-
center:保持原尺寸居中,不缩放。 -
centerCrop:保持比例缩放,使图片完全覆盖视图,可能裁剪部分区域。 -
centerInside:保持比例缩放,使图片完全显示在视图内。 -
fitXY:拉伸图片填满视图,可能变形。
-
-
android:adjustViewBounds:是否根据图片宽高比调整视图边界,常与maxWidth/maxHeight配合使用。
示例:
xml
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/sample_image"
android:scaleType="centerCrop" />
加载网络图片:推荐使用Glide或Picasso。例如Glide的用法(需添加依赖):
java
Glide.with(context).load("https://example.com/image.jpg").into(imageView);
4.3.5 CheckBox(复选框)与 RadioButton(单选按钮)
CheckBox用于多选,RadioButton用于单选,通常需要配合RadioGroup使用。
CheckBox示例:
xml
<CheckBox
android:id="@+id/cb_agree"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我同意用户协议" />
在Java中可以通过 setOnCheckedChangeListener 监听状态变化。
RadioGroup与RadioButton:
xml
<RadioGroup
android:id="@+id/rg_gender"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioButton
android:id="@+id/rb_male"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="男" />
<RadioButton
android:id="@+id/rb_female"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="女" />
</RadioGroup>
通过 RadioGroup.setOnCheckedChangeListener 获取选中的RadioButton的id。
4.3.6 ProgressBar(进度条)
ProgressBar用于显示进度,有两种风格:圆形(默认)和水平。
圆形ProgressBar:
xml
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
默认是无限循环的圆圈,常用于加载指示。
水平ProgressBar:
xml
<ProgressBar
android:id="@+id/progress_horizontal"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="0" />
在代码中调用 setProgress() 更新进度。
4.3.7 ScrollView(滚动视图)
当内容超出屏幕高度时,可以使用ScrollView使其可以滚动。注意:ScrollView只能包含一个直接子视图(通常是一个LinearLayout或其他布局)。
xml
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- 大量内容 -->
<TextView ... />
<TextView ... />
<!-- ... -->
</LinearLayout>
</ScrollView>
4.4 列表控件:ListView与RecyclerView
列表是移动应用最常见的UI模式之一。Android提供了ListView(传统)和RecyclerView(现代、强大)两种主要方式。
4.4.1 ListView的基本使用
ListView通过适配器(Adapter)将数据源与列表项视图关联起来。
步骤:
-
在布局中添加ListView。
-
准备数据(如字符串数组或集合)。
-
创建适配器(常用ArrayAdapter或自定义BaseAdapter)。
-
将适配器设置给ListView。
示例:简单的字符串列表:
xml
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
java
ListView listView = findViewById(R.id.listview);
String[] data = {"Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape", "Strawberry"};
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, data);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String item = data[position];
Toast.makeText(MainActivity.this, "点击了:" + item, Toast.LENGTH_SHORT).show();
}
});
自定义列表项 :如果需要更复杂的布局,可以创建自定义适配器继承BaseAdapter,并重写 getCount()、getItem()、getItemId() 和 getView() 方法。在 getView() 中,通常会使用ViewHolder模式优化性能(避免重复 findViewById)。
4.4.2 RecyclerView------现代列表之王
RecyclerView是Android 5.0引入的,相比ListView,它更灵活、性能更好,强制使用ViewHolder模式,并提供了布局管理器(LayoutManager)、动画、分隔线等强大功能。
使用RecyclerView的步骤:
-
添加依赖 :在
build.gradle(app模块)中添加:groovy
implementation 'androidx.recyclerview:recyclerview:1.3.2' -
在布局中添加RecyclerView:
xml
<androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" /> -
创建列表项布局 (例如
item_contact.xml):xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="16dp"> <ImageView android:id="@+id/iv_avatar" android:layout_width="48dp" android:layout_height="48dp" android:src="@drawable/ic_launcher_round" /> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginStart="16dp" android:orientation="vertical"> <TextView android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="18sp" android:textColor="#333" /> <TextView android:id="@+id/tv_phone" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="14sp" android:textColor="#999" /> </LinearLayout> </LinearLayout> -
创建数据模型类 (如
Contact.java):java
public class Contact { private String name; private String phone; private int avatarResId; public Contact(String name, String phone, int avatarResId) { this.name = name; this.phone = phone; this.avatarResId = avatarResId; } // getters and setters... } -
创建ViewHolder类:
java
public class ContactViewHolder extends RecyclerView.ViewHolder { ImageView ivAvatar; TextView tvName, tvPhone; public ContactViewHolder(@NonNull View itemView) { super(itemView); ivAvatar = itemView.findViewById(R.id.iv_avatar); tvName = itemView.findViewById(R.id.tv_name); tvPhone = itemView.findViewById(R.id.tv_phone); } } -
创建适配器:
java
public class ContactAdapter extends RecyclerView.Adapter<ContactViewHolder> { private List<Contact> contactList; private OnItemClickListener listener; public interface OnItemClickListener { void onItemClick(int position); } public void setOnItemClickListener(OnItemClickListener listener) { this.listener = listener; } public ContactAdapter(List<Contact> contactList) { this.contactList = contactList; } @NonNull @Override public ContactViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_contact, parent, false); return new ContactViewHolder(view); } @Override public void onBindViewHolder(@NonNull ContactViewHolder holder, int position) { Contact contact = contactList.get(position); holder.ivAvatar.setImageResource(contact.getAvatarResId()); holder.tvName.setText(contact.getName()); holder.tvPhone.setText(contact.getPhone()); holder.itemView.setOnClickListener(v -> { if (listener != null) { listener.onItemClick(position); } }); } @Override public int getItemCount() { return contactList.size(); } } -
在Activity中使用RecyclerView:
java
RecyclerView recyclerView = findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(this)); // 线性布局 // 也可以使用 GridLayoutManager 或 StaggeredGridLayoutManager List<Contact> contacts = new ArrayList<>(); contacts.add(new Contact("张三", "13812345678", R.drawable.avatar1)); contacts.add(new Contact("李四", "13987654321", R.drawable.avatar2)); // ... 添加更多数据 ContactAdapter adapter = new ContactAdapter(contacts); recyclerView.setAdapter(adapter); adapter.setOnItemClickListener(position -> { Contact contact = contacts.get(position); Toast.makeText(this, "点击了:" + contact.getName(), Toast.LENGTH_SHORT).show(); });
RecyclerView的优势:
-
强制使用ViewHolder,性能更好。
-
内置多种布局管理器(线性、网格、瀑布流)。
-
支持添加ItemDecoration(如分割线)。
-
支持ItemAnimator(增删改的动画)。
-
支持部分刷新。
4.4.3 RecyclerView的常用补充
-
添加分割线 :可以继承
RecyclerView.ItemDecoration自定义,或者使用DividerItemDecoration(需导入material库)。 -
添加点击和长按事件 :通常在
onBindViewHolder中为itemView设置监听。 -
多类型布局 :通过重写
getItemViewType()并在onCreateViewHolder中根据类型创建不同的ViewHolder。
4.5 综合实战:构建一个联系人列表
让我们将所学内容整合起来,创建一个简单的联系人列表应用。功能包括:
-
使用RecyclerView展示联系人(头像、姓名、电话)。
-
点击列表项弹出Toast显示联系人姓名。
-
添加一个浮动按钮(FloatingActionButton),点击后添加新的联系人(简化起见,添加固定的测试数据)。
步骤:
-
创建项目,添加RecyclerView依赖和Material Design库(用于FloatingActionButton):
groovy
implementation 'androidx.recyclerview:recyclerview:1.3.2' implementation 'com.google.android.material:material:1.9.0' -
在
activity_main.xml中,使用CoordinatorLayout包裹RecyclerView和FloatingActionButton,实现Snackbar等效果:xml
<androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="16dp" android:src="@android:drawable/ic_input_add" /> </androidx.coordinatorlayout.widget.CoordinatorLayout> -
创建Contact数据类和ContactAdapter(如上所述)。
-
在MainActivity中初始化RecyclerView和FAB,实现添加功能:
java
public class MainActivity extends AppCompatActivity { private RecyclerView recyclerView; private ContactAdapter adapter; private List<Contact> contactList = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 准备初始数据 contactList.add(new Contact("张三", "13812345678", R.drawable.avatar1)); contactList.add(new Contact("李四", "13987654321", R.drawable.avatar2)); contactList.add(new Contact("王五", "13512345678", R.drawable.avatar3)); recyclerView = findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(this)); adapter = new ContactAdapter(contactList); recyclerView.setAdapter(adapter); // 设置点击事件 adapter.setOnItemClickListener(position -> { Contact contact = contactList.get(position); Toast.makeText(MainActivity.this, "点击了:" + contact.getName(), Toast.LENGTH_SHORT).show(); }); // 添加按钮 FloatingActionButton fab = findViewById(R.id.fab); fab.setOnClickListener(v -> { // 添加一个新联系人(示例数据) int newId = contactList.size() + 1; Contact newContact = new Contact("新用户" + newId, "10086", R.drawable.ic_launcher_round); contactList.add(newContact); adapter.notifyItemInserted(contactList.size() - 1); recyclerView.smoothScrollToPosition(contactList.size() - 1); }); } }
运行应用,你将看到联系人列表,点击FAB可以动态添加新联系人,列表自动滚动到底部,并显示Snackbar(可自行添加)。
4.6 小结与下篇预告
在本篇中,我们系统地学习了Android UI开发的基础知识:
-
视图层次与布局参数的概念。
-
常用布局:LinearLayout、RelativeLayout、ConstraintLayout 的特性和使用场景。
-
常用控件:TextView、EditText、Button、ImageView、CheckBox、RadioButton、ProgressBar、ScrollView 的核心属性和用法。
-
列表控件:ListView 的基本用法和 RecyclerView 的完整使用流程,包括适配器、ViewHolder、布局管理器、点击事件等。
-
通过一个联系人列表的实战,将所学知识串联起来。
掌握这些内容后,你已经能够构建丰富、交互性强的用户界面。UI 的世界远不止这些,还包括动画、自定义视图、Material Design 组件、ConstraintLayout 的高级特性等,我们将在后续篇章中深入探讨。
下一篇预告:我们将进入数据持久化领域,学习如何在 Android 中保存和读取数据。我们会详细介绍 SharedPreferences(轻量级键值对存储)、文件存储(内部/外部存储)、SQLite 数据库(使用 Room 库),以及如何管理应用的设置和用户数据。敬请期待!