第六章 常用UI控件(下):让界面活起来的交互组件

各位同学,欢迎来到第六章。上一章我们聊了按钮、文本框这些基础控件,今天要讲的这几个家伙,才是真正让APP“动起来”的关键。说白了,列表、下拉、开关、拖动条——这些都是用户和APP交互最频繁的地方。

我个人习惯把这一章叫做“交互五虎将”。为什么?因为你在任何一个商业APP里,几乎都能看到它们的身影。咱们一个一个来拆解。

第六章 常用UI控件(下)知识体系 UI交互控件 ListView 列表控件 RecyclerView 高级列表 Spinner 下拉列表 Switch 开关 SeekBar 拖动条 每个控件都有其适用场景,选对控件比写对代码更重要

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