简体   繁体   English

Flutter 如何以编程方式调用自定义小部件中的方法?

[英]Flutter how to programmatically call method in custom widget?

I'm new to Flutter and I've hit my first roadblock while trying to make a custom switch.我是 Flutter 的新手,在尝试进行自定义切换时遇到了第一个障碍。 My switch should functionally work the same like the actual Switch from the material library, the only difference is the UI.我的开关在功能上应该和材质库中的实际Switch一样,唯一的区别是用户界面。

I'm using ValueNotifier and ValueListenableBuilder to update the switch value from another widget.我正在使用ValueNotifierValueListenableBuilder从另一个小部件更新开关值。 Here's the relevant parts of my code:这是我的代码的相关部分:

Containing Widget包含小工具

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;
                });
            },
        );
    }
}

The widget that changes the switch 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(...),
            ),
        );
    }
}

Custom Switch自定义开关

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);
        });
    }
}

If I'm calling _toggle() from the CustomSwitch the switch would toggle nicely with animation (I'm using AnimatedPositioned for the switch).如果我从CustomSwitch调用_toggle()开关会很好地切换动画(我使用AnimatedPositioned作为开关)。 This works fine if I'm only relying on user input, but I need to also programmatically toggle the switch and I feel like I'm missing something basic yet I'm stumped.如果我只依赖用户输入,这很好用,但我还需要以编程方式切换开关,我觉得我错过了一些基本的东西,但我很难过。

My current understanding is that the CustomSwitch would be rebuilt every time I change its value from ChildWidget because I'm using the switch value as a Key , but how to do it nicely with animation as if I'm calling _toggle() after it has been built?我目前的理解是每次我从ChildWidget更改它的值时都会重建CustomSwitch ,因为我使用 switch 值作为Key ,但是如何用动画很好地做到这一点,就像我在调用_toggle()之后一样已建成?

Like in Java you'd usually do something like customSwitch.toggle() .就像在 Java 中一样,您通常会执行customSwitch.toggle()

Majority of the flutter widgets use controllers to interact with widgets externally (TextFormField, ListView etc).大多数 flutter 小部件使用控制器与外部小部件进行交互(TextFormField、ListView 等)。

The most simple solution for your problem would also be to create a custom controller.您的问题最简单的解决方案也是创建自定义控制器。

Firstly you'll create a controller class, which will have your custom widget's state as a parameter.首先,您将创建一个控制器类,它将您的自定义小部件的状态作为参数。 This class will also expose your widget's methods.It would Look like this:此类还将公开您的小部件的方法。它看起来像这样:

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();
  }
}

You'll need to accept the Custom controller as a parameter in your CustomWidget, and pass it to its state like this:您需要在 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);
}

In your custom class's state, you'll assign you class's state to the controller using the addState method we created.在自定义类的状态中,您将使用我们创建的 addState 方法将类的状态分配给控制器。 You can do it the constructor like this:您可以像这样在构造函数中执行此操作:

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;
        });
    }
}

Now you can pass the controller and call methods using the controller in your parent widget like this:现在,您可以使用父小部件中的控制器传递控制器和调用方法,如下所示:

  CustomWidgetController customWidgetController = new CustomWidgetController();

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

This is it!就是这个! Now you can call customWidgetController.toggle() anywhere in your code to toggle the value!现在您可以在代码中的任何位置调用customWidgetController.toggle()来切换值! This is how native widgets let you interact with them.这就是原生小部件让您与它们交互的方式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM