Как добавить галочку и метку над слайдером?

#flutter #flutter-layout

Вопрос:

я пытаюсь использовать ползунок().. деления выглядят хорошо (значение >= 50 ? 10 : 20)

Но как добавить галочку и метку над слайдером ?

Ожидать :

галочка будет менять цвет в соответствии с положением ползунка

Ползунок

Фактический:

 Slider(
 min: 0,
 max: 100,
 value: value,
 onChanged: (val) {
   setState(() {
     value = val;
   });
  },
 divisions: value >= 50 ? 10 : 20,
 label: value.toString(),
),
 

моя проблема в том, что :

  1. положение метки и галочки (при использовании столбца)
  2. Измените цвет галочки, если положение ползунка одинаковое

мой код с использованием столбца(

 Column(
      children: [
        Container(
          margin: EdgeInsets.symmetric(horizontal: 20),
          child:
          Column(
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: List.generate(6, (index) => Text('$index')),
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: List.generate(
                  16,
                  (index) => SizedBox(
                    height: 8,
                    child: VerticalDivider(
                      width: 8,
                      color: HelperColors.orange,
                    ),
                  ),
                ),
              )
            ],
          ),
        ),
        Slider(
          value: widget.value,
          min: widget.minValue,
          max: widget.maxValue,
          divisions: widget.divisions,
          onChanged: widget.onChanged,
          label: widget.value.toString(),
        ),
      ],
    );
 

не могли бы вы помочь мне решить некоторые проблемы с этим дизайном?

Ответ №1:

Расположение тиков и значений Тиков

Вместо того, чтобы заключать строки тиков и значений тиков в столбец, может быть лучше объединить как тик, так и значение тика в один столбец. Затем оберните этот столбец Expanded виджетом и поместите его в List.generate строку. Это гарантирует, что галочка всегда совпадает со значением галочки, и каждый столбец имеет одинаковый интервал.

Галочки и Выравнивание ползунка

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

Пожалуйста, проверьте эту ссылку, если вам интересно узнать больше о смещении/ марже слайдера.
Проблема смещения ползунка

Вот что вам нужно сделать, чтобы достичь идеального выравнивания

  1. Удалите смещение, создав пользовательский trackShape ползунок.
  2. Измерьте смещение одного интервала между делениями и разделите его на 2.
    Формула такова MediaQuery.of(context).size.width / numOfTick / 2
  3. Оберните ползунок с нулевым смещением Padding с помощью виджета и используйте вычисленное смещение в качестве значения горизонтального заполнения. padding: EdgeInsets.symmetric(horizontal: offset),
     offset
      V
    |---|
    .----------/   /------------.
    |   0   |           |  100  |    
    |       |Ticks area |       |
    |   |   |           |   |   ||
    .----------/   /------------.|
    |                           ||
    |       Slider area         ||Screen edge
    |                           ||
    '----------/  /-------------'|
     
  4. Now ticks and slider will always be perfectly aligned, edge-to-edge, regardless screen size.

Here’s the example. You may still want to improve the UI as per your requirements.

What’s supported

  1. Adjustable major and minor ticks
  2. Выделение галочки, когда значение совпадает
  3. Контроль точности значений
 double value = 50;
double actualValue = 50;
double minValue = 0;
double maxValue = 100;
List<double> steps = [0,5,10,15,20,25,30,35,40,45,50,60,70,80,90,100];

// ...

CustomSlider(
            minValue: minValue,
            maxValue: maxValue,
            value: value,
            majorTick: 6,
            minorTick: 2,
            labelValuePrecision: 0,
            tickValuePrecision: 0,
            onChanged: (val) => setState(() {
              value = val;
              actualValue =
                  steps[(val / maxValue * (steps.length - 1)).ceil().toInt()];
              print('Slider value (linear): $value');
              print('Actual value (non-linear): $actualValue');
            }),
            activeColor: Colors.orange,
            inactiveColor: Colors.orange.shade50,
            linearStep: false,
            steps: steps,
          ),
 

