第六章 常用UI控件(下):让界面活起来的交互组件
各位同学,欢迎来到第六章。上一章我们聊了按钮、文本框这些基础控件,今天要讲的这几个家伙,才是真正让APP“动起来”的关键。说白了,列表、下拉、开关、拖动条——这些都是用户和APP交互最频繁的地方。
我个人习惯把这一章叫做“交互五虎将”。为什么?因为你在任何一个商业APP里,几乎都能看到它们的身影。咱们一个一个来拆解。
6.1 ListView:老牌列表,依然能打
ListView 是 Android 最早的列表控件。我记得刚入行那会儿,几乎所有列表都用它。虽然现在 RecyclerView 更流行,但 ListView 在一些简单场景下依然好用。
它的核心思想就三个字:适配器。数据怎么展示?交给适配器。列表项怎么复用?也交给适配器。
核心要点:ListView = 数据源 + 适配器 + 视图复用
来看一个最简单的例子:
// 1. 准备数据
String[] fruits = {"苹果", "香蕉", "橘子", "葡萄", "西瓜"};
// 2. 创建适配器
ArrayAdapter<String> adapter = new ArrayAdapter<>(
this,
android.R.layout.simple_list_item_1,
fruits
);
// 3. 绑定到ListView
ListView listView = findViewById(R.id.listView);
listView.setAdapter(adapter);
// 4. 设置点击事件
listView.setOnItemClickListener((parent, view, position, id) -> {
String item = parent.getItemAtPosition(position).toString();
Toast.makeText(this, "你选了:" + item, Toast.LENGTH_SHORT).show();
});
小技巧:如果你只需要显示简单的文本列表,ArrayAdapter 就够了。但如果你需要自定义布局(比如带图片的列表项),就得用 BaseAdapter 或者 SimpleAdapter。
我曾经在一个电商项目里,用 ListView 展示商品分类。当时图省事直接用了 ArrayAdapter,结果产品经理说“每个分类前面要加个图标”。嗯,后来老老实实改成了 BaseAdapter。所以我的建议是:如果列表项稍微复杂一点,直接上 BaseAdapter,别走弯路。
6.2 RecyclerView:列表界的“瑞士军刀”
RecyclerView 是 ListView 的升级版。说白了,Google 把 ListView 里那些“不够灵活”的地方全改了。它最大的特点就是:高度解耦。
你想想看,ListView 的布局方向是固定的——垂直。但 RecyclerView 可以通过 LayoutManager 随意切换:
- LinearLayoutManager:线性布局(垂直/水平)
- GridLayoutManager:网格布局
- StaggeredGridLayoutManager:瀑布流布局
来看一个完整的 RecyclerView 示例:
// 1. 定义ViewHolder
public class FruitViewHolder extends RecyclerView.ViewHolder {
TextView nameText;
ImageView iconImage;
public FruitViewHolder(View itemView) {
super(itemView);
nameText = itemView.findViewById(R.id.tv_name);
iconImage = itemView.findViewById(R.id.iv_icon);
}
}
// 2. 定义适配器
public class FruitAdapter extends RecyclerView.Adapter<FruitViewHolder> {
private List<Fruit> fruitList;
public FruitAdapter(List<Fruit> fruitList) {
this.fruitList = fruitList;
}
@Override
public FruitViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_fruit, parent, false);
return new FruitViewHolder(view);
}
@Override
public void onBindViewHolder(FruitViewHolder holder, int position) {
Fruit fruit = fruitList.get(position);
holder.nameText.setText(fruit.getName());
holder.iconImage.setImageResource(fruit.getIcon());
}
@Override
public int getItemCount() {
return fruitList.size();
}
}
// 3. 在Activity中使用
RecyclerView recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(new FruitAdapter(getFruitList()));
注意:RecyclerView 的点击事件需要自己实现。不像 ListView 自带 OnItemClickListener。我一般会在 ViewHolder 里直接设置点击监听,这样更灵活。
为什么说 RecyclerView 是“高级列表”?因为它还支持:
- 动画:添加/删除/移动列表项时自动播放动画
- 分割线:通过 ItemDecoration 自定义
- 局部刷新:调用 notifyItemChanged() 而不是 notifyDataSetChanged()
我个人习惯:新项目一律用 RecyclerView。除非是维护老代码,否则别碰 ListView 了。
6.3 Spinner:下拉列表,省空间利器
Spinner 就是 Android 里的下拉选择框。当选项太多,用 RadioButton 会占满屏幕时,Spinner 就派上用场了。
用法其实和 ListView 很像,也是适配器模式:
// XML布局
<Spinner
android:id="@+id/spinner_city"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
// Java代码
String[] cities = {"北京", "上海", "广州", "深圳", "杭州"};
ArrayAdapter<String> adapter = new ArrayAdapter<>(
this,
android.R.layout.simple_spinner_item,
cities
);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Spinner spinner = findViewById(R.id.spinner_city);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String selected = parent.getItemAtPosition(position).toString();
Log.d("Spinner", "选中了:" + selected);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
// 啥也没选
}
});
避坑指南:我曾经在项目里遇到一个 bug——Spinner 第一次打开时默认选中了第一项,但 onItemSelected 没有被触发。后来发现,默认选中项不会触发 onItemSelected。解决方案是在初始化时手动调用一次回调,或者用 setSelection(0, false) 来避免动画触发。
6.4 Switch:开关控件,二选一的优雅方案
Switch 就是那个“打开/关闭”的滑块。在 Android 5.0 之后,Google 还推出了 SwitchCompat(兼容旧版本)。
用法极其简单:
<Switch
android:id="@+id/switch_wifi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Wi-Fi"
android:checked="true" />
// 监听状态变化
Switch switchWifi = findViewById(R.id.switch_wifi);
switchWifi.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (isChecked) {
// 打开Wi-Fi
} else {
// 关闭Wi-Fi
}
});
这里有个细节:Switch 和 ToggleButton 的区别。Switch 是滑块样式,ToggleButton 是按钮样式。我个人更推荐 Switch,因为它的交互更直观——用户一看就知道“滑过去就是开”。
最佳实践:Switch 适合做“即时生效”的设置,比如 Wi-Fi 开关、蓝牙开关。如果操作需要“保存”按钮确认,那用 CheckBox 更合适。
6.5 SeekBar:拖动条,让用户“微调”
SeekBar 是 ProgressBar 的“可拖动”版本。最常见的场景就是音量调节、亮度调节、视频进度条。
基本用法:
<SeekBar
android:id="@+id/seekbar_volume"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="50" />
SeekBar seekBar = findViewById(R.id.seekbar_volume);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// 进度变化时调用
// fromUser 为 true 表示是用户拖动的,false 表示代码设置的
Log.d("SeekBar", "当前进度:" + progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// 开始拖动
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// 停止拖动
}
});
注意:SeekBar 的 onProgressChanged 会频繁调用。如果你在里面做耗时操作(比如写数据库),会导致界面卡顿。我的做法是:只在 onStopTrackingTouch 里做最终处理,onProgressChanged 只用来更新 UI 预览。
我记得有一次做视频播放器,用户拖动进度条时,我直接在 onProgressChanged 里调了 seekTo()。结果视频画面一顿一顿的,用户体验极差。后来改成“拖动时只更新进度条显示,松手后才真正跳转”,问题就解决了。
6.6 本章小结
好了,这一章的内容就这些。咱们快速回顾一下:
| 控件 | 核心用途 | 推荐场景 |
|---|---|---|
| ListView | 简单列表展示 | 老项目维护、极简列表 |
| RecyclerView | 复杂列表、多种布局 | 新项目首选 |
| Spinner | 下拉选择 | 选项较多时 |
| Switch | 二选一开关 | 即时生效的设置 |
| SeekBar | 拖动调节 | 音量、亮度、进度 |
这些控件看起来简单,但用好它们需要经验。我的建议是:多写、多试、多踩坑。每个坑都是你成长的阶梯。
公众号:蓝海资料掘金营,微信deep3321