如何使用 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 获取当天的 nameText(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),
                  );
                },
              ),
            ),
          ],
        ),
      );
    });
  }
}

添加计划屏幕

  1. 首先,它应该是 StatefulWidget 而不是带有静态变量的 StatelessWidget
  2. 它应该接受一个参数weekdayName
  3. 设置名称时,需要将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',
};