Почему изменение состояния дочернего нижнего листа вызывает перестройку родительского виджета? - PullRequest
0 голосов
/ 05 апреля 2020

У меня есть экран Scaffold (ListsScreen). Который имеет кнопку (AddNewListButton), которая открывает модальный нижний лист (ListScreenBottomSheetWidget). Нижний лист имеет TextField (ListTitleInputTextFieldWidget).

Когда я нажимаю на TextField, чтобы открыть клавиатуру, родительский экран перестраивается, благодаря чему все его дочерние виджеты также перестраиваются.

Почему это происходит? У меня сложилось впечатление, что изменения в состоянии только перестраивают себя или своих детей, а не их родителей. И я также добавил конструкторы const почти везде, чтобы избежать перестроений, но это все еще происходит.

Экран родительских списков:

class ListsScreen extends StatelessWidget {
  const ListsScreen();
  static const routeName = '/lists-screen';

  @override
  Widget build(BuildContext context) {
    final user = Provider.of<AuthProvider>(context, listen: false).getUser;
    print('stateless rebuilding');
    return Scaffold(
      appBar: AppBar(
        centerTitle: false,
        title: Text(
          '${user['name']}\'s Lists',
          style: TextStyle(
            color: Theme.of(context).primaryColorLight,
          ),
        ),
        actions: <Widget>[
          const SignOutButton(),
        ],
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(20.0),
          child: Column(
            children: <Widget>[
              SizeConfig.smallDevice
                  ? const SizedBox(
                      height: 30,
                    )
                  : const SizedBox(
                      height: 40,
                    ),
              SizeConfig.smallDevice
                  ? Text(
                      'Welcome to TODOS',
                      textAlign: TextAlign.center,
                      style: TextStyle(
                        fontSize: 20,
                        color: Colors.grey[700],
                      ),
                    )
                  : Text(
                      'Welcome to TODOS',
                      textAlign: TextAlign.center,
                      style: TextStyle(
                        fontSize: 25,
                        color: Colors.grey[700],
                      ),
                    ),
              SizeConfig.smallDevice
                  ? const SizedBox(
                      height: 30,
                    )
                  : const SizedBox(
                      height: 40,
                    ),
              const AddNewListButton(),
              SizeConfig.smallDevice
                  ? const SizedBox(
                      height: 30,
                    )
                  : const SizedBox(
                      height: 40,
                    ),
              const UserListsListViewWidget(),
            ],
          ),
        ),
      ),
    );
  }
}

class SignOutButton extends StatelessWidget {
  const SignOutButton();

  Future<void> _submitRequest(BuildContext context) async {
    _showLoadingAlert(context);

    try {
      await Provider.of<AuthProvider>(context, listen: false)
          .submitLogOutRequest();
      Navigator.of(context).pop();
      Navigator.of(context).pushReplacementNamed(LoginScreen.routeName);
    } on HttpExceptions catch (error) {
      Navigator.of(context).pop();
      _showErrorDialogue(error.getErrorList, context);
    }
  }

  void _showErrorDialogue(List<dynamic> errorMessages, BuildContext context) {
    showDialog(
      context: context,
      builder: (ctx) => Dialog(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(20.0),
        ),
        child: ErrorListWidget(errorMessages: errorMessages),
      ),
    );
  }

  void _showLoadingAlert(BuildContext context) {
    showDialog(
      context: context,
      builder: (ctx) => const LoadingWidget(),
    );
  }

  @override
  Widget build(BuildContext context) {
    return FlatButton(
      onPressed: () => _submitRequest(context),
      child: Row(
        children: <Widget>[
          Text(
            'Sign Out',
            style: TextStyle(
              color: Theme.of(context).primaryColorLight,
            ),
          ),
          Icon(
            Icons.exit_to_app,
            color: Theme.of(context).primaryColorLight,
          ),
        ],
      ),
    );
  }
}

class AddNewListButton extends StatelessWidget {
  const AddNewListButton();

  void _modalBottomSheetMenu(BuildContext context) {
    showModalBottomSheet(
      context: context,
      backgroundColor: Colors.transparent,
      isScrollControlled: true,
      builder: (builder) {
        return const ListScreenBottomSheetWidget();
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(5),
      ),
      elevation: 10,
      color: Theme.of(context).primaryColor,
      onPressed: () => _modalBottomSheetMenu(context),
      child: Text(
        '+ Add List',
        style: TextStyle(
          color: Colors.white,
          fontSize: SizeConfig.smallDevice ? 10 : 15,
        ),
      ),
    );
  }
}

