У меня есть медицинское приложение 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);
}
}