下面的代码(可以直接在 dartpad 中复制/粘贴)根据类别列表创建 3 个选项卡,在本例中:
'People', 'Cars', 'Animals'
。
该应用程序无法正常工作,因为如果您单击“计数”按钮,显然所有页面上的计数器都会增加。每个选项卡页的计数器应分别增加。所以我想每页有一个单独的
Data
实例,但我不知道如何在 Flutter 中做到这一点。
我无法为每个类别创建继承的数据对象,因为对于我的真实应用程序,我事先不知道类别。
有什么想法吗?
此代码可以直接复制/粘贴到https://dartpad.dev/
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Data>(
create: (context) => Data(),
child: MaterialApp(
title: _title,
home: MyHomePage(),
));
}
}
class MyHomePage extends StatelessWidget {
List<String> categories = ['People', 'Cars', 'Animals'];
List<Tab> getTabs(int tabsCount) {
List<Tab> tabs = [];
for (var category in categories) {
tabs.add(
Tab(text: category),
);
}
return tabs;
}
@override
Widget build(BuildContext context) {
var tabs = getTabs(4);
var data = Provider.of<Data>(context);
return DefaultTabController(
length: tabs.length,
child: Builder(builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: tabs,
),
),
body: TabBarView(
children: tabs.map((Tab tab) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
tab.text!,
style: Theme.of(context).textTheme.headline5,
),
const SizedBox(height: 30),
Text(
data.counter.toString(),
style: Theme.of(context).textTheme.headline3,
),
const SizedBox(height: 30),
ElevatedButton(
onPressed: data.increment, child: const Text('Count'))
],
);
}).toList(),
),
);
}),
);
}
}
class Data extends ChangeNotifier {
int counter = 0;
void increment() {
counter++;
notifyListeners();
}
}
所以我想出了以下解决方案,请参阅下面的代码,您可以直接在 DartPad 中运行:我已将
ChangeNotifierProvider
移动到直接环绕标签页。这样您就可以拥有同一类型的多个实例。 tabPage 在树中查找提供者并找到最近的一个。
不过,我认为提供者模式不是很灵活。在下面的示例中,每次选项卡页移出视图时都会处理数据提供程序,并在移入视图时重新创建数据提供程序。明天我将在我的真实应用程序中测试这一点。
您可以直接在 dartpad 中复制/粘贴以下代码并自行测试:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
List<MyExtendedTab> getTabs() {
List<MyExtendedTab> tabs = [];
for (var category in Repo.categories) {
tabs.add(
MyExtendedTab(category),
);
}
return tabs;
}
@override
Widget build(BuildContext context) {
var tabs = getTabs();
return DefaultTabController(
length: tabs.length,
child: Builder(builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: tabs,
),
),
body: TabBarView(
children: //tabPages
tabs.map((MyExtendedTab tab) {
// Moved the ChangeNotifierProvider here to directly wrap the tabpage(s) so the tab page will find its corresponding ChangeNotifierProvider when moving op in the widget tree
// this way we can have multiple provider instances of the same type
return ChangeNotifierProvider<Data>(
// Here the Data object (ChangeNotifier) is instantiated with an id. This id determines which data to retrieve.
create: (context) => Data(
category: tab.category,
),
child: Consumer<Data>(builder: (context, data, child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${data.category.name} ${data.category.id}',
style: Theme.of(context).textTheme.headline5,
),
const SizedBox(height: 30),
Text(
data.counter.toString(),
style: Theme.of(context).textTheme.headline3,
),
const SizedBox(height: 30),
ElevatedButton(
onPressed: data.increment,
child: const Text('Count'))
],
);
}));
}).toList(),
),
);
}),
);
}
}
class Data extends ChangeNotifier {
final Category category;
Data({required this.category}) {
print('Data.constructor id:${category.id}');
}
int get counter {
print(
'Data.get counter id:${category.id}; Repo.counters:${Repo.counters.length}');
return Repo.counters[category.id];
}
void increment() {
Repo.counters[category.id]++;
notifyListeners();
print('counters: ${Repo.counters}');
}
}
// the mockup repo: this is where the data is 'stored'.
class Repo {
static List<Category> categories = [
Category(id: 0, name: 'People'),
Category(id: 1, name: 'Cars'),
Category(id: 2, name: 'Animals')
];
// just a list of integers to store the counter values. 1st int is for People, 2nd int for Cars, etc.
static List<int> counters = [0, 0, 0];
}
class Category {
final int id;
final String name;
Category({required this.id, required this.name});
}
// I need the Id in the tab to work with, hence an extendedTab
class MyExtendedTab extends Tab {
final Category category;
MyExtendedTab(this.category, {super.key}) : super(text: category.name);
}
TL;DR 我也看过 RiverPod,但它似乎只适用于简单类型?尽管文档很丰富,但提供的代码示例充其量也很模糊。我最终编写了这样的代码:
onPressed: ref.read(counterProvider(category).notifier).increment,
而不是
onPressed: data.increment,
我更喜欢后者...