0

我正在尝试将数据从包含文本字段的自定义小部件传递到计算器小部件。我面临的问题是我希望利用我的自定义小部件来创建可以进入计算器的多个输入(即身高和体重)。任何人都可以协助使用自定义小部件传递数据吗?

创建了自定义文本字段小部件

import 'package:auto_size_text/auto_size_text.dart';

enum Units { unit1, unit2 }

class InputRow extends StatefulWidget {
  InputRow({this.inputParameter, this.unit1, this.unit2});
  final String inputParameter;
  final String unit1;
  final String unit2;

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

class _InputRowState extends State<InputRow> {
  String newTaskTitle;
  Units selectedUnit;
  String unit;

  @override
  void initState() {
    super.initState();
    setState(() {
      unit = widget.unit1;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      constraints: BoxConstraints(maxWidth: 375, maxHeight: 50),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Container(
            child: AutoSizeText(
              widget.inputParameter,
              textAlign: TextAlign.center,
              style: TextStyle(
                fontSize: 20.0,
              ),
            ),
          ),
          Expanded(
            child: Container(
              decoration: BoxDecoration(
                border: Border.all(
                  color: Colors.red,
                  width: 3,
                ),
                borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(10),
                  bottomLeft: Radius.circular(10),
                ),
              ),
              child: TextField(
                autofocus: true,
                textAlign: TextAlign.center,
                onChanged: (newText) {
                  newTaskTitle = newText;
                },
              ),
            ),
          ),
          Container(
            decoration: BoxDecoration(
              color: Colors.red,
              border: Border.all(
                color: Colors.red,
                width: 3,
              ),
              borderRadius: BorderRadius.only(
                topRight: Radius.circular(10),
                bottomRight: Radius.circular(10),
              ),
            ),
            child: Row(
              children: <Widget>[
                Container(
                  padding: EdgeInsets.all(5),
                  child: Center(
                      child: AutoSizeText(
                    unit,
                    style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
                  )),
                ),
                Container(
                    constraints: BoxConstraints(maxHeight: 50, maxWidth: 60),
                    child: FlatButton(
                      highlightColor: Colors.transparent,
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.center,
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                          Icon(
                            Icons.loop,
                            size: 25,
                          ),
                        ],
                      ),
                      onPressed: () {
                        setState(() {
                          selectedUnit = selectedUnit == Units.unit2
                              ? Units.unit1
                              : Units.unit2;
                          if (selectedUnit == Units.unit1) {
                            unit = widget.unit1;
                          } else {
                            unit = widget.unit2;
                          }
                        });
                      },
                    )),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

屏幕调用小部件,并希望将在文本字段中输入的身高和体重传递给计算器




class InputScreen extends StatefulWidget {
  static const String id = 'adjustments';
  @override
  _InputScreenState createState() =>
      _AdjustmentInputScreenState();
}

class AdjustmentInputScreenState
    extends State<AdjustmentInputScreen> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: kActiveButtonColor,
      body: Column(
        children: <Widget>[
          AppBar(
            leading: null,
            actions: <Widget>[
              IconButton(
                  icon: Icon(Icons.close),
                  onPressed: () {
                    Navigator.pop(context);
                  }),
            ],
            title: Text('Dose Adjustment'),
            backgroundColor: Colors.transparent,
            elevation: 0.0,
          ),
          InputRow(
            unit1: 'cm',
            unit2: 'inches',
            inputParameter: 'height',
          ),
          InputRow(unit1: 'lbs', unit2: 'kg', inputParameter: 'weight',),
          RoundedButton(
            title: 'Calculate',
            onPressed: () {
//- code needed to pass the custom textfield widget data
            },
          ),
        ],
      ),
    );
  }
}

计算器大脑

import 'dart:math';

class CalculatorTest {
  CalculatorTest({this.height, this.weight, this.heightUnit, this.weightUnit});

  double height;
  double weight;
  final String heightUnit;
  final String weightUnit;

  double _bmi;

  String calculateBMI() {
    if (weightUnit == 'lbs') {
      weight = weight / 2.2;
    } else {
      weight = weight;
    }

    if (heightUnit == 'inches') {
      height = height / 2.53;
    } else {
      height = height;
    }

    _bmi = weight / pow(height / 100, 2);
    return _bmi.toStringAsFixed(1);
  }
}

第三轮

目标:要能够选择三个按钮之一,选择的按钮将是不同的颜色(如下Button2),然后我可以在单击计算按钮时打印按钮的标题(即Button2)。

选择 Button2 的示例

目前,除了打​​印的内容外,一切正常。我只能获取有关 Button1 的信息(如果使用 selected.option,我会得到“Option.one”,如果使用 selected.title,我会得到“Button1”),尽管实际选择了哪个按钮

我的按钮代码

class MyButton extends ValueNotifier<Option> {
  final String _title1;
  final String _title2;
  final String _title3;

  MyButton(
      {Option option = Option.one,
      String title1 = 'A',
      String title2 = 'B',
      String title3 = 'C'})
      : _title1 = title1,
        _title2 = title2,
        _title3 = title3,
        super(option);

  //You can add a get method to retrieve the title based on the option selected with a switch
  String get title {
    switch (value) {
      case Option.one:
        return _title1;
      case Option.two:
        return _title2;
      case Option.three:
        return _title3;
      default:
        return _title1; //or a default String, but to be honest this will never be used
    }
  }

