Flutter ListView неправильное сопоставление состояний для элементов StatefulWidget

#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. На самом деле нет, то, что я решил, это то, что я написал. Он будет поддерживаться автоматически, даже если долго прокручивать. Вы должны попробовать это один раз. Пожалуйста, поддержите и примите ответ, если вы нашли его полезным.