Пользовательский виджет слайдера

 class CustomSlider extends StatelessWidget {
  final double value;
  final double minValue;
  final double maxValue;
  final int majorTick;
  final int minorTick;
  final Function(double)? onChanged;
  final Color? activeColor;
  final Color? inactiveColor;
  final int labelValuePrecision;
  final int tickValuePrecision;
  final bool linearStep;
  final List<double>? steps;

  CustomSlider({
    required this.value,
    required this.minValue,
    required this.maxValue,
    required this.majorTick,
    required this.minorTick,
    required this.onChanged,
    this.activeColor,
    this.inactiveColor,
    this.labelValuePrecision = 2,
    this.tickValuePrecision = 1,
    this.linearStep = true,
    this.steps,
  });

  @override
  Widget build(BuildContext context) {
    final allocatedHeight = MediaQuery.of(context).size.height;
    final allocatedWidth = MediaQuery.of(context).size.width;
    final divisions = (majorTick - 1) * minorTick   majorTick;
    final double valueHeight =
        allocatedHeight * 0.05 < 41 ? 41 : allocatedHeight * 0.05;
    final double tickHeight =
        allocatedHeight * 0.025 < 20 ? 20 : allocatedHeight * 0.025;
    final labelOffset = allocatedWidth / divisions / 2;

    return Column(
      children: [
        Row(
          children: List.generate(
            divisions,
            (index) => Expanded(
              child: Column(
                children: [
                  Container(
                    alignment: Alignment.bottomCenter,
                    height: valueHeight,
                    child: index % (minorTick   1) == 0
                        ? Text(
                            linearStep
                                ? '${(index / (divisions - 1) * maxValue).toStringAsFixed(tickValuePrecision)}'
                                : '${(steps?[index])?.toStringAsFixed(tickValuePrecision)}',
                            style: TextStyle(
                              fontSize: 12,
                            ),
                            textAlign: TextAlign.center,
                          )
                        : null,
                  ),
                  Container(
                    alignment: Alignment.bottomCenter,
                    height: tickHeight,
                    child: VerticalDivider(
                      indent: index % (minorTick   1) == 0 ? 2 : 6,
                      thickness: 1.2,
                      color: (index / (divisions - 1)) * maxValue == value
                          ? activeColor ?? Colors.orange
                          : Colors.grey.shade300,
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
        Padding(
          padding: EdgeInsets.symmetric(horizontal: labelOffset),
          child: SliderTheme(
            data: SliderThemeData(
              trackHeight:
                  allocatedHeight * 0.011 < 9 ? 9 : allocatedHeight * 0.011,
              activeTickMarkColor: activeColor ?? Colors.orange,
              inactiveTickMarkColor: inactiveColor ?? Colors.orange.shade50,
              activeTrackColor: activeColor ?? Colors.orange,
              inactiveTrackColor: inactiveColor ?? Colors.orange.shade50,
              thumbColor: activeColor ?? Colors.orange,
              overlayColor: activeColor == null
                  ? Colors.orange.withOpacity(0.1)
                  : activeColor!.withOpacity(0.1),
              thumbShape: RoundSliderThumbShape(enabledThumbRadius: 12.0),
              trackShape: CustomTrackShape(),
              showValueIndicator: ShowValueIndicator.never,
              valueIndicatorTextStyle: TextStyle(
                fontSize: 12,
              ),
            ),
            child: Slider(
              value: value,
              min: minValue,
              max: maxValue,
              divisions: divisions - 1,
              onChanged: onChanged,
              label: value.toStringAsFixed(labelValuePrecision),
            ),
          ),
        ),
      ],
    );
  }
}

class CustomTrackShape extends RoundedRectSliderTrackShape {
  Rect getPreferredRect({
    required RenderBox parentBox,
    Offset offset = Offset.zero,
    required SliderThemeData sliderTheme,
    bool isEnabled = false,
    bool isDiscrete = false,
  }) {
    final double trackHeight = sliderTheme.trackHeight!;
    final double trackLeft = offset.dx;
    final double trackTop =
        offset.dy   (parentBox.size.height - trackHeight) / 2;
    final double trackWidth = parentBox.size.width;
    return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
  }
}
 

Тест

Портрет
Пейзаж

Редактирование (ползунок с нелинейными шагами)

Поскольку ползунок Flutter имеет линейное сопоставление значений и позиций, возможно, не стоит изменять значение ползунка на основе нелинейных значений. Однако этого все еще можно достичь, создав дополнительный диапазон значений для фактического значения, а затем сопоставив их вместе.

Вот шаги

  1. Создайте список, содержащий нелинейные значения (тот, который вы создали)
  2. Убедитесь, что длина списка совпадает с общим количеством галочек. В противном случае отображение будет неверным.
    listLength = (majorTick - 1) * minorTick majorTick

Приведенный выше код был отредактирован для решения этой проблемы.

Тест

Нелинейный

Сопоставленные значения

                            Start                            End
Slider value (linear)      0.00 6.67 13.33 ... 86.67 93.33 100.0
Actual value (non-linear)  0.00 5.00 10.00 ... 80.00 90.00 100.0
 

Комментарии:

1. боже, большое тебе спасибо

2. но я просто хочу знать … как отправить выбранное значение индекса thick в OnChanged ?.. у меня есть значение values = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100]; …. итак, я установил значение на верхнюю метку.. но я вижу (индекс) внутри List.generate и снаружи слайдера().. поэтому я не могу передать SelectedIndex в OnChanged

3. Область галочек — это просто украшение пользовательского интерфейса. Даже если вам удастся извлечь индекс и передать его родительскому виджету, в конечном итоге вы измените поведение слайдера. Основная причина заключается в том, что значение и положение ползунка отображаются линейно. Между тем, шаги элементов в списке не являются линейными. Итак, один из способов добиться этого-создать 2 диапазона значений, один для слайдера и один для фактического значения. Оба должны иметь одинаковую длину для правильного сопоставления. Но будьте очень осторожны, потому что он подвержен нежелательным ошибкам, если он неправильно отображен. Более подробная информация в разделе ответов.

4. хммм.. мой лидер говорит … я должен настроить слайдер, чтобы исправить это.. я должен создать галочку аналогичного значения внутри списка из BE… потому что значение из списка может измениться в будущем из BE… так что этот слайдер должен быть гибким, используя значение из списка для увеличения или уменьшения галочки