Flutter, как программно вызвать метод в пользовательском виджете?

Я новичок во Flutter, и я впервые столкнулся с препятствием, пытаясь сделать настраиваемый переключатель. Мой переключатель должен функционально работать так же, как реальный Switch из библиотеки материалов, единственное отличие - это пользовательский интерфейс.

Я использую ValueNotifier и ValueListenableBuilder для обновления значения переключателя из другого виджета. Вот соответствующие части моего кода:

Содержащий виджет

class ParentWidget extends StatefulWidget {
    @override
    _ParentWidget createState() => _ParentWidget();
}

class _ParentWidgetState extends State<ParentWidget> {
    ValueNotifier _switchNotifier = ValueNotifier(false);

    @override
    Widget build(BuildContext context) {
        return Container(
            child: ValueListenableBuilder(
                valueListenable: _switchNotifier,
                builder: (context, switchValue, child) {
                    return _buildSwitch(switchValue);
                },
            ),
        );
    }

    Widget _buildSwitch(bool switchValue) {
        return CustomSwitch(
            key: Key(value.toString()),
            initValue: switchValue,
            onChanged: (value) {
                setState(() {
                    _switchNotifier.value = value;
                });
            },
        );
    }
}

Виджет, изменяющий значение переключателя

class ChildWidget extends StatefulWidget {
    final ValueNotifier _switchNotifier;
    ChildWidget(this._switchNotifier);

    @override
    _ChildWidgetState createState() => _ChildWidgetState();
}

class _ChildWidgetState extends State<ChildWidget> {
    @override
    Widget build(BuildContext context) {
        return Container(
            child: GestureDetector(
                onTap: () {
                    widget._switchNotifier.value = false;
                },
                child: Image(...),
            ),
        );
    }
}

Пользовательский переключатель

class CustomSwitch extends StatefulWidget {
    final ValueChanged<bool> onChanged;
    final bool initValue;
    final Key key;

    const CustomSwitch({
        @required this.key,
        this.onChanged,
        this.initValue,
    });

    @override
    _CustomSwitchState createState() => _CustomSwitchState();
}

class _CustomSwitchState extends State<CustomSwitch> {
    bool value;

    @override
    void initState() {
        value = widget.initValue;
        super.initState();
    }

    @override
    Widget build(BuildContext context) {
        // the switch/toggle is animated just like the Material Switch
        return TheSwitch(...);
    }

    _toggle() {
        setState(() {
            value = !value;
            widget.onChanged(value);
        });
    }
}

Если я вызываю _toggle() с CustomSwitch, переключатель будет хорошо переключаться с анимацией (я использую AnimatedPositioned для переключателя). Это отлично работает, если я полагаюсь только на ввод пользователя, но мне также нужно программно переключать переключатель, и я чувствую, что мне не хватает чего-то базового, но я в тупике.

В настоящее время я понимаю, что CustomSwitch будет перестраиваться каждый раз, когда я изменяю его значение с ChildWidget, потому что я использую значение переключателя как Key, но как сделать это красиво с анимацией, как если бы я звонил _toggle() после того, как он был построен ?

Как и в Java, вы обычно делаете что-то вроде customSwitch.toggle().


person bunbunn    schedule 19.01.2020    source источник


Ответы (1)


Большинство виджетов флаттера используют контроллеры для внешнего взаимодействия с виджетами (TextFormField, ListView и т. Д.).

Самым простым решением вашей проблемы также было бы создание настраиваемого контроллера.

Сначала вы создадите класс контроллера, который будет иметь состояние вашего пользовательского виджета в качестве параметра. Этот класс также будет предоставлять методы вашего виджета. Это будет выглядеть так:

class CustomWidgetController{
  _CustomWidgetState _customWidgetState;

  void _addState(_CustomWidgetState customWidgetState){
    this._customWidgetState = customWidgetState;
  }

  /// Determine if the CustomWidgetController is attached to an instance
  /// of the CustomWidget (this property must return true before any other
  /// functions can be used)
  bool get isAttached => _customWidgetState != null;

  /// Here is the method you are exposing
  void toggle() {
    assert(isAttached, "CustomWidgetController must be attached to a CustomWidget");
    _customWidgetState.toggle();
  }
}

Вам нужно будет принять настраиваемый контроллер в качестве параметра в вашем CustomWidget и передать его в его состояние следующим образом:

class CustomSwitch extends StatefulWidget {
    final CustomWidgetController customWidgetController;
    final bool initValue;
    final Key key;

    const CustomSwitch({
        @required this.key,
        this.customWidgetController,
        this.initValue,
    });

    @override
    _CustomSwitchState createState() => _CustomSwitchState(customWidgetController, initValue);
}

В состоянии вашего настраиваемого класса вы назначите состояние вашего класса контроллеру, используя созданный нами метод addState. Сделать это конструктором можно так:

class _CustomSwitchState extends State<CustomSwitch> {
    final CustomWidgetController _customWidgetController;
    bool value;

    _CustomSwitchState(this._customWidgetController, this.value) {
    if (_customWidgetController != null)
        _customWidgetController._addState(this);
    }

    @override
    Widget build(BuildContext context) {
        // the switch/toggle is animated just like the Material Switch
        return TheSwitch(...);
    }

    toggle() {
        setState(() {
            value = !value;
        });
    }
}

Теперь вы можете передавать контроллер и вызывать методы с помощью контроллера в родительском виджете следующим образом:

  CustomWidgetController customWidgetController = new CustomWidgetController();

  @override
   Widget build(BuildContext context) {
        return CustomWidget(
            controller: customWidgetController,
            initValue: true
        );
    }

Это оно! Теперь вы можете вызвать customWidgetController.toggle() в любом месте кода, чтобы переключить значение! Вот как собственные виджеты позволяют вам взаимодействовать с ними.

person OutOfnames    schedule 27.06.2020