#flutter #dart
Вопрос:
Я работаю с ListView
ним и сталкиваюсь с проблемой, когда его элементы находятся StatefulWidget
.
Например, у каждого виджета есть свой собственный countdownTime
сохраненный в своем состоянии. Когда я СНАЧАЛА вставляю новый виджет в список, новое состояние принадлежит неправильному виджету (оно должно принадлежать первому, а не последнему элементу). Похоже, что процесс сопоставления состояний деактивации и удаления столкнулся с проблемой. Я попытался добавить key
к ним, но он действовал странно, поскольку удалял и воссоздавал все элементы виджета, в результате чего все отсчеты были сброшены.
Мне потребовалось 2 дня, чтобы узнать больше о StatefulWidget и ListView, но я не мог помочь.
Я создал Дартпад здесь, пожалуйста, посмотрите, если я сделал что-то не так. Дартпад к примеру
Большое вам спасибо!
Комментарии:
1. удалите
color
из_MyStatefulState
и добавьте егоMyStatefulWidget
, что-то вроде:class MyStatefulWidget extends StatefulWidget { final color = generateRandomColor(); ...
2. Спасибо. Я знаю, что состояние очень чувствительно, когда мы меняем местами виджеты, в этом случае оно может не совпадать, но есть ли способ решить эту проблему, не выводя цвет из состояния?
3. Кажется, мой пример был слишком простым, так как я использую цветовую константу в состоянии. Я изменил код в Dartpad, который на самом деле был состоянием, содержащим переменные свойства, такие как время обратного отсчета. Хотя я добавляю новый виджет в первую позицию, но на самом деле он повторно использует неправильное состояние
4. То, что вы говорите, не совсем понятно. Пожалуйста, объясните подробнее.
Ответ №1:
Вы должны использовать AutomaticKeepAliveClientMixin-миксин
Добавьте AutomaticKeepAliveClientMixin
миксин в свой State
StatefulWidget
(виджет элемента), переопределите wantKeepAlive
метод и вернитесь true
.
Что я решил:
- Новый элемент будет добавлен в начало списка.
- Поддерживать состояние после прокрутки
Ваш полный (обновленный) дротик:
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, this.title}) : super(key: key);
final String? title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Widget> _widgets = [MyStatefulWidget()];
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.separated(
itemCount: _widgets.length,
itemBuilder: (_, index) {
return _widgets[index];
},
reverse: true,
shrinkWrap: true,
separatorBuilder: (BuildContext context, int index) {
return SizedBox(
width: 5,
);
},
),
floatingActionButton: TextButton(
onPressed: () {
setState(() {
// Insert first in the list, hope it's just need to rebuild only the first item, the remains are unchanged
_widgets.add(MyStatefulWidget());
});
},
child: Container(
child: Text(
'Press',
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
),
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.red,
),
),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() {
print('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> createState');
return _MyStatefulState();
}
}
class _MyStatefulState extends State<MyStatefulWidget> with AutomaticKeepAliveClientMixin{
StreamSubscription? countdownSubscription;
int countdownTime = 30;
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
initTimeoutUI();
}
void initTimeoutUI() {
countdownSubscription = Future.delayed(Duration(seconds: 1)).asStream().listen((value) {
if (countdownTime > 0) {
setState(() {
countdownTime--;
});
}
initTimeoutUI();
});
}
@override
void dispose() {
super.dispose();
countdownSubscription?.cancel();
}
@override
Widget build(BuildContext context) {
super.build(context);
return Container(
width: 100,
height: 100,
child: Center(child: Text('countdown: $countdownTime')),
);
}
}
Комментарии:
1. Спасибо! Ты спас мои тяжелые дни. Это сработало! Я проверил и увидел, что
reverse: true, shrinkWrap: true,
были для добавления элементов в начало списка, верно? И автоматическое сохранение смешивания компонентов для поддержания состояния после длительной прокрутки? Я не проверял, что произойдет со штатами, если список будет слишком длинным, но, как вы сказали, некоторые штаты будут потеряны, если я прокручу список от начала до конца, верно?2. На самом деле нет, то, что я решил, это то, что я написал. Он будет поддерживаться автоматически, даже если долго прокручивать. Вы должны попробовать это один раз. Пожалуйста, поддержите и примите ответ, если вы нашли его полезным.