如何在 Flutter 中实现这个时间选择器滑块小部件?

问题描述 投票:0回答:1

所需的用户界面

我当前的实施

我想实现这个滑块,允许用户选择时间。当代表刻钟、半小时和整小时的条形位于锚点区域时,它们应变为紫色(与图钉/锚点颜色相同)。我将其用作垂直可滚动屏幕中的组件。

我还附上了我当前的实现。但我对如何获取时间值以及如何更改当前条的颜色感到困惑。

class FixedPinTimePicker extends StatefulWidget {
  FixedPinTimePicker({super.key, required this.width});
  double width;

  @override
  State<FixedPinTimePicker> createState() => _FixedPinTimePickerState();
}

Widget hourBar() {
  return Padding(
    padding: const EdgeInsets.symmetric(horizontal: 20),
    child: Container(
      height: 100,
      width: 5,
      color: Colors.grey,
    ),
  );
}

Widget halfHourBar() {
  return Padding(
    padding: const EdgeInsets.symmetric(horizontal: 20),
    child: Container(
      height: 70,
      width: 5,
      color: Colors.grey,
    ),
  );
}

Widget quarterHourBar() {
  return Padding(
    padding: const EdgeInsets.symmetric(horizontal: 20),
    child: Container(
      height: 50,
      width: 5,
      color: Colors.grey,
    ),
  );
}

Widget getBarBasedOnIndex(int index) {
  if (index % 4 == 0) {
    return halfHourBar();
  } else if (index % 2 == 0) {
    return hourBar();
  } else {
    return quarterHourBar();
  }
}

String getTimeBasedOnIndex(int index) {
  final int hour = index ~/ 2;
  final int minute = index % 2 == 0 ? 0 : 30;
  return index % 2 == 0 ? '$hour:$minute' : '';
}

class _FixedPinTimePickerState extends State<FixedPinTimePicker> {
  ScrollController _scrollController = ScrollController();
  @override
  Widget build(BuildContext context) {
    log("Width: ${widget.width}");
    return SizedBox(
      height: 150,
      child: Stack(
        children: [
          ListView.builder(
            controller: _scrollController,
            scrollDirection: Axis.horizontal,
            itemCount: 49,
            itemBuilder: (context, index) {
              log("Controller: ${_scrollController.offset}");
              return Stack(
                children: [
                  getBarBasedOnIndex(index),
                  Positioned(
                    bottom: 0,
                    child: Text(
                      getTimeBasedOnIndex(index),
                      style: TextStyle(
                        color: Colors.black,
                        fontSize: 20,
                      ),
                    ),
                  ),
                ],
              );
            },
          ),
          Positioned(
              top: 0,
              right: widget.width / 2 - 40,
              child: CustomPaint(
                painter: InvertedTrianglePainter(),
                size: Size(30, 60),
              )),
        ],
      ),
    );
  }
}
flutter flutter-animation
1个回答
0
投票
    import 'package:flutter/material.dart';

class FixedPinTimePicker extends StatefulWidget {
  const FixedPinTimePicker({
    super.key,
    required this.width,
    required this.onTimeSelected,
  });

  final double width;
  final ValueChanged<String> onTimeSelected;

  @override
  State<FixedPinTimePicker> createState() => _FixedPinTimePickerState();
}

class _FixedPinTimePickerState extends State<FixedPinTimePicker> {
  final ScrollController _scrollController = ScrollController();
  int _currentHighlightedIndex = 0;

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  Widget _buildBar(int index, bool isHighlighted) {
    Color barColor = isHighlighted ? Colors.purple : Colors.grey;

    if (index % 4 == 0) {
      return Padding(
        padding: const EdgeInsets.symmetric(horizontal: 20),
        child: Container(
          height: 100,
          width: 5,
          color: barColor,
        ),
      );
    } else if (index % 2 == 0) {
      return Padding(
        padding: const EdgeInsets.symmetric(horizontal: 20),
        child: Container(height: 70, width: 5, color: barColor),
      );
    } else {
      return Padding(
        padding: const EdgeInsets.symmetric(horizontal: 20),
        child: Container(height: 50, width: 5, color: barColor),
      );
    }
  }

  String _getTimeBasedOnIndex(int index) {
    final int hour = index ~/ 2;
    final int minute = index % 2 == 0 ? 0 : 30;
    return index % 2 == 0 ? '$hour:$minute' : '';
  }

  void _updateHighlightedIndex() {
    double anchorPosition = widget.width / 2; // Center of the widget
    double closestDistance = double.infinity;
    int closestIndex = 0;

    for (int i = 0; i < 49; i++) {
      double barPosition = (i * 45) +
          5 -
          _scrollController.offset; // Assuming each bar has a width of 50
      double distance = (barPosition - anchorPosition).abs();
      if (distance < closestDistance) {
        closestDistance = distance;
        closestIndex = i;
      }
    }

    if (_currentHighlightedIndex != closestIndex) {
      setState(() {
        _currentHighlightedIndex = closestIndex;
      });
      widget.onTimeSelected(_getTimeBasedOnIndex(closestIndex));
    }
  }

  @override
  Widget build(BuildContext context) {
    return NotificationListener<ScrollNotification>(
      onNotification: (notification) {
        if (notification is ScrollUpdateNotification) {
          _updateHighlightedIndex();
        }
        return true;
      },
      child: SizedBox(
        width: widget.width,
        height: 150,
        child: Stack(
          children: [
            ListView.builder(
              controller: _scrollController,
              scrollDirection: Axis.horizontal,
              itemCount: 49,
              itemBuilder: (context, index) {
                bool isHighlighted = index == _currentHighlightedIndex;
                return Stack(
                  children: [
                    const SizedBox(
                      height: 10,
                    ),
                    _buildBar(index, isHighlighted),
                    const SizedBox(height: 10),
                    Positioned(
                      bottom: 0,
                      child: Text(
                        _getTimeBasedOnIndex(index),
                        style: TextStyle(
                          color: isHighlighted ? Colors.purple : Colors.black,
                          fontSize: 18,
                        ),
                      ),
                    ),
                  ],
                );
              },
            ),
            Positioned(
              top: -20,
              right: widget.width / 2 - 25,
              child: const Icon(
                Icons.arrow_drop_down,
                color: Colors.purple,
                size: 50,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

复制并粘贴,如果它适合您,请接受作为答案。我花了很多时间。

© www.soinside.com 2019 - 2024. All rights reserved.