Модальный нижний лист:

import 'package:flutter/material.dart';
import 'package:todo_spicotech/helpers/size_config.dart';

class ListScreenBottomSheetWidget extends StatelessWidget {
  const ListScreenBottomSheetWidget();
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        FocusScopeNode currentFocus = FocusScope.of(context);
        if (!currentFocus.hasPrimaryFocus) {
          currentFocus.unfocus();
        }
        currentFocus.unfocus();
      },
      child: Container(
        margin: const EdgeInsets.all(20.0),
        padding: EdgeInsets.only(
          bottom: MediaQuery.of(context).viewInsets.bottom,
        ),
        child: Material(
          borderRadius: BorderRadius.all(Radius.circular(15)),
          elevation: 10,
          child: Padding(
            padding: const EdgeInsets.all(20.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[

                SizeConfig.smallDevice
                    ? Text(
                        'Create a new List',
                        textAlign: TextAlign.center,
                        style: TextStyle(
                          fontSize: 20,
                          color: Colors.grey[700],
                        ),
                      )
                    : Text(
                        'Create a new List',
                        textAlign: TextAlign.center,
                        style: TextStyle(
                          fontSize: 25,
                          color: Colors.grey[700],
                        ),
                      ),
                SizeConfig.smallDevice
                    ? const SizedBox(
                  height: 20,
                )
                    : const SizedBox(
                  height: 30,
                ),
                const ListTitleInputTextFieldWidget(),
                SizeConfig.smallDevice
                    ? const SizedBox(
                  height: 20,
                )
                    : const SizedBox(
                  height: 30,
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    InkWell(
                      borderRadius: BorderRadius.circular(5),
                      onTap: () {
                        Navigator.of(context).pop();
                      },
                      child: Ink(
                        padding: EdgeInsets.all(10),
                        child: const Text('CANCEL'),
                      ),
                    ),
                    RaisedButton(
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(5),
                      ),
                      elevation: 10,
                      color: Theme.of(context).primaryColor,
                      onPressed: () {},
                      child: Text(
                        'Create',
                        style: TextStyle(
                          color: Colors.white,
                          fontSize: SizeConfig.smallDevice ? 10 : 15,
                        ),
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class ListTitleInputTextFieldWidget extends StatefulWidget {
  const ListTitleInputTextFieldWidget();
  @override
  _ListTitleInputTextFieldWidgetState createState() => _ListTitleInputTextFieldWidgetState();
}

class _ListTitleInputTextFieldWidgetState extends State<ListTitleInputTextFieldWidget> {
  @override
  Widget build(BuildContext context) {
    return TextFormField(
      decoration: const InputDecoration(
        enabledBorder: OutlineInputBorder(
          borderSide: BorderSide(
            color: Colors.lightBlue,
          ),
        ),
        focusedBorder: OutlineInputBorder(
          borderSide: BorderSide(
            color: Colors.lightBlue,
          ),
        ),
        labelText: 'List Title',
        contentPadding: EdgeInsets.all(10),
      ),
    );
  }
}

1 Ответ

0 голосов
/ 06 апреля 2020

когда вы звоните showModalBottomSheet, он на самом деле использует Navigator внутри

return Navigator.of(context, rootNavigator: useRootNavigator).push(_ModalBottomSheetRoute<T>(
builder: builder,

исходный код showModalBottomSheet https://github.com/flutter/flutter/blob/17079f26b54c8517678699a0cefe5f7bfec67b3f/packages/flutter/lib/src/material/bottom_sheet.dart#L635

Ответ команды флаттера на вопрос Pages on Navigator stack rebuild when a new page is pushed https://github.com/flutter/flutter/issues/11655#issuecomment -348287396

Работает, как задумано. В общем, вы должны предполагать, что все виджеты могут быть перестроены в любое время, а что нет, это в основном просто оптимизация.

В частности, маршруты будут перестраиваться, потому что их состояние навигатора изменилось, поэтому им может потребоваться обновить как они рисуют кнопки назад и тому подобное.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...