Flutter | 基于函数式编程的通用单选列表设计
背景
项目中多次用到如下图的通用单选列表页:
常规封装
此列表需要三样东西:
- 标题数组
- 当前选中项的 index
- 点击 cell 的回调
封装大体如下:
import 'package:flutter/material.dart';
class ListPage1 extends StatefulWidget {
const ListPage1({
super.key,
required this.titles,
required this.selectedIndex,
required this.onSelected,
});
// 标题数组
final List<String> titles;
// 当前选中项的 index
final int selectedIndex;
// 点击 cell 的回调
final ValueChanged<int> onSelected;
State<ListPage1> createState() => _ListPage1State();
}
class _ListPage1State extends State<ListPage1> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("ListPage1")),
body: ListView.builder(
itemCount: widget.titles.length,
itemBuilder: (context, index) {
// 根据传入的index判断是否选中
final isSelected = widget.selectedIndex == index;
return GestureDetector(
onTap: () {
Navigator.pop(context);
widget.onSelected(index);
},
child: Container(
width: double.infinity,
color: isSelected ? Colors.orange : Colors.white,
height: 50,
child: Text(
widget.titles[index],
style: const TextStyle(fontSize: 30),
),
),
);
},
),
);
}
}
使用:
final person1 = Person(name: 'amy', age: 10);
final person2 = Person(name: 'bob', age: 20);
final person3 = Person(name: 'candy', age: 30);
final persons = [person1, person2, person3];
final normalList = ListPage1(
titles: persons.map((e) => e.name).toList(),
selectedIndex: _selectedIndex,
onSelected: (selectIndex) {
setState(() {
// 保存选中的index
_selectedIndex = selectIndex;
});
},
);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => normalList),
);
存在的问题:
必须在外部保存这个 selectedIndex
,但我们真正需要的其实是这个 index
对应的 person
。
函数式封装
目标是在功能不变的前提下消除 selectedIndex
。
代码如下:
import 'package:flutter/material.dart';
class ListPage2<T> extends StatefulWidget {
const ListPage2({
super.key,
required this.values,
required this.titleBuilder,
required this.selectedStateBuilder,
required this.onSelected,
});
final List<T> values;
// 用函数创建 title
final String Function(T) titleBuilder;
// 用函数表示选中状态
final bool Function(T) selectedStateBuilder;
// 选中回调,返回 model
final ValueChanged<T> onSelected;
State<ListPage2<T>> createState() => _ListPage2State<T>();
}
class _ListPage2State<T> extends State<ListPage2<T>> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("ListPage2")),
body: ListView.builder(
itemCount: widget.values.length,
itemBuilder: (context, index) {
final currentItem = widget.values[index];
final isSelected = widget.selectedStateBuilder(currentItem);
return GestureDetector(
onTap: () {
Navigator.pop(context);
widget.onSelected(currentItem);
},
child: Container(
width: double.infinity,
color: isSelected ? Colors.orange : Colors.white,
height: 50,
child: Text(
widget.titleBuilder(currentItem),
style: const TextStyle(fontSize: 30),
),
),
);
},
),
);
}
}
使用:
final person1 = Person(name: 'amy', age: 10);
final person2 = Person(name: 'bob', age: 20);
final person3 = Person(name: 'candy', age: 30);
final persons = [person1, person2, person3];
final listPage = ListPage2<Person>(
values: persons,
titleBuilder: (person) {
return person.name;
},
selectedStateBuilder: (person) {
return person.name == _currentPerson?.name;
},
onSelected: (person) {
setState(() {
_currentPerson = person;
});
});
Navigator.push(
context,
MaterialPageRoute(builder: (context) => listPage),
);
这种写法直接保存了选中的 person
,没有了中间变量 selectedIndex
,选中状态从之前的通过 selectedIndex
决定,到现在直接由外部函数判断。
所有功能未做任何改变,只是编程思想由命令式变成了函数式。