有状态和无状态混合 UI 未更新
Mix of Stateful and Stateless flutter UI not getting updated
使用有状态的小部件导致显示陈旧的小部件
我有一个 UI 通过网络接收更新文本更新。当更新出现时,我希望首先以红色显示它,然后随着消息年龄的增加慢慢淡化为黑色。如果来自同一来源的新消息,我想用新消息替换旧消息的文本,然后让文本再次从红色淡化为黑色。
当我使用带有 RestartableTimer 的有状态小部件时,我第一次启动我的 UI,一切似乎都运行良好。但是,一旦 second 消息从特定来源传入,事情就会中断。如果我使用纯 Text() 来显示新消息,它就可以正常工作。但是,如果我使用有状态小部件来显示文本并从红色淡化为黑色,UI 仍然陈旧,似乎没有多少更新可以解决问题。
我的层次结构(删除了特定于布局的内容)如下所示:
Stateful Container for different sources
|
List View
|
+--- Stateless Custom Widget
|
+-- Stateless Text (displays the source)
|
+-- Stateful Custom Text class
<repeated for each source>
如果我将我的自定义 class 替换为纯文本 (),那么文本会突然更新得很好(但不会褪色)。
无状态自定义小部件
Widget _CreateDataTable() {
const Color start_color = Colors.red;
const Color end_color = Colors.black;
const Duration fade_len = Duration(seconds: 10);
var read_time = DateTime.fromMillisecondsSinceEpoch(
_data.data.timeSeconds * 1000,
isUtc: true);
String date_text = read_time.toLocal().toString();
List<String> labels = ["Temperature", "Humidity", "RSSI", "Last Reading"];
List<Widget> data = [
Text(_data.data.temperature.toStringAsFixed(2) + " C"),
Text(_data.data.humidity.toStringAsFixed(2) + "%"),
Text(_data.rssi.toString() + " dB"),
FlashText(date_text, start_color, end_color, read_time, fade_len),
// Text(date_text),
//
// IF I COMMENT OUT FlashText AND UNCOMMENT Text, EVERYTHING
// WORKS FINE. IF I KEEP IT AS IS, I ONLY SEE THE FIRST EVER
// MESSAGE AND NEW MESSAGES, WHICH I CAN CONFIRM RECEIVING VIA
// debugPrint, ARE SEEMINGLY IGNORED.
];
var rows = List<Row>();
for (int i = 0; i < labels.length; ++i) {
rows.add(Row(children: [
IntrinsicWidth(child: SizedBox(width: 10.0)),
IntrinsicWidth(child: Text(labels[i] + ":")),
IntrinsicWidth(child: data[i]),
Expanded(child: SizedBox()),
]));
}
return Column(children: rows);
有状态自定义小部件
class FlashText extends StatefulWidget {
final Color _color0;
final Color _color1;
final DateTime _start;
final Duration _duration;
final String _text;
FlashText(
this._text, this._color0, this._color1, this._start, this._duration);
@override
_FlashTextState createState() {
debugPrint("Creating state for $_text - $_start");
return _FlashTextState(_text, _color0, _color1, _start, _duration);
}
}
class _FlashTextState extends State<FlashText> {
Color _color0;
Color _color1;
DateTime _start;
Duration _duration;
String _text;
Animation<double> _animation;
AnimationController _controller;
_FlashTextState(
this._text, this._color0, this._color1, this._start, this._duration);
@override
Widget build(BuildContext ctx) {
return Text(_text,
style: TextStyle(color: Color.lerp(_color0, _color1, _GetT())));
}
@override
void initState() {
super.initState();
RestartableTimer(_duration ~/ 30, _Update);
}
double _GetT() {
DateTime now = DateTime.now().toUtc();
return min(1, max(0, now.difference(_start).inMilliseconds /
_duration.inMilliseconds.toDouble()));
}
void _Update() {
if (_GetT() < 1.0) RestartableTimer(_duration ~/ 30, _Update);
}
}
我认为您必须更改 FlashText
小部件的设计,并在小部件文本更改时使用 didUpdateWidget
方法重建。
我在下面实现了一个完整的应用程序,使用文本字段和按钮来模拟来自服务器的文本。
我认为这或多或少是您所需要的,但可能需要进行一些调整。
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _cont = TextEditingController();
String text = "qwertrrr";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Test"),
),
body: Column(
children: <Widget>[
TextFormField(
controller: _cont,
),
RaisedButton(
child: new Text("Test"),
onPressed: () => setState(() {
text = _cont.text;
}),
),
FlashText(text, Colors.red, Colors.black, 10),
],
),
);
}
}
class FlashText extends StatefulWidget {
final Color color0;
final Color color1;
final int duration;
final String text;
FlashText(this.text, this.color0, this.color1, this.duration);
@override
_FlashTextState createState() {
return _FlashTextState();
}
}
class _FlashTextState extends State<FlashText>
with SingleTickerProviderStateMixin<FlashText> {
Animation<Color> _animation;
AnimationController _controller;
Timer timer;
@override
void didUpdateWidget(FlashText oldWidget) {
if (widget.text != oldWidget.text) {
_controller.forward(from: 0.0);
}
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext ctx) {
return Text(
widget.text,
style: TextStyle(color: _animation.value),
);
}
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this, duration: Duration(seconds: widget.duration));
_animation = ColorTween(begin: widget.color0, end: widget.color1)
.animate(_controller)
..addListener(() => setState(() {}));
_controller.forward();
}
}
使用有状态的小部件导致显示陈旧的小部件
我有一个 UI 通过网络接收更新文本更新。当更新出现时,我希望首先以红色显示它,然后随着消息年龄的增加慢慢淡化为黑色。如果来自同一来源的新消息,我想用新消息替换旧消息的文本,然后让文本再次从红色淡化为黑色。
当我使用带有 RestartableTimer 的有状态小部件时,我第一次启动我的 UI,一切似乎都运行良好。但是,一旦 second 消息从特定来源传入,事情就会中断。如果我使用纯 Text() 来显示新消息,它就可以正常工作。但是,如果我使用有状态小部件来显示文本并从红色淡化为黑色,UI 仍然陈旧,似乎没有多少更新可以解决问题。
我的层次结构(删除了特定于布局的内容)如下所示:
Stateful Container for different sources
|
List View
|
+--- Stateless Custom Widget
|
+-- Stateless Text (displays the source)
|
+-- Stateful Custom Text class
<repeated for each source>
如果我将我的自定义 class 替换为纯文本 (),那么文本会突然更新得很好(但不会褪色)。
无状态自定义小部件
Widget _CreateDataTable() {
const Color start_color = Colors.red;
const Color end_color = Colors.black;
const Duration fade_len = Duration(seconds: 10);
var read_time = DateTime.fromMillisecondsSinceEpoch(
_data.data.timeSeconds * 1000,
isUtc: true);
String date_text = read_time.toLocal().toString();
List<String> labels = ["Temperature", "Humidity", "RSSI", "Last Reading"];
List<Widget> data = [
Text(_data.data.temperature.toStringAsFixed(2) + " C"),
Text(_data.data.humidity.toStringAsFixed(2) + "%"),
Text(_data.rssi.toString() + " dB"),
FlashText(date_text, start_color, end_color, read_time, fade_len),
// Text(date_text),
//
// IF I COMMENT OUT FlashText AND UNCOMMENT Text, EVERYTHING
// WORKS FINE. IF I KEEP IT AS IS, I ONLY SEE THE FIRST EVER
// MESSAGE AND NEW MESSAGES, WHICH I CAN CONFIRM RECEIVING VIA
// debugPrint, ARE SEEMINGLY IGNORED.
];
var rows = List<Row>();
for (int i = 0; i < labels.length; ++i) {
rows.add(Row(children: [
IntrinsicWidth(child: SizedBox(width: 10.0)),
IntrinsicWidth(child: Text(labels[i] + ":")),
IntrinsicWidth(child: data[i]),
Expanded(child: SizedBox()),
]));
}
return Column(children: rows);
有状态自定义小部件
class FlashText extends StatefulWidget {
final Color _color0;
final Color _color1;
final DateTime _start;
final Duration _duration;
final String _text;
FlashText(
this._text, this._color0, this._color1, this._start, this._duration);
@override
_FlashTextState createState() {
debugPrint("Creating state for $_text - $_start");
return _FlashTextState(_text, _color0, _color1, _start, _duration);
}
}
class _FlashTextState extends State<FlashText> {
Color _color0;
Color _color1;
DateTime _start;
Duration _duration;
String _text;
Animation<double> _animation;
AnimationController _controller;
_FlashTextState(
this._text, this._color0, this._color1, this._start, this._duration);
@override
Widget build(BuildContext ctx) {
return Text(_text,
style: TextStyle(color: Color.lerp(_color0, _color1, _GetT())));
}
@override
void initState() {
super.initState();
RestartableTimer(_duration ~/ 30, _Update);
}
double _GetT() {
DateTime now = DateTime.now().toUtc();
return min(1, max(0, now.difference(_start).inMilliseconds /
_duration.inMilliseconds.toDouble()));
}
void _Update() {
if (_GetT() < 1.0) RestartableTimer(_duration ~/ 30, _Update);
}
}
我认为您必须更改 FlashText
小部件的设计,并在小部件文本更改时使用 didUpdateWidget
方法重建。
我在下面实现了一个完整的应用程序,使用文本字段和按钮来模拟来自服务器的文本。
我认为这或多或少是您所需要的,但可能需要进行一些调整。
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _cont = TextEditingController();
String text = "qwertrrr";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Test"),
),
body: Column(
children: <Widget>[
TextFormField(
controller: _cont,
),
RaisedButton(
child: new Text("Test"),
onPressed: () => setState(() {
text = _cont.text;
}),
),
FlashText(text, Colors.red, Colors.black, 10),
],
),
);
}
}
class FlashText extends StatefulWidget {
final Color color0;
final Color color1;
final int duration;
final String text;
FlashText(this.text, this.color0, this.color1, this.duration);
@override
_FlashTextState createState() {
return _FlashTextState();
}
}
class _FlashTextState extends State<FlashText>
with SingleTickerProviderStateMixin<FlashText> {
Animation<Color> _animation;
AnimationController _controller;
Timer timer;
@override
void didUpdateWidget(FlashText oldWidget) {
if (widget.text != oldWidget.text) {
_controller.forward(from: 0.0);
}
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext ctx) {
return Text(
widget.text,
style: TextStyle(color: _animation.value),
);
}
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this, duration: Duration(seconds: widget.duration));
_animation = ColorTween(begin: widget.color0, end: widget.color1)
.animate(_controller)
..addListener(() => setState(() {}));
_controller.forward();
}
}