如何使用 Provider 将文本导航到一个小部件而不是同时导航到屏幕上的所有小部件?
How to navigate text to one widget and not to all widgets on the screen at the same time using Provider?
我正在开发一个计划器应用程序,该应用程序的屏幕上有多个小部件('Monday'、'Tuesday' 等)。当我点击一个小部件时,我应该能够使用 TextField在弹出屏幕上并将文本导航到我点击的小部件。现在的问题是提供者同时将文本导航到所有小部件,而不是我点击的一个小部件。我该如何解决?感谢您的帮助
这是一个计划屏幕
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:my_planner_app/weekday_card.dart';
class PlannerScreen extends StatefulWidget {
static const String id = 'planner_screen';
@override
_PlannerScreenState createState() => _PlannerScreenState();
}
class _PlannerScreenState extends State<PlannerScreen>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation animation;
@override
void initState() {
super.initState();
controller =
AnimationController(duration: Duration(seconds: 3), vsync: this);
animation = ColorTween(begin: Colors.grey[800], end: Colors.white)
.animate(controller);
controller.forward();
controller.addListener(() {
setState(() {});
});
}
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
final double itemHeight = (size.height - 24) / 2;
final double itemWidth = size.width / 2;
return Scaffold(
backgroundColor: Color(0xFFcf9e9f),
body: Container(
child: GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: (itemWidth / itemHeight),
),
children: <Widget>[
WeekDayCard(
text: '',
),
WeekDayCard(text: 'Monday'),
WeekDayCard(text: 'Tuesday'),
WeekDayCard(text: 'Wednesday'),
WeekDayCard(text: 'Thursday'),
WeekDayCard(text: 'Friday'),
WeekDayCard(text: 'Saturday'),
WeekDayCard(text: 'Sunday'),
WeekDayCard(text: 'Notes'),
],
),
),
);
}
}
这是关联的小部件
import 'package:flutter/material.dart';
import 'package:my_planner_app/screens/addPlan_screen.dart';
import 'package:provider/provider.dart';
import 'package:my_planner_app/widgets/plan_widget.dart';
class WeekDayCard extends StatelessWidget {
WeekDayCard({@required this.text, this.name});
final String name;
final String text;
@override
Widget build(BuildContext context) {
return Consumer<MyProvider>(builder: (context, myProvider, child) {
return Card(
color: Color(0xFFFEEFCD),
elevation: 10,
child: Column(
children: [
Text(text),
Text(Provider.of<MyProvider>(context).name),
Expanded(
child: InkWell(
onTap: () {
showModalBottomSheet(
backgroundColor: Color(0xFFFEEFCD),
context: context,
builder: (context) => AddPlanScreen(),
);
},
),
),
],
),
);
});
}
}
这是关联弹窗AddScreen
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:my_planner_app/widgets/plan_widget.dart';
class AddPlanScreen extends StatelessWidget {
static String name;
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: TextFormField(
onChanged: (text) {
name = text;
},
decoration: InputDecoration(
border: InputBorder.none,
),
minLines: 10,
maxLines: 30,
autocorrect: false,
),
),
FlatButton(
onPressed: () {
print(name);
Provider.of<MyProvider>(context, listen: false).setName(name);
},
color: Colors.blue,
),
],
);
}
}
提供商
import 'package:flutter/material.dart';
class MyProvider extends ChangeNotifier {
String _name = '';
String get name => _name;
void setName(String newString) {
_name = newString;
print(_name);
notifyListeners();
}
}
ChangeNotifierProvider 放在 MaterialApp 之前
void main() {
runApp(MyPlanner());
}
class MyPlanner extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyProvider(),
child: MaterialApp(
theme: ThemeData(fontFamily: 'IndieFlower'),
initialRoute: WelcomeScreen.id,
routes: {
WelcomeScreen.id: (context) => WelcomeScreen(),
RegisterScreen.id: (context) => RegisterScreen(),
LogInScreen.id: (context) => LogInScreen(),
PlannerScreen.id: (context) => PlannerScreen(),
},
),
);
}
}
快速解决方案 1
(接近您当前的代码库)
您的 ChangeNotifier
应该保留一个 Map<String, String>
而不是 String
,每个工作日一个条目。
提供商
class MyProvider extends ChangeNotifier {
Map<String, String> _names = {};
String name(String key) => _names[key];
void setName(String key, String newString) {
_names[key] = newString;
notifyListeners();
}
}
然后,您将需要进行以下更改:
工作日卡
而不是 Text(Provider.of<MyProvider>(context).name)
,使用键 text
获取当天的 name
:Text(Provider.of<MyProvider>(context).name(text) ?? '')
.
打开模态底部时sheet,传递工作日名称:AddPlanScreen(weekdayName: text)
.
class WeekDayCard extends StatelessWidget {
WeekDayCard({@required this.text, this.name});
final String name;
final String text;
@override
Widget build(BuildContext context) {
return Consumer<MyProvider>(builder: (context, myProvider, child) {
return Card(
color: Color(0xFFFEEFCD),
elevation: 10,
child: Column(
children: [
Text(text),
Text(Provider.of<MyProvider>(context).name(text) ?? ''),
Expanded(
child: InkWell(
onTap: () {
showModalBottomSheet(
backgroundColor: Color(0xFFFEEFCD),
context: context,
builder: (context) => AddPlanScreen(weekdayName: text),
);
},
),
),
],
),
);
});
}
}
添加计划屏幕
- 首先,它应该是
StatefulWidget
而不是带有静态变量的 StatelessWidget
。
- 它应该接受一个参数
weekdayName
- 设置名称时,需要将
weekdayName
作为key传递:Provider.of<MyProvider>(context, listen: false).setName(widget.weekdayName, name);
class AddPlanScreen extends StatefulWidget {
final String weekdayName;
const AddPlanScreen({Key key, this.weekdayName}) : super(key: key);
@override
_AddPlanScreenState createState() => _AddPlanScreenState();
}
class _AddPlanScreenState extends State<AddPlanScreen> {
String name;
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: TextFormField(
onChanged: (text) {
name = text;
},
decoration: InputDecoration(
border: InputBorder.none,
),
minLines: 10,
maxLines: 30,
autocorrect: false,
),
),
ElevatedButton(
onPressed: () {
Provider.of<MyProvider>(context, listen: false)
.setName(widget.weekdayName, name);
},
child: Text('UPDATE'),
),
],
);
}
}
解决方案 2 的进一步重构
在此解决方案中,我将使用 Riverpod instead of Provider. Both packages have been authored by Remi ROUSSELET. Riverpod comes in several flavors, my preference goes for hooks_riverpod。
我保留了相同的结构:
MyPlanner
就是MaterialApp
。我封装在 Riverpod 的 ProviderScope
里面
PlannerScreen
是主屏幕。现在是 StatelessWidget
。它也是响应式的,根据方向显示 4x2 或 2x4 的网格
WeekdayCard
是一个HookWidget
,它取一个weekday
,听planProvider
AddPlanScreen
是一个 HookWidget
。这允许在不需要 StatefulWidget
的情况下维护 TextEditingController。它还使用 context.read
更改 planProvider
的状态
提供商呢?
final planProvider = StateProvider.family<String, int>((ref, weekday) => '');
这是一个使用 .family
提供者修饰符的简单 StateProvider。 (more info)
这使我们能够聆听并修改特定工作日的计划:
听:
final String plan = useProvider(planProvider(weekday)).state;
修改:
context.read(planProvider(weekday)).state = plan;
完整源代码
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() {
runApp(MyPlanner());
}
class MyPlanner extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(fontFamily: 'IndieFlower'),
initialRoute: PlannerScreen.id,
routes: {
// WelcomeScreen.id: (context) => WelcomeScreen(),
// RegisterScreen.id: (context) => RegisterScreen(),
// LogInScreen.id: (context) => LogInScreen(),
PlannerScreen.id: (context) => PlannerScreen(),
},
),
);
}
}
// SCREENS
class PlannerScreen extends StatelessWidget {
static const String id = 'planner_screen';
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFD04E43),
body: LayoutBuilder(
builder: (_, constraints) {
final mainAxisCount =
MediaQuery.of(context).orientation == Orientation.landscape
? 2
: 4;
final crossAxisCount = mainAxisCount == 2 ? 4 : 2;
final aspectRatio =
constraints.biggest.aspectRatio * mainAxisCount / crossAxisCount;
return GridView.count(
crossAxisCount: crossAxisCount,
childAspectRatio: aspectRatio,
children: weekdayNames.keys
.map((weekday) => WeekdayCard(weekday: weekday))
.toList(),
);
},
),
);
}
}
// WIDGETS
class WeekdayCard extends HookWidget {
final int weekday;
WeekdayCard({@required this.weekday});
@override
Widget build(BuildContext context) {
final plan = useProvider(planProvider(weekday)).state;
return InkWell(
onTap: () {
showModalBottomSheet(
backgroundColor: Color(0xFFAFBDB8),
barrierColor: Colors.black38,
context: context,
builder: (context) => AddPlanScreen(weekday: weekday),
);
},
child: Card(
color: Color(0xFFAFBDB8),
elevation: 10,
child: Column(
children: [
Text(weekdayNames[weekday]),
Text(plan),
],
),
),
);
}
}
class AddPlanScreen extends HookWidget {
final int weekday;
const AddPlanScreen({Key key, this.weekday}) : super(key: key);
void submit(BuildContext context, String plan) {
context.read(planProvider(weekday)).state = plan;
Navigator.pop(context);
}
@override
Widget build(BuildContext context) {
final _controller = useTextEditingController(text: '');
return Container(
padding: EdgeInsets.all(16.0),
alignment: Alignment.center,
child: Column(
children: [
Text('What are you planning for ${weekdayNames[weekday]}?'),
const SizedBox(height: 16.0),
TextFormField(
controller: _controller,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderRadius: new BorderRadius.circular(25.0),
borderSide: BorderSide(
color: Colors.black45,
width: 2.0,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: new BorderRadius.circular(25.0),
borderSide: BorderSide(
color: Color(0xFFD04E43),
width: 2.0,
),
),
),
autofocus: true,
onEditingComplete: () => submit(context, _controller.text),
),
const SizedBox(height: 16.0),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Color(0xFF548279),
onPrimary: Colors.white,
),
onPressed: () => submit(context, _controller.text),
child: Text('UPDATE'),
),
],
),
);
}
}
// PROVIDERS
final planProvider = StateProvider.family<String, int>((ref, weekday) => '');
// DOMAIN
const weekdayNames = {
0: 'Notes',
DateTime.monday: 'Monday',
DateTime.tuesday: 'Tuesday',
DateTime.wednesday: 'Wednesday',
DateTime.thursday: 'Thursday',
DateTime.friday: 'Friday',
DateTime.saturday: 'Saturday',
DateTime.sunday: 'Sunday',
};
我正在开发一个计划器应用程序,该应用程序的屏幕上有多个小部件('Monday'、'Tuesday' 等)。当我点击一个小部件时,我应该能够使用 TextField在弹出屏幕上并将文本导航到我点击的小部件。现在的问题是提供者同时将文本导航到所有小部件,而不是我点击的一个小部件。我该如何解决?感谢您的帮助
这是一个计划屏幕
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:my_planner_app/weekday_card.dart';
class PlannerScreen extends StatefulWidget {
static const String id = 'planner_screen';
@override
_PlannerScreenState createState() => _PlannerScreenState();
}
class _PlannerScreenState extends State<PlannerScreen>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation animation;
@override
void initState() {
super.initState();
controller =
AnimationController(duration: Duration(seconds: 3), vsync: this);
animation = ColorTween(begin: Colors.grey[800], end: Colors.white)
.animate(controller);
controller.forward();
controller.addListener(() {
setState(() {});
});
}
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
final double itemHeight = (size.height - 24) / 2;
final double itemWidth = size.width / 2;
return Scaffold(
backgroundColor: Color(0xFFcf9e9f),
body: Container(
child: GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: (itemWidth / itemHeight),
),
children: <Widget>[
WeekDayCard(
text: '',
),
WeekDayCard(text: 'Monday'),
WeekDayCard(text: 'Tuesday'),
WeekDayCard(text: 'Wednesday'),
WeekDayCard(text: 'Thursday'),
WeekDayCard(text: 'Friday'),
WeekDayCard(text: 'Saturday'),
WeekDayCard(text: 'Sunday'),
WeekDayCard(text: 'Notes'),
],
),
),
);
}
}
这是关联的小部件
import 'package:flutter/material.dart';
import 'package:my_planner_app/screens/addPlan_screen.dart';
import 'package:provider/provider.dart';
import 'package:my_planner_app/widgets/plan_widget.dart';
class WeekDayCard extends StatelessWidget {
WeekDayCard({@required this.text, this.name});
final String name;
final String text;
@override
Widget build(BuildContext context) {
return Consumer<MyProvider>(builder: (context, myProvider, child) {
return Card(
color: Color(0xFFFEEFCD),
elevation: 10,
child: Column(
children: [
Text(text),
Text(Provider.of<MyProvider>(context).name),
Expanded(
child: InkWell(
onTap: () {
showModalBottomSheet(
backgroundColor: Color(0xFFFEEFCD),
context: context,
builder: (context) => AddPlanScreen(),
);
},
),
),
],
),
);
});
}
}
这是关联弹窗AddScreen
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:my_planner_app/widgets/plan_widget.dart';
class AddPlanScreen extends StatelessWidget {
static String name;
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: TextFormField(
onChanged: (text) {
name = text;
},
decoration: InputDecoration(
border: InputBorder.none,
),
minLines: 10,
maxLines: 30,
autocorrect: false,
),
),
FlatButton(
onPressed: () {
print(name);
Provider.of<MyProvider>(context, listen: false).setName(name);
},
color: Colors.blue,
),
],
);
}
}
提供商
import 'package:flutter/material.dart';
class MyProvider extends ChangeNotifier {
String _name = '';
String get name => _name;
void setName(String newString) {
_name = newString;
print(_name);
notifyListeners();
}
}
ChangeNotifierProvider 放在 MaterialApp 之前
void main() {
runApp(MyPlanner());
}
class MyPlanner extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyProvider(),
child: MaterialApp(
theme: ThemeData(fontFamily: 'IndieFlower'),
initialRoute: WelcomeScreen.id,
routes: {
WelcomeScreen.id: (context) => WelcomeScreen(),
RegisterScreen.id: (context) => RegisterScreen(),
LogInScreen.id: (context) => LogInScreen(),
PlannerScreen.id: (context) => PlannerScreen(),
},
),
);
}
}
快速解决方案 1
(接近您当前的代码库)
您的 ChangeNotifier
应该保留一个 Map<String, String>
而不是 String
,每个工作日一个条目。
提供商
class MyProvider extends ChangeNotifier {
Map<String, String> _names = {};
String name(String key) => _names[key];
void setName(String key, String newString) {
_names[key] = newString;
notifyListeners();
}
}
然后,您将需要进行以下更改:
工作日卡
而不是 Text(Provider.of<MyProvider>(context).name)
,使用键 text
获取当天的 name
:Text(Provider.of<MyProvider>(context).name(text) ?? '')
.
打开模态底部时sheet,传递工作日名称:AddPlanScreen(weekdayName: text)
.
class WeekDayCard extends StatelessWidget {
WeekDayCard({@required this.text, this.name});
final String name;
final String text;
@override
Widget build(BuildContext context) {
return Consumer<MyProvider>(builder: (context, myProvider, child) {
return Card(
color: Color(0xFFFEEFCD),
elevation: 10,
child: Column(
children: [
Text(text),
Text(Provider.of<MyProvider>(context).name(text) ?? ''),
Expanded(
child: InkWell(
onTap: () {
showModalBottomSheet(
backgroundColor: Color(0xFFFEEFCD),
context: context,
builder: (context) => AddPlanScreen(weekdayName: text),
);
},
),
),
],
),
);
});
}
}
添加计划屏幕
- 首先,它应该是
StatefulWidget
而不是带有静态变量的StatelessWidget
。 - 它应该接受一个参数
weekdayName
- 设置名称时,需要将
weekdayName
作为key传递:Provider.of<MyProvider>(context, listen: false).setName(widget.weekdayName, name);
class AddPlanScreen extends StatefulWidget {
final String weekdayName;
const AddPlanScreen({Key key, this.weekdayName}) : super(key: key);
@override
_AddPlanScreenState createState() => _AddPlanScreenState();
}
class _AddPlanScreenState extends State<AddPlanScreen> {
String name;
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: TextFormField(
onChanged: (text) {
name = text;
},
decoration: InputDecoration(
border: InputBorder.none,
),
minLines: 10,
maxLines: 30,
autocorrect: false,
),
),
ElevatedButton(
onPressed: () {
Provider.of<MyProvider>(context, listen: false)
.setName(widget.weekdayName, name);
},
child: Text('UPDATE'),
),
],
);
}
}
解决方案 2 的进一步重构
在此解决方案中,我将使用 Riverpod instead of Provider. Both packages have been authored by Remi ROUSSELET. Riverpod comes in several flavors, my preference goes for hooks_riverpod。
我保留了相同的结构:
MyPlanner
就是MaterialApp
。我封装在 Riverpod 的ProviderScope
里面
PlannerScreen
是主屏幕。现在是StatelessWidget
。它也是响应式的,根据方向显示 4x2 或 2x4 的网格WeekdayCard
是一个HookWidget
,它取一个weekday
,听planProvider
AddPlanScreen
是一个HookWidget
。这允许在不需要StatefulWidget
的情况下维护 TextEditingController。它还使用context.read
更改
planProvider
的状态
提供商呢?
final planProvider = StateProvider.family<String, int>((ref, weekday) => '');
这是一个使用 .family
提供者修饰符的简单 StateProvider。 (more info)
这使我们能够聆听并修改特定工作日的计划:
听:
final String plan = useProvider(planProvider(weekday)).state;
修改:
context.read(planProvider(weekday)).state = plan;
完整源代码
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() {
runApp(MyPlanner());
}
class MyPlanner extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(fontFamily: 'IndieFlower'),
initialRoute: PlannerScreen.id,
routes: {
// WelcomeScreen.id: (context) => WelcomeScreen(),
// RegisterScreen.id: (context) => RegisterScreen(),
// LogInScreen.id: (context) => LogInScreen(),
PlannerScreen.id: (context) => PlannerScreen(),
},
),
);
}
}
// SCREENS
class PlannerScreen extends StatelessWidget {
static const String id = 'planner_screen';
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFD04E43),
body: LayoutBuilder(
builder: (_, constraints) {
final mainAxisCount =
MediaQuery.of(context).orientation == Orientation.landscape
? 2
: 4;
final crossAxisCount = mainAxisCount == 2 ? 4 : 2;
final aspectRatio =
constraints.biggest.aspectRatio * mainAxisCount / crossAxisCount;
return GridView.count(
crossAxisCount: crossAxisCount,
childAspectRatio: aspectRatio,
children: weekdayNames.keys
.map((weekday) => WeekdayCard(weekday: weekday))
.toList(),
);
},
),
);
}
}
// WIDGETS
class WeekdayCard extends HookWidget {
final int weekday;
WeekdayCard({@required this.weekday});
@override
Widget build(BuildContext context) {
final plan = useProvider(planProvider(weekday)).state;
return InkWell(
onTap: () {
showModalBottomSheet(
backgroundColor: Color(0xFFAFBDB8),
barrierColor: Colors.black38,
context: context,
builder: (context) => AddPlanScreen(weekday: weekday),
);
},
child: Card(
color: Color(0xFFAFBDB8),
elevation: 10,
child: Column(
children: [
Text(weekdayNames[weekday]),
Text(plan),
],
),
),
);
}
}
class AddPlanScreen extends HookWidget {
final int weekday;
const AddPlanScreen({Key key, this.weekday}) : super(key: key);
void submit(BuildContext context, String plan) {
context.read(planProvider(weekday)).state = plan;
Navigator.pop(context);
}
@override
Widget build(BuildContext context) {
final _controller = useTextEditingController(text: '');
return Container(
padding: EdgeInsets.all(16.0),
alignment: Alignment.center,
child: Column(
children: [
Text('What are you planning for ${weekdayNames[weekday]}?'),
const SizedBox(height: 16.0),
TextFormField(
controller: _controller,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderRadius: new BorderRadius.circular(25.0),
borderSide: BorderSide(
color: Colors.black45,
width: 2.0,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: new BorderRadius.circular(25.0),
borderSide: BorderSide(
color: Color(0xFFD04E43),
width: 2.0,
),
),
),
autofocus: true,
onEditingComplete: () => submit(context, _controller.text),
),
const SizedBox(height: 16.0),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Color(0xFF548279),
onPrimary: Colors.white,
),
onPressed: () => submit(context, _controller.text),
child: Text('UPDATE'),
),
],
),
);
}
}
// PROVIDERS
final planProvider = StateProvider.family<String, int>((ref, weekday) => '');
// DOMAIN
const weekdayNames = {
0: 'Notes',
DateTime.monday: 'Monday',
DateTime.tuesday: 'Tuesday',
DateTime.wednesday: 'Wednesday',
DateTime.thursday: 'Thursday',
DateTime.friday: 'Friday',
DateTime.saturday: 'Saturday',
DateTime.sunday: 'Sunday',
};