  Option get option => value;
  set option(Option newOption) => value = newOption;
}

三键码

enum Option {
  one,
  two,
  three,
}

class TriButton extends StatefulWidget {
  TriButton(
      {this.title1, this.title2, this.title3, this.triWidth, this.myButton});

  final String title1;
  final String title2;
  final String title3;
  final Constraints triWidth;
  final MyButton myButton;

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

class _TriButtonState extends State<TriButton> {
  Option selectedOption;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        constraints: widget.triWidth,
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Expanded(
              child: RectButton(
                buttonChild: Text(
                  widget.title1,
                  style: TextStyle(color: Colors.white),
                ),
                onPress: () {
                  setState(() {
                    selectedOption = Option.one;
                  });
                },
                bgColor: selectedOption == Option.one
                    ? kActiveButtonColor
                    : kInactiveButtonColor,
              ),
            ),
            Expanded(
              child: RectButton(
                buttonChild: Text(
                  widget.title2,
                  style: TextStyle(color: Colors.white),
                ),
                onPress: () {
                  setState(() {
                    selectedOption = Option.two;
                  });
                },
                bgColor: selectedOption == Option.two
                    ? kActiveButtonColor
                    : kInactiveButtonColor,
              ),
            ),
            Expanded(
              child: RectButton(
                buttonChild: Text(
                  widget.title3,
                  style: TextStyle(color: Colors.white),
                ),
                onPress: () {
                  setState(() {
                    selectedOption = Option.three;
                  });
                },
                bgColor: selectedOption == Option.three
                    ? kActiveButtonColor
                    : kInactiveButtonColor,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

输入画面

class InputScreen extends StatefulWidget {
  static const String id = 'adjustments';

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

class _InputScreenState
    extends State<InputScreen> {
  final TextEditingController weightController = TextEditingController();
  final TextEditingController heightController = TextEditingController();
  final TextEditingController creatController = TextEditingController();
  final MyUnit heightUnit = MyUnit();
  final MyUnit weightUnit = MyUnit(imperial: 'lbs', metric: 'kg');
  final MyUnit creatUnit = MyUnit(imperial: 'mg/dL', metric: 'mg/dL');
  final MyButton selected = MyButton();

  @override
  void dispose() {
    super.dispose();
    weightController.dispose();
    heightController.dispose();
    creatController.dispose();
    heightUnit.dispose();
    weightUnit.dispose();
    selected.dispose();
  }

  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Color(0xff142651),
      body: Column(
        children: <Widget>[
          AppBar(
            leading: null,
            actions: <Widget>[
              IconButton(
                  icon: Icon(Icons.close),
                  onPressed: () {
                    Navigator.pop(context);
                  }),
            ],
            title: Text('Dose Adjustment'),
            backgroundColor: Colors.transparent,
            elevation: 0.0,
          ),
          ValueListenableBuilder<Option>(
            valueListenable: selectedAbx,
            builder: (context, option, _) => TriButton(
              title1: 'Button 1',
              title2: 'Button 2',
              title3: 'Button 3',
            ),
          ),
          InputRow(
            myUnit: heightUnit,
            inputParameter: 'height',
            textField: heightController,
            colour: kOrangePantone,
          ),
          InputRow(
            myUnit: weightUnit,
            inputParameter: 'weight',
            textField: weightController,
            colour: kRoyalPurple,
          ),
          InputRow(
            myUnit: creatUnit,
            inputParameter: 'SCr',
            textField: creatController,
            colour: kDogwoodRose,
          ),
          RoundedButton(
            title: 'Calculate',
            onPressed: () {
              print(selected.option);
              String inputHeight = heightController.text;
              String inputWeight = weightController.text;
              String inputCreat = creatController.text;

              double imperialHeight = double.parse(inputHeight) * 2.54;
              double metricHeight = double.parse(inputHeight);
              double imperialWeight = double.parse(inputWeight) / 2.2;
              double metricWeight = double.parse(inputWeight);

              double creat = double.parse(inputCreat);

              CalculatorTest calc;
              if (heightUnit.unitType == 'cm' && weightUnit.unitType == 'kg') {
                calc = CalculatorTest(
                    height: metricHeight,
                    weight: metricWeight,
                    creatinine: creat);
              } else if (heightUnit.unitType == 'inches' &&
                  weightUnit.unitType == 'lbs') {
                calc = CalculatorTest(
                    height: imperialHeight,
                    weight: imperialWeight,
                    creatinine: creat);
              } else if (heightUnit.unitType == 'cm' &&
                  weightUnit.unitType == 'lbs') {
                calc = CalculatorTest(
                    height: metricHeight,
                    weight: imperialWeight,
                    creatinine: creat);
              } else {
                heightUnit.unitType == 'inches' && weightUnit.unitType == 'kg';
                calc = CalculatorTest(
                    height: imperialHeight,
                    weight: metricWeight,
                    creatinine: creat);
              }
              ;

              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => ResultsScreen(
                    bmiResult: calc.calculate(),
                  ),
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}
4

5 回答 5

1

在您的自定义小部件上为您的 TextField 添加参数 TextEditingController

class InputRow extends StatefulWidget {
  InputRow({this.inputParameter, this.unit1, this.unit2, this.textField});
  final String inputParameter;
  final String unit1;
  final String unit2;
  final TextEditingController textField; //Add this controller and also to the parameters of the constructor

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



class _InputRowState extends State<InputRow> {
  String newTaskTitle;
  Units selectedUnit;
  String unit;

  @override
  void initState() {
    super.initState();
    setState(() {
      unit = widget.unit1;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      constraints: BoxConstraints(maxWidth: 375, maxHeight: 50),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Container(
            child: AutoSizeText(
              widget.inputParameter,
              textAlign: TextAlign.center,
              style: TextStyle(
                fontSize: 20.0,
              ),
            ),
          ),
          Expanded(
            child: Container(
              decoration: BoxDecoration(
                border: Border.all(
                  color: Colors.red,
                  width: 3,
                ),
                borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(10),
                  bottomLeft: Radius.circular(10),
                ),
              ),
              child: TextField(
                controller: widget.textField, //  <-- The Controller
                autofocus: true,
                textAlign: TextAlign.center,
                onChanged: (newText) {
                  newTaskTitle = newText;
                },
              ),
            ),
          ),
          Container(
            decoration: BoxDecoration(
              color: Colors.red,
              border: Border.all(
                color: Colors.red,
                width: 3,
              ),
              borderRadius: BorderRadius.only(
                topRight: Radius.circular(10),
                bottomRight: Radius.circular(10),
              ),
            ),
            child: Row(
              children: <Widget>[
                Container(
                  padding: EdgeInsets.all(5),
                  child: Center(
                      child: AutoSizeText(
                    unit,
                    style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
                  )),
                ),
                Container(
                    constraints: BoxConstraints(maxHeight: 50, maxWidth: 60),
                    child: FlatButton(
                      highlightColor: Colors.transparent,
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.center,
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                          Icon(
                            Icons.loop,
                            size: 25,
                          ),
                        ],
                      ),
                      onPressed: () {
                        setState(() {
                          selectedUnit = selectedUnit == Units.unit2
                              ? Units.unit1
                              : Units.unit2;
                          if (selectedUnit == Units.unit1) {
                            unit = widget.unit1;
                          } else {
                            unit = widget.unit2;
                          }
                        });
                      },
                    )),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

在父小部件(调用自定义小部件的屏幕)上,为您想知道的每个 TextField 创建 TextEditingController,它们有一个参数 TextEditingController.text ,它为您提供写在正在控制的 Textfield 上的值

class InputScreen extends StatefulWidget {
  static const String id = 'adjustments';
  
  @override
  AdjustmentInputScreenState createState() => AdjustmentInputScreenState();
}

class AdjustmentInputScreenState extends State<InputScreen> {
  final TextEditingController weightController = TextEditingController(); //create one for the height
  final TextEditingController heightController = TextEditingController(); //create one for the width
  

  //don't forget to dispose them
  @override
  void dispose(){
    super.dispose();
    weightController.dispose();
    heightController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: <Widget>[
          AppBar(
            leading: null,
            actions: <Widget>[
              IconButton(
                  icon: Icon(Icons.close),
                  onPressed: () {
                    Navigator.pop(context);
                  }),
            ],
            title: Text('Dose Adjustment'),
            backgroundColor: Colors.transparent,
            elevation: 0.0,
          ),
          InputRow(
            unit1: 'cm',
            unit2: 'inches',
            inputParameter: 'height',
            textField: heightController, // The textcontroller to check the height
          ),
          InputRow(unit1: 'lbs', unit2: 'kg', inputParameter: 'weight',
                   textField: weightController, // The textcontroller to check the weight
                  ),
          FlatButton(
            child: Text('Calculate'),
            onPressed: () {
              //int.tryparse if you want a number, check for null, empty strings or strings that aren't number
              String height = heightController.text;
              String weight = weightController.text;
              print('Height: $height');
              print('Weight: $weight');
              //Do your math here
            },
          ),
        ],
      ),
    );
  }
}

使用 heightController.text 或 weightController.text 您可以在父级中的任何位置看到该值,只要您有 TextEditingController 将其附加到您想要查看的 TextField 小部件

更新

尝试看看 TextEditingController 是如何工作的,你会看到它扩展了一个 ValueNotifier 类,当值改变时重建它的监听器,你可以像这样创建自己的

class MyUnit extends ValueNotifier<Units>{ //You want to check when the enum Units change, so that will be your ValueNotifier
  final String _label1;
  final String _label2;
  
  MyUnit({Units unit = Units.unit1, String label1 = 'cm', String label2 = 'inches'}) : _label1 = label1, _label2 = label2, super(unit);
  
  String get label => value == Units.unit1 ? _label1 : _label2; //The labels you define, just like unit1 and unit2 in InputRow 
  Units get unit => value; //the enum value 
  set unit(Units newUnit) => value = newUnit; //when this change, it will rebuild the listeners
}

现在就像 TextEditingController 你只需要创建它们并处理它们

final MyUnit heightUnit = MyUnit();
final MyUnit weightUnit = MyUnit(label1: 'lbs', label2: 'kg');


//don't forget to dispose them
@override
void dispose(){
  super.dispose();
  weightController.dispose();
  heightController.dispose();
  heightUnit.dispose();
  weightUnit.dispose();
}

...

InputRow(
   myUnit: heightUnit,
   inputParameter: 'height',
   textField: heightController,
),
InputRow(myUnit: weightUnit, inputParameter: 'weight',
   textField: weightController,
),
FlatButton(
   child: Text('Calculate'),
   onPressed: () {        
     //I change the names of the variables to avoid confusion
     String myHeight = heightController.text;
     String myWeight = weightController.text;
     String labelHeight = heightUnit.label;
     String labelWeight = weightUnit.label;
     print('Height: $myHeight $labelHeight');
     print('Weight: $myWeight $labelWeight');
          
     double weight = double.parse(myWeight); //this could throw an error if myWeight cannot be parsed
     if(weightUnit.unit == Units.unit1) weight = weight / 2.2;
     print(weight.toStringAsFixed(1));
     //Do your math here
   },
),

在 InputRow 中,您可以像 TextEditingController 一样传递此类,现在您不需要提供其他值 unit1、unit2、selectedUnit,因为该逻辑现在位于 MyUnit 类中

class InputRow extends StatefulWidget {
  InputRow({this.inputParameter, this.textField, this.myUnit});
  final String inputParameter;
  final MyUnit myUnit;
  final TextEditingController textField; //Add this controller and also to the parameters of the constructor

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



class _InputRowState extends State<InputRow> {

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      constraints: BoxConstraints(maxWidth: 375, maxHeight: 50),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Container(
            child: Text(
              widget.inputParameter,
              textAlign: TextAlign.center,
              style: TextStyle(
                fontSize: 20.0,
              ),
            ),
          ),
          Expanded(
            child: Container(
              decoration: BoxDecoration(
                border: Border.all(
                  color: Colors.red,
                  width: 3,
                ),
                borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(10),
                  bottomLeft: Radius.circular(10),
                ),
              ),
              child: TextField(
                controller: widget.textField, //  <-- The Controller
                autofocus: true,
                textAlign: TextAlign.center,
              ),
            ),
          ),
          Container(
            decoration: BoxDecoration(
              color: Colors.red,
              border: Border.all(
                color: Colors.red,
                width: 3,
              ),
              borderRadius: BorderRadius.only(
                topRight: Radius.circular(10),
                bottomRight: Radius.circular(10),
              ),
            ),
            child: Row(
              children: <Widget>[
                Container(
                  padding: EdgeInsets.all(5),
                  child: Center(
                   child: ValueListenableBuilder<Units>( //This work as a listener
                      valueListenable: widget.myUnit, //the object to listen, it needs to extend a ValueNotifier
                      builder: (context, unit, _) =>
                        Text(widget.myUnit.label,style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500))
                /*
                 The builder gives me a value unit, that I can use when the ValueListenableBuilder rebuilds,
                 but that is the Units enum, which you don't want to display, so you ignore it and give widget.myUnit.label to the Text widget, it will rebuild only when Units change, but the label also getter also change with that value, so it's ok
                */
                  )
                  ),
                ),
                Container(
                    constraints: BoxConstraints(maxHeight: 50, maxWidth: 60),
                    child: FlatButton(
                      highlightColor: Colors.transparent,
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.center,
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                          Icon(
                            Icons.loop,
                            size: 25,
                          ),
                        ],
                      ),
                      onPressed: () {
                        Units unit = widget.myUnit.unit;
                        widget.myUnit.unit = unit == Units.unit1 ? Units.unit2 : Units.unit1; //this will call the setter in MyUnit and rebuild the listeners
                      },
                    )),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

TriButton 代码 如您所见,我尝试使用值通知程序,但无法弄清楚如何获取所选按钮的标题。我不知道如何将该信息拉到下一个屏幕。

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'rect_button.dart';
import 'package:pocketpk/constants.dart';

enum Option {
  one,
  two,
  three,
}

class TriButton extends StatefulWidget {
  TriButton(
      {this.title1, this.title2, this.title3, this.triWidth, this.onChanged});

  final String title1;
  final String title2;
  final String title3;
  final Constraints triWidth;
  ValueChanged<Option> onChanged;

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

class _TriButtonState extends State<TriButton> {
  Option selectedOption;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        constraints: widget.triWidth,
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Expanded(
              child: ValueListenableBuilder<Option>(
                valueListenable: widget.onChanged,
                builder: (context, option, _) => RectButton(
                  buttonChild: Text(
                    widget.title1,
                    style: TextStyle(color: Colors.white),
                  ),
                  onPress: () {
                    setState(() {
                      selectedOption = Option.one;
                    });
                  },
                  bgColor: selectedOption == Option.one
                      ? kActiveButtonColor
                      : kInactiveButtonColor,
                ),
              ),
            ),
            Expanded(
              child: ValueListenableBuilder<Option>(
                valueListenable: widget.onChanged,
                builder: (context, option, _) => RectButton(
                  buttonChild: Text(
                    widget.title2,
                    style: TextStyle(color: Colors.white),
                  ),
                  onPress: () {
                    setState(() {
                      selectedOption = Option.two;
                    });
                  },
                  bgColor: selectedOption == Option.two
                      ? kActiveButtonColor
                      : kInactiveButtonColor,
                ),
              ),
            ),
            Expanded(
              child: ValueListenableBuilder<Option>(
                valueListenable: widget.onChanged,
                builder: (context, option, _) => RectButton(
                  buttonChild: Text(
                    widget.title3,
                    style: TextStyle(color: Colors.white),
                  ),
                  onPress: () {
                    setState(() {
                      selectedOption = Option.three;
                    });
                  },
                  bgColor: selectedOption == Option.three
                      ? kActiveButtonColor
                      : kInactiveButtonColor,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

通知者

import 'package:flutter/material.dart';
import 'package:pocketpk/widgets/tri_button.dart';

class MyButton extends ValueNotifier<Option> {
  final String _title1;
  final String _title2;
  final String _title3;

  MyButton(
      {Option option = Option.one,
      String title1 = 'A',
      String title2 = 'B',
      String title3 = 'C'})
      : _title1 = title1,
        _title2 = title2,
        _title3 = title3,
        super(option);

  //You can add a get method to retrieve the title based on the option selected with a switch
  String get title {
    switch (value) {
      case Option.one:
        return _title1;
      case Option.two:
        return _title2;
      case Option.three:
        return _title3;
      default:
        return _title1; //or a default String, but to be honest this will never be used
    }
  }

  Option get option => value;
  set option(Option newOption) => value = newOption;
}

更新

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'rect_button.dart';
import 'package:pocketpk/constants.dart';

enum Option {
  one,
  two,
  three,
}

class Parent extends StatelessWidget{
  ValueNotifier<Option> myButton = MyButton();

  @override
  Widget build(BuildContext context){
    return ValueListenableBuilder<Option>(
       valueListenable: myButton,
       builder: (context, button, _) => TriButton(
           title1: button.title1, //take the underscores of the names in the MyButton class to make them public
           title2: button.title2,
           title3: button.title3,
           triWidth: BoxConstraints(), //I don't know this value
           onChanged: (newOption) => button.option = newOption,
         )
     );
  }
}

class TriButton extends StatefulWidget {
  TriButton(
      {this.title1, this.title2, this.title3, this.triWidth, this.onChanged});

  final String title1;
  final String title2;
  final String title3;
  final Constraints triWidth;
  ValueChanged<Option> onChanged;

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

class _TriButtonState extends State<TriButton> {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        constraints: widget.triWidth,
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Expanded(
              child: RectButton(
                buttonChild: Text(
                  widget.title1,
                  style: TextStyle(color: Colors.white),
                ),
                onPress: () {
                  widget.onChanged(Option.one);
                },
                bgColor: selectedOption == Option.one
                  ? kActiveButtonColor
                  : kInactiveButtonColor,
              ),
            ),
            Expanded(
              child: RectButton(
                buttonChild: Text(
                  widget.title2,
                  style: TextStyle(color: Colors.white),
                ),
                onPress: () {
                  widget.onChanged(Option.two);
                },
                bgColor: selectedOption == Option.two
                  ? kActiveButtonColor
                  : kInactiveButtonColor,
              ),
            ),
            Expanded(
              child: RectButton(
                buttonChild: Text(
                  widget.title3,
                  style: TextStyle(color: Colors.white),
                ),
                onPress: () {
                  widget.onChanged(Option.three);
                },
                bgColor: selectedOption == Option.three
                  ? kActiveButtonColor
                  : kInactiveButtonColor,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

于 2020-06-11T05:48:54.757 回答
1

第三轮

你几乎明白了,问题就在这里

class _TriButtonState extends State<TriButton> {
  Option selectedOption; 
  // this value is not part of the notifier,
  // it's an independent variable

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        constraints: widget.triWidth,
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Expanded(
              child: RectButton(
                buttonChild: Text(
                  widget.title1,
                  style: TextStyle(color: Colors.white),
                ),
                onPress: () {
                  setState(() {
                    selectedOption = Option.one; 
                    //changing this value doesn't notify/change the ValueNotifier
                  });
                },
                bgColor: selectedOption == Option.one
                    ? kActiveButtonColor
                    : kInactiveButtonColor,
              ),
            ),

Valuechanged最好更改通知程序

class TriButton extends StatefulWidget {
  TriButton(
      {this.title1, this.title2, this.title3, this.triWidth, this.selected, this.onChanged});

  final String title1;
  final String title2;
  final String title3;
  final BoxConstraints triWidth;
  final Option selected; //instead of passing the class, just pass the option from the class
  final ValueChanged<Option> onChanged; //create this to tell the notifier a value changed

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

现在在 TriButton

class _TriButtonState extends State<TriButton> {
  Option selectedOption; 
  // this value is not part of the notifier,
  // it's an independent variable

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        constraints: widget.triWidth,
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Expanded(
              child: RectButton(
                buttonChild: Text(
                  widget.title1,
                  style: TextStyle(color: Colors.white),
                ),
                onPress: () => widget.onChanged(Option.one), //tell to which value you want to change when this is pressed
                bgColor: widget.selected == Option.one
                    ? kActiveButtonColor
                    : kInactiveButtonColor,
              ),
            ),
            .... //Repeat for the others

并在父级 ( InputScreen) 中更改 ValueNotifier 以添加这些参数

ValueListenableBuilder<Option>(
        valueListenable: selected,
        builder: (context, option, _) => TriButton(
          title1: 'Button 1',
          title2: 'Button 2',
          title3: 'Button 3',
          selected: option, //the value selected
          onChanged: (newOption) => selected.option = newOption //the value to change when one of the buttons is pressed
        ),
      ),

现在它会相应地改变,当你点击“计算”时,它会打印正确的值


也只是为了帮助您了解一些逻辑,这是在没有 ValueNotifier 的情况下如何做到的,有一个名为CupertinoSegmentedControl的 Cupertino 小部件(现在不要关注小部件的样式,只关注逻辑)

制作按钮需要 a Map<T, Widget>,其中每个小部件都是Text('Button 1,2...)决定选择哪个 T 的组值,在这种情况下,我只需将 T 设为 int 并根据索引进行选择。在 _InputScreenState 中创建一个带有小部件的 Map 和一个保存所选值的 int;

final Map<int,Widget> buttons = {
    1: Text('Button 1'),
    2: Text('Button 2'),
    3: Text('Button 3')
  };
  
  final Map<int,Widget> genereatedButtons = List<Widget>.generate(
    10, (index) => Text('Button $index')).asMap(); //This is the same as the above, just to generate as much as you want, in this case I just genereated 10
  int keySelected; //it holds the value selected, if null nothing is selected, but you could initilialize it at 0

现在在 InputRow 之前创建小部件

CupertinoSegmentedControl<int>(
    children: genereatedButtons, //or the other map buttons
    groupValue: keySelected,
    onValueChanged: (index) => setState(() => keySelected = index), 
),

并在“计算”按钮print(keySelected)中为您提供选择的索引

在此处输入图像描述

于 2020-07-12T17:32:51.413 回答
0
RoundedButton(
    title: 'Calculate',
    onPressed: () {
      String inputHeight = heightController.text;
      String inputWeight = weightController.text;
      String inputCreat = creatController.text;
      String inputAge = ageController.text;

      double creat = double.parse(inputCreat);
      double age = double.parse(inputAge);

      print(weight);
      print(idealWeight);
      print(adjustWeight);


      /// Create a factory constructor to help you do the math before creating the Calculator
      Calculator calc = Calculator.idealCalculate(
        height: double.parse(inputHeight),
        weight: double.parse(inputWeight),
        creatinine: double.parse(inputCreat),
        age: double.parse(inputAge),
        isFemale: selected.title == 'Female',
        isMetricHeight: heightUnit.unitType == 'cm',
        isMetricWeight: weightUnit.unitType == 'cm'
      );

      Navigator.push(
         context,
         MaterialPageRoute(
           builder: (context) => ResultsScreen(
             Result: calc.calculate(),
             idealResult: calc.calculateIdeal(),
             adjustResult: calc.calculateAdjust(),
           ),
         ),
      );
   },
),

然后在你的Calculate模型中创建一个像这样的工厂构造函数

factory Calculator.idealCalculate({
      double height,
      double weight,
      double creatinine,
      double age,
      bool isFemale = true,
      bool isMetricHeight = true,
      bool isMetricWeight = true,
    }){
    double myHeight = isMetricHeight ? height : height * 2.54;
    double myWeight = isMetricWeight ? weight : weight / 2.2;
    double imperialHeight = isMetricHeight ? myHeight / 2.54 : height;
    double multiplier;
    double idealWeight;
    double adjustWeight;
    if(isFemale){
      multiplier = 0.85;
      idealWeight = 45 + 2.3 * (imperialHeight - 60);
    }
    else{
      multiplier = 1.0;
      idealWeight = 50 + 2.3 * (imperialHeight - 60);
    }
    adjustWeight = (myWeight - idealWeight) * 0.4 + idealWeight;
    return Calculator(
      height: myHeight,
      weight: myWeight,
      creatinine: creatinine,
      age: age,
      genderMultiplier: multiplier,
      ideal: idealWeight,
      adjust: adjustWeight,
    );
  }

此外,如果您想在您的应用程序(Redux、Provider、Bloc 等)中保留一个 Calculator 模型的单个实例并添加设置器或方法来动态更改您想要的值,我建议您检查不同的状态管理

例子

import 'package:flutter/material.dart';

class Calculator {
  Calculator({
    this.height,
    this.weight,
    this.creatinine,
    this.age,
    this.genderMultiplier,
    this.ideal,
    this.adjust,
  });

  double height;
  double weight;
  double creatinine;
  double age;
  double genderMultiplier;
  double ideal;
  double adjust;
  String heightUnit;

  double _crcl;
  double _idealCrCL;
  double _adjustCrCL;
  
  factory Calculator.idealCalculate({
      double height,
      double weight,
      double creatinine,
      double age,
      bool isFemale = true,
      bool isMetricHeight = true,
      bool isMetricWeight = true,
    }){
    double myHeight = isMetricHeight ? height : height * 2.54;
    double myWeight = isMetricWeight ? weight : weight / 2.2;
    double imperialHeight = isMetricHeight ? myHeight / 2.54 : height;
    double multiplier;
    double idealWeight;
    double adjustWeight;
    if(isFemale){
      multiplier = 0.85;
      idealWeight = 45 + 2.3 * (imperialHeight - 60);
    }
    else{
      multiplier = 1.0;
      idealWeight = 50 + 2.3 * (imperialHeight - 60);
    }
    adjustWeight = (myWeight - idealWeight) * 0.4 + idealWeight;
    return Calculator(
      height: myHeight,
      weight: myWeight,
      creatinine: creatinine,
      age: age,
      genderMultiplier: multiplier,
      ideal: idealWeight,
      adjust: adjustWeight,
    );
  }
  
  
  set idealWeight(String title) {
    bool isFemale = title == 'Female';
    double imperialHeight = heightUnit == 'cm' ? height / 2.54 : height;
    if(isFemale){
      genderMultiplier = 0.85;
      ideal = 45 + 2.3 * (imperialHeight - 60);
    }
    else{
      genderMultiplier = 1.0;
      ideal = 50 + 2.3 * (imperialHeight - 60);
    }
    adjust = (weight - ideal) * 0.4 + ideal;
  }


  String calculate() {
    _crcl = (((140 - age) * weight) / (72 * creatinine)) * genderMultiplier;
    return _crcl.toStringAsFixed(1);
  }

  String calculateIdeal() {
    _idealCrCL = (((140 - age) * ideal) / (72 * creatinine)) * genderMultiplier;
    return _idealCrCL.toStringAsFixed(1);
  }

  String calculateAdjust() {
    _adjustCrCL = weight / ideal >= 1.4
        ? (((140 - age) * adjust) / (72 * creatinine)) * genderMultiplier
        : _idealCrCL;
    return _adjustCrCL.toStringAsFixed(1);
  }
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  
  final Calculator calc = Calculator.idealCalculate(
    age: 24,
    creatinine: 152,
    height: 162,
    weight: 64
  );
  
  
  @override
  Widget build(BuildContext context) {
    return Text(calc.calculate(), style: Theme.of(context).textTheme.headline4);
  }
}
于 2020-08-04T19:34:57.283 回答
0

您应该在 InputRow 小部件中创建一个函数,该函数从字段中获取数据并返回它们。然后,当您创建 InputRow 时,给它一个键。最后,当您想从 InputRow 中获取值时,只需调用 key.yourFunction() 并存储结果。

class InputRow extends StatefulWidget {
  InputRow({this.inputParameter, this.unit1, this.unit2, Key key}) : super(key: key););
  final String inputParameter;
  final String unit1;
  final String unit2;

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

创建密钥:GlobalKey<_InputRowState> key = new GlobalKey();

将密钥传递给 InputRow:

InputRow(
  unit1: 'cm',
  unit2: 'inches',
  inputParameter: 'height',
  key: key,
),

从 InputRow 获取参数:var data = key.yourFunction();

于 2020-06-11T04:02:22.397 回答
0

输入画面

class InputScreen extends StatefulWidget {
  static const String id = 'Input';
  @override
  _InputScreenState createState() =>
      _InputScreenState();
}

class _InputScreenState
    extends State<InputScreen> {
  final TextEditingController weightController = TextEditingController();
  final TextEditingController heightController = TextEditingController();
  final TextEditingController creatController = TextEditingController();
  final TextEditingController ageController = TextEditingController();
  final MyUnit heightUnit = MyUnit();
  final MyUnit weightUnit = MyUnit(imperial: 'lbs', metric: 'kg');
  final MyUnit creatUnit = MyUnit(imperial: 'mg/dL', metric: 'mg/dL');
  final MyUnit ageUnit = MyUnit(imperial: 'years', metric: 'years');
  final MyButton selected = MyButton(title3: 'Female', title4: 'Male');

  @override
  void dispose() {
    super.dispose();
    weightController.dispose();
    heightController.dispose();
    creatController.dispose();
    heightUnit.dispose();
    weightUnit.dispose();
    ageUnit.dispose();
    selected.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: Column(
        children: <Widget>[
          ClipPath(
            clipper: MyClipper(),
            child: Container(
              height: 250,
              width: double.infinity,
              decoration: BoxDecoration(
                gradient: kHeaderGradient,
                image: DecorationImage(
                  image: AssetImage('images/virus.png'),
                ),
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  AppBar(
                    leading: null,
                    actions: <Widget>[
                      IconButton(
                          icon: Icon(Icons.close),
                          onPressed: () {
                            Navigator.pop(context);
                          }),
                    ],
                    title: Text(
                      'Creatinine Clearance',
                      style: kHeaderTextStyle,
                    ),
                    backgroundColor: Colors.transparent,
                    elevation: 0.0,
                  ),
                ],
              ),
            ),
          ),
          Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: <Widget>[
              ValueListenableBuilder<Option>(
                valueListenable: selected,
                builder: (context, option, _) => MakeButtons(
                  num0: 3,
                  num1: 5,
                  makeButtonWidth: MediaQuery.of(context).size.width * 0.45,
                  selected: option,
                  onChanged: (newOption) => selected.option = newOption,
                ),
              ),
              InputRow(
                myUnit: heightUnit,
                inputParameter: 'height',
                textField: heightController,
                colour: kOrangePantone,
              ),
              InputRow(
                myUnit: weightUnit,
                inputParameter: 'weight',
                textField: weightController,
                colour: kRoyalPurple,
              ),
              InputRow(
                myUnit: creatUnit,
                inputParameter: 'SCr',
                textField: creatController,
                colour: kDogwoodRose,
              ),
              InputRow(
                myUnit: ageUnit,
                inputParameter: 'Age',
                textField: ageController,
                colour: kDogwoodRose,
              ),
              RoundedButton(
                title: 'Calculate',
                onPressed: () {
                  String inputHeight = heightController.text;
                  String inputWeight = weightController.text;
                  String inputCreat = creatController.text;
                  String inputAge = ageController.text;

                  double imperialHeight = double.parse(inputHeight) * 2.54;
                  double metricHeight = double.parse(inputHeight);
                  double imperialWeight = double.parse(inputWeight) / 2.2;
                  double metricWeight = double.parse(inputWeight);

                  double creat = double.parse(inputCreat);
                  double age = double.parse(inputAge);


                  double multiplier = selected.title == 'Female' ? 0.85 : 1.0; //- code I am trying to have performed on my calculator model //
                  double height = heightUnit.unitType == 'cm'
                      ? metricHeight
                      : imperialHeight;
                  double weight = weightUnit.unitType == 'cm'
                      ? metricWeight
                      : imperialWeight;


                  double idealWeight = selected.title == 'Female'//- Code I am trying to perform on my calculator model
                      ? (45 +
                          2.3 *
                              (heightUnit.unitType == 'cm'
                                  ? ((double.parse(inputHeight) - 152.4) / 2.54)
                                  : (double.parse(inputHeight) - 60)))
                      : (50 +
                          2.3 *
                              (heightUnit.unitType == 'cm'
                                  ? ((double.parse(inputHeight) - 152.4) / 2.54)
                                  : (double.parse(inputHeight) - 60)));

                  double adjustWeight = (weightUnit.unitType == 'kg'
                      ? (double.parse(inputWeight) - idealWeight) * 0.4 +
                          idealWeight
                      : ((double.parse(inputWeight) / 2.2) - idealWeight) *
                              0.4 +
                          idealWeight);

                  print(weight);
                  print(idealWeight);
                  print(adjustWeight);

                  Calculator calc;
                  calc = Calculator(
                    height: height,
                    weight: weight,
                    creatinine: creat,
                    age: age,

//- right now I can only pass the data this way. If I try to do math in my calculator model, I keep getting the else result of my if statements because no value is passed before the code is run
                    genderMultiplier: multiplier,
                    ideal: idealWeight,
                    adjust: adjustWeight,
                  );

                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => ResultsScreen(
                        Result: calc.calculate(),
                        idealResult: calc.calculateIdeal(),
                        adjustResult: calc.calculateAdjust(),
                      ),
                    ),
                  );
                },
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class MyClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    var path = Path();
    path.lineTo(0, size.height - 80);
    path.quadraticBezierTo(
        size.width / 2, size.height, size.width, size.height - 80);
    path.lineTo(size.width, 0);
    path.close();
    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) {
    return false;
  }
}

计算器

class Calculator {
  Calculator({
    height,
    weight,
    creatinine,
    age,
    genderMultiplier,
    ideal,
    adjust,
  });

  double _crcl;
  double _idealCrCL;
  double _adjustCrCL;
  
  
  factory Calculator.idealCalculate({
    double height,
    double weight,
    double creatinine,
    double age,
    bool isFemale = true,
    bool isMetricHeight = true,
    bool isMetricWeight = true,
  }) {
    double myHeight = isMetricHeight ? height : height * 2.54;
    double myWeight = isMetricWeight ? weight : weight / 2.2;
    double imperialHeight = isMetricHeight ? myHeight / 2.54 : height;
    double multiplier;
    double idealWeight;
    double adjustWeight;
    if (isFemale) {
      multiplier = 0.85;
      idealWeight = 45 + 2.3 * (imperialHeight - 60);
    } else {
      multiplier = 1.0;
      idealWeight = 50 + 2.3 * (imperialHeight - 60);
    }
    adjustWeight = (myWeight - idealWeight) * 0.4 + idealWeight;
    return Calculator(
      height: myHeight,
      weight: myWeight,
      creatinine: creatinine,
      age: age,
      genderMultiplier: multiplier,
      ideal: idealWeight,
      adjust: adjustWeight,
      
    );
  }

  String calculate() {
    _crcl = (((140 - age) * weight) / (72 * creatinine)) * genderMultiplier;
    return _crcl.toStringAsFixed(1);
  }

  String calculateIdeal() {
    _idealCrCL = (((140 - age) * ideal) / (72 * creatinine)) * genderMultiplier;
    return _idealCrCL.toStringAsFixed(1);
  }

  String calculateAdjust() {
    _adjustCrCL = weight / ideal >= 1.4
        ? (((140 - age) * adjust) / (72 * creatinine)) * genderMultiplier
        : _idealCrCL;
    return _adjustCrCL.toStringAsFixed(1);
  }
}

结果屏幕

class ResultsScreen extends StatelessWidget {
  static const String id = 'results';
  ResultsScreen({
    @required this.Result,
    this.idealResult,
    this.adjustResult,
  });

  final String Result;
  final String idealResult;
  final String adjustResult;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('BMI CALCULATOR'),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          Container(
            padding: EdgeInsets.all(15),
            alignment: Alignment.bottomLeft,
            child: Text(
              'Your Result',
            ),
          ),
          ReuseableCard(
            bgColor: kGreyBackgroundColor,
            cardChild: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                Text(
                  Result,
                ),
                Text(idealResult),
                Text(adjustResult),
              ],
            ),
          ),
          RoundedButton(
            title: 'Re-Calc',
            onPressed: () {
              Navigator.pop(context);
            },
          )
        ],
      ),
    );
  }
}

于 2020-08-02T02:36:44.007 回答