Как я могу убедиться, что заголовок степпера не выходит из поля зрения на вкладке? - PullRequest
0 голосов
/ 31 мая 2019

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

Я пытался изменить физику на ClampingScrollPhysics (), как советовали в комментариях к виджету, но это не решает проблему.Ниже урезанная версия программы, чтобы помочь другим воссоздать - просто вставьте в демонстрационный проект флаттера.

Я также обновил флаттер: -)

class _MyHomePageState extends State<MyHomePage> {
  static YourAge yourAge = YourAge();

  static Widget _ageSelector = CustomSelectorFromList(
      selections: yourAge.valueList,
      initialSelection: yourAge.currentSelectionIndex,
      onSelectionChanged: (int choice) {
        yourAge.value = yourAge.valueList[choice];
      });

  List<Step> _listSteps = [
    Step(
      title: Text("Step 1"),
      content: _ageSelector,
    ),
    Step(
      title: Text("Step 2"),
      content: _ageSelector,
    ),
    Step(
      title: Text("Step 3"),
      content: _ageSelector,
    ),
    Step(
      title: Text("Step 4"),
      content: _ageSelector,
    ),
    Step(
      title: Text("Step 5"),
      content: _ageSelector,
    ),
    Step(
      title: Text("Step 6"),
      content: _ageSelector,
    ),
    Step(
      title: Text("Step 7"),
      content: _ageSelector,
    ),
  ];

  static int _currentStep = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Column(
          children: <Widget>[
            Expanded(
              child: Container(
                child: Stepper(
                  steps: _listSteps,
                  physics: ClampingScrollPhysics(),
                  currentStep: _currentStep,
                  onStepTapped: (step) {
                    setState(() {
                      _currentStep = step;
                    });
                  },
                ),
              ),
            ),
          ],
        ));
  }
}

abstract class DiscreteParameter {
  final String _yourValueKey;
  final List<String> _valueList;
  final String _defaultValue;

  DiscreteParameter(this._yourValueKey, this._valueList, this._defaultValue);

  String _value;

  String get value => _value;
  set value(String value) {
    _value = value;
  }

  int get currentSelectionIndex => _valueList.indexOf(_value);
  set currentSelectionIndex(int index) => () {
        _value = _valueList[index];
      };

  List<String> get valueList => _valueList;
}

class YourAge extends DiscreteParameter {
  static String _yourAgeKey = 'yourAge';
  YourAge() : super(_yourAgeKey, _ageList, aged26to35);

  //String _yourAge = aged26to35;
  static const String agedUpto15 = "<15";
  static const String aged16to25 = "16-25";
  static const String aged26to35 = "26-35";
  static const String aged36to45 = "36-45";
  static const String aged46to55 = "46-55";
  static const String aged56to65 = "56-65";
  static const String aged66plus = "66+";
  static const List<String> _ageList = [
    agedUpto15,
    aged16to25,
    aged26to35,
    aged36to45,
    aged46to55,
    aged56to65,
    aged66plus
  ];
}

class CustomSelectorFromList extends StatefulWidget {
  final Function(int) onSelectionChanged;
  final int initialSelection;
  final List<String> selections;

  @override
  _CustomSelectorFromListState createState() => _CustomSelectorFromListState(
      onSelectionChanged: onSelectionChanged,
      initialSelection: initialSelection,
      selections: selections);

  //include a callback function to parent to react to state change
  CustomSelectorFromList(
      {Key key,
      @required this.selections,
      @required this.onSelectionChanged,
      @required this.initialSelection});
}

class _CustomSelectorFromListState extends State<CustomSelectorFromList> {
  Function(int) onSelectionChanged;
  final int initialSelection;
  final List<String> selections;
  int _listLength;
  int _value = 0;

  _CustomSelectorFromListState(
      {Key key,
      @required this.selections,
      @required this.onSelectionChanged,
      @required this.initialSelection}) {
    //state is preserved so can be set from user "shared preferences"
    _value = initialSelection;
    _listLength = selections.length;
  }

  @override
  Widget build(BuildContext context) {
    //debugPrint("list length: ${selections.length.toString()}");
    return Wrap(
      direction: Axis.vertical,
      children: List<Widget>.generate(
        _listLength,
        (int index) {
          return ChoiceChip(
            label: Text(
              "${selections[index]}",
              style: TextStyle(
                  color: _value == index ? Colors.white70 : Colors.blueGrey),
            ),
            selectedColor: Colors.blueGrey,
            disabledColor: Colors.white70,
            //labelPadding: EdgeInsets.symmetric(),
            padding: const EdgeInsets.all(10),
            selected: _value == index,
            onSelected: (bool selected) {
              setState(() {
                _value = selected ? index : null;
              });
              //callback to parent widget - index of selected item
              onSelectionChanged(index);
            },
          );
        },
      ).toList(),
    );
  }
}

Я ожидаю onTap изшаг, чтобы прокрутить, чтобы показать заголовок шага и содержание ниже.Но если я открою шаг 1, не прокручивайте и не нажимайте шаг 2, тогда заголовок вылетает вверх из поля зрения, и отображаются только несколько нижних фишек выбора, вынуждая пользователя перетащить список вниз, чтобы просмотреть все варианты.

1 Ответ

0 голосов
/ 03 июня 2019

Данное поведение является результатом использования комбинации: Column => Expanded => Stepper. Сам виджет Column не прокручивается, поэтому, если дочерние элементы превышают высоту (например, высоту экрана), это приведет к переполнению пикселей. Таким образом, комбинация Column => Stepper не приведет к желаемому результату. В вашем случае это работает, потому что вы обернули Stepper виджетом Expanded, который можно использовать для виджетов, которые реализуют RenderFlex (что делает Stepper) и будут использовать все доступное пространство для его рендеринга и его прокрутки. если необходимо. Таким образом, вы можете просто использовать Stepper в качестве тела вашего Scaffold, и вы достигнете того же самого - просто хотели указать на это, поскольку это может помочь вам!

Рассматривая реализацию обратного вызова Stepper onStepTapped, он вызовет функцию Scrollable.ensureVisible(...), чтобы убедиться, что содержимое одного Step будет видимым после касания. Так что флаттер пытается избежать вашего поведения на данном этапе. Похоже, что смещение, которое будет установлено в этом случае, неправильно рассчитывается комбинацией Column => Expanded => Stepper.

Чтобы получить лучший результат, я попробовал другой подход: ListView => Stepper и установил scrollPhysics из Stepper в NeverScrollableScrollPhysics, чтобы Stepper не потреблял никакого прокручиваемого ввода и ListView используется в качестве основного обработчика прокрутки:

return Scaffold(
  appBar: AppBar(
    title: Text(widget.title),
  ),
  body: ListView(
    children: <Widget>[
      Stepper(
        steps: _listSteps,
        physics: NeverScrollableScrollPhysics(),
        currentStep: _currentStep,
        onStepTapped: (step) {
          setState(() {
            _currentStep = step;
          });
        },
      ),
    ],
  ),
);
...