Как построить зависимые выпадающие меню в Flutter? - PullRequest
0 голосов
/ 19 февраля 2020

У меня есть три выпадающих меню, и я хочу, чтобы четвертое отображалось ONLY, если выполняются определенные условия (Favourite Animal = Cat). Я также хочу, чтобы четвертое выпадающее меню исчезало всякий раз, когда условия больше не выполняются. Прямо сейчас изменения отображаются, если я вручную сохраняю файл и выполняю горячую перезагрузку, поэтому я предполагаю, что это как-то связано с setState(), который я не включил в filter.dart -файл.

Файл 1 - filter.dart:

import 'package:flutter/material.dart';
import './dropdown.dart';

class Filter extends StatefulWidget {
  @override
  _FilterState createState() => _FilterState();
}

class _FilterState extends State<Filter> {  
  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 10,
      child: Container(
        height: ((MediaQuery.of(context).size.height) / 2),
        padding: EdgeInsets.all(20),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: <Widget>[
            Column(
              children: <Widget>[
                Dropdown('Gender', ['Female', 'Male']),
                Dropdown('Age', ['<15', '15-20', '>20']),
                Dropdown('Favourite Animal', ['Cat', 'Dog', 'Hamster']),
                (cat) ? Dropdown('Favourite cat-toy', ['Toy-mouse', 'Ribbon', 'Ball']) : Text('')
              ],
            ),
            RaisedButton(
              child: Text('Submit'),
              onPressed: () {},
            ),
          ],
        ),
      ),
    );
  }
}

Файл 2 - dropdown.dart:

import 'package:flutter/material.dart';

class Dropdown extends StatefulWidget {
  final String _key;
  final List<String> _values;

  Dropdown(this._key, this._values);

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

class _DropdownState extends State<Dropdown> {
  var _chosenValue;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
      child: DropdownButton<String> (
        hint: Text(widget._key),
        value: _chosenValue,
        icon: Icon(Icons.arrow_drop_down),
        iconSize: 24,
        isExpanded: true,
        items: widget._values.map<DropdownMenuItem<String>>((String value) {
          return DropdownMenuItem<String>(
            value: value,
            child: Text(value),
          );
        }).toList(),
        onChanged: (String value) {
          _chosenValue = value;
          (widget._key == 'Favourite Animal' && _chosenValue == 'Cat') ? cat = true : cat = false;
          setState(() {

          });
        },
      ),
    );
  }
}

bool cat = false; //Feels wrong to have this bool out in the open

Файл filter.dart вызывается из main с Filter().

Дополнительный вопрос: я также хочу извлечь выбранные значения и вернуть их в filter.dart -файл и использовать их в функции onPressed в RaisedButton, не совсем уверенный, как это сделать.

Ответы [ 2 ]

0 голосов
/ 19 февраля 2020

Фикс 6 мест. Как насчет этого?

class _FilterState extends State<Filter> {
  bool cat = false; // Move here.

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 10,
      child: Container(
        height: ((MediaQuery.of(context).size.height) / 2),
        padding: EdgeInsets.all(20),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: <Widget>[
            Column(
              children: <Widget>[
                Dropdown('Gender', ['Female', 'Male']),
                Dropdown('Age', ['<15', '15-20', '>20']),
                Dropdown('Favourite Animal', ['Cat', 'Dog', 'Hamster'], update: _update), // Fix this line.
                (cat)
                    ? Dropdown(
                        'Favourite cat-toy', ['Toy-mouse', 'Ribbon', 'Ball'])
                    : Text('')
              ],
            ),
            RaisedButton(
              child: Text('Submit'),
              onPressed: () {},
            ),
          ],
        ),
      ),
    );
  }

  // Add this function.
  void _update(bool b) {
    setState(() {
      cat = b;
    });
  }

}


class Dropdown extends StatefulWidget {
  final String _key;
  final List<String> _values;
  final Function update; // Add this line.

  Dropdown(this._key, this._values, {this.update = null}); // Fix this line.

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

class _DropdownState extends State<Dropdown> {
  var _chosenValue;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
      child: DropdownButton<String>(
        hint: Text(widget._key),
        value: _chosenValue,
        icon: Icon(Icons.arrow_drop_down),
        iconSize: 24,
        isExpanded: true,
        items: widget._values.map<DropdownMenuItem<String>>((String value) {
          return DropdownMenuItem<String>(
            value: value,
            child: Text(value),
          );
        }).toList(),
        onChanged: (String value) {
          // Fix start.
          setState(() {
            _chosenValue = value;
          });
          if (widget.update != null) {
            (widget._key == 'Favourite Animal' && _chosenValue == 'Cat')
                ? widget.update(true)
                : widget.update(false);
          }
          // Fix end.
        },
      ),
    );
  }
}
0 голосов
/ 19 февраля 2020

На вашем DropdownButton есть обратный вызов onChanged:

onChanged: (String value) {
          _chosenValue = value;
          (widget._key == 'Favourite Animal' && _chosenValue == 'Cat') ? cat = true : cat = false;
          setState(() {

          });
        }

Этот вызов setState() будет сделан для виджета Dropdown, а не для более высокого виджета Filter.

Одним из способов решения этой проблемы является добавление в ваш пользовательский класс Dropdown обратного вызова функции:

class Dropdown extends StatefulWidget {
  final String _key;
  final List<String> _values;
  final Function callback;

  Dropdown(this._key, this._values, this.callback);

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

И в свойстве onChanged:

onChanged: (String value) {
          _chosenValue = value;
          (widget._key == 'Favourite Animal' && _chosenValue == 'Cat') ? cat = true : cat = false;
          callback();
        },

Это вызовет обратный вызов Вы определяете при создании Dropdown:

children: <Widget>[
                Dropdown('Gender', ['Female', 'Male'], callbackFunc),
                Dropdown('Age', ['<15', '15-20', '>20'], callbackFunc),
                Dropdown('Favourite Animal', ['Cat', 'Dog', 'Hamster'], callbackFunc),
                (cat) ? Dropdown('Favourite cat-toy', ['Toy-mouse', 'Ribbon', 'Ball'], callbackFunc) : Text('')
              ],

Где callbackFun c может быть таким простым:

void callbackFunc(){
    setState((){});
}

Конечно, это не самое элегантное решение. Поскольку вы используете обратный вызов, вы можете отменить все глобальные переменные, которые вы использовали, и просто использовать обратный вызов с аргументом:

void callbackFunc(String value){
    if (value=='cat')
        choseCat=true;
    else
        choseCat=false;
    setState((){});
}

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

children: <Widget>[
                Dropdown('Gender', ['Female', 'Male']),
                Dropdown('Age', ['<15', '15-20', '>20']),
                Dropdown('Favourite Animal', ['Cat', 'Dog', 'Hamster'], callbackFunc),
                (cat) ? Dropdown('Favourite cat-toy', ['Toy-mouse', 'Ribbon', 'Ball']) : Text('')
              ],

[...]

onChanged: (String value) {
          _chosenValue = value;
          (widget._key == 'Favourite Animal' && _chosenValue == 'Cat') ? cat = true : cat = false;
          if (callback)
              callback();
        },

Не забудьте сделать этот обратный вызов необязательным в этом случае:

Dropdown({@required this._key, @required this._values, this.callback});

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

...