Как я могу динамически сместить позицию прокрутки или сместить дочерний элемент SingleChildScrollView при изменении содержимого во Flutter? - PullRequest
0 голосов
/ 07 марта 2020

У меня есть медицинское приложение Flutter, которое показывает формы волны. Клиенты хотят бесконечную прокрутку в обоих направлениях по горизонтали. Чтобы сэкономить память, я использую SingleChildScrollView с контейнером фиксированного размера в качестве дочернего элемента (для поддержания соотношения сторон) в сочетании с ScrollController и слушателем, который обеспечивает предварительную выборку данных в обоих направлениях при прокрутке. Все это прекрасно работает; однако проблема заключается в том, что, как только я достигну конца прокрутки на левой стороне и заменим содержимое в контейнере более старыми данными формы волны, положение прокрутки останется равным нулю и больше не будет прокручиваться. Я пытался обойти эту проблему, используя ScrollController.jumpTo(), чтобы сохранить предыдущую точку прокрутки после замены данных, но она дрянная и менее идеальная, и клиенты ее ненавидят.

Любые предложения приветствуются! Код ниже:


class WaveformsLayout extends StatefulWidget {
  final BedsidePatient patient;
  final double width;

  const WaveformsLayout({Key key, this.patient, this.width}) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return WaveformsLayoutState();
  }
}

class WaveformsLayoutState extends State<WaveformsLayout> {
  int orgID = 0;
  WaveformApi waveformApi;
  int maxTimestamp = 0;
  int minTimestamp = 0;
  int currentEnd = 0;
  int currentStart = 0;
  ScrollController _controller;

  List<WaveformFeed> feeds = [];
  List<WaveformFeed> activeFeeds = [];
  List<GlobalKey<WaveformChartState>> chartKeys = [];
  List<Widget> charts = [];
  ScrollDirection _scrollDirection;
  double _currentOffset;
  final int seconds = 11000;
  final DateFormat fmt = DateFormat('HH:mm:ss');

  _initAPI() async {
            // fetch timestamps and feeds from server
                setState(() {
                  this.maxTimestamp = timestamps[timestamps.length - 1];
                  this.minTimestamp = max(widget.patient.admitDate, timestamps[0]);
                  this.currentEnd = this.maxTimestamp;
                  this.currentStart = max(this.minTimestamp, this.maxTimestamp - 11000);
                });
  }

  @override
  void initState() {
    this._controller = ScrollController();
    this._controller.addListener(_scrollListener);
    super.initState();
    _initAPI();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: widget.width,
      height: this.activeFeeds.length * 160.0 + 30,
      child: ListView.builder(
          physics: NeverScrollableScrollPhysics(),
          itemCount: 2,
          itemBuilder: (context, index) {
            if (index == 0) {
              return Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: <Widget>[
                  Text(
                    'Waveform Review',
                    style:
                        TextStyle(fontWeight: FontWeight.bold, fontFamily: "OpenSans", color: BoxViewColors.darkBlue, fontSize: 20),
                  ),
                  Text(
                    '${DateFormat("MM/dd/yy").format(DateTime.fromMillisecondsSinceEpoch(this.currentEnd, isUtc: true).toLocal())}',
                    style: TextStyle(fontFamily: "OpenSans", color: BoxViewColors.details, fontSize: 18),
                  ),
                ],
              );
            }
            return _buildWaveforms(context);
            }
          }),
    );
  }

  Widget _buildWaveforms(BuildContext context) {
    if (this.charts.isEmpty && this.currentEnd > 0) {
      int i = 0;
      this.activeFeeds.forEach((f) {
        final GlobalKey<WaveformChartState> key = GlobalKey();
        this.chartKeys.add(key);
        String feedName = f.feed;
        if (f.topic == 'ECG') {
          feedName = feedName.replaceAll("ECGLead", "").replaceAll("Waveform", "Lead ");
        }
        this.charts.add(Container(
            width: 1375,
            height: 67.5,
            child: WaveformChart(key: key, feed: f, start: this.currentStart, end: this.currentEnd, height: 67.5)));
      });
    }
    List<Widget> labels = [];
    double timeDiff = (this.currentEnd - this.currentStart) / 11.0;
    for (int i = 0; i < 11; i++) {
      int time = _normalize((this.currentStart + (timeDiff * i)).toInt());
      labels.add(Text(
        '${fmt.format(DateTime.fromMillisecondsSinceEpoch(time, isUtc: true).toLocal())}',
        style: TextStyle(color: BoxViewColors.details, fontSize: 13),
      ));
    }

    List<Widget> children = [];
    children.addAll(this.charts);
    children.add(Container(
      width: 1375,
      height: 30,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: labels,
      ),
    ));
    Future.delayed(Duration(milliseconds: 100), () {
      _jumpToEnd(); // HACK !!!
    });
    var scroll = SingleChildScrollView(
      controller: _controller,
      scrollDirection: Axis.horizontal,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: children,
      ),
    );
    List<Widget> widgets = [];
    widgets.add(scroll);
    return Stack(children: widgets);
  }

  _normalize(int value) {
    return value ~/ 1000.0 * 1000;
  }

  _scrollListener() async {
    double off = _controller.offset;
    ScrollPosition position = _controller.position;
    if (this._scrollDirection != position.userScrollDirection && position.userScrollDirection != ScrollDirection.idle) {
      this._scrollDirection = position.userScrollDirection;
    }
    this._currentOffset = off;
    if (off == position.minScrollExtent) {
      this.currentEnd = this.currentStart;
      this.currentStart = max(this.currentStart - seconds, this.minTimestamp);
      this.chartKeys.forEach((k) {
        k.currentState.reload(this.currentStart, this.currentEnd); // reload waveform chart with older data
      });
      setState(() {});
    } else if (off == position.maxScrollExtent) {
      int end = min(_normalize(DateTime.now().toUtc().millisecondsSinceEpoch), this.currentEnd + seconds);
      this.currentStart = max(this.minTimestamp, end - seconds);
      this.currentEnd = end;
      this.chartKeys.forEach((k) {
        k.currentState.reload(this.currentStart, this.currentEnd);  // reload waveform chart with newer data
      });
      setState(() {});
    }
  }

  void _jumpToEnd() {
    this._currentOffset =
        this._controller.position.maxScrollExtent * (this._scrollDirection == ScrollDirection.reverse ? 0.001 : 0.995); //HACKY HACK HACK!!!
    this._controller.jumpTo(this._currentOffset);
  }
}
...