Gridview 中的小部件在屏幕外时丢失状态

Widgets in Gridview lose state when off-screen

我刚开始玩 Flutter/Dart,我想知道为什么我的 Card 小部件在滚动到屏幕外时会丢失它们的状态。

_isSelected 由用户点击 Card 小部件之一切换。一切都很好,直到它们从屏幕上消失——此时它们恢复正常。我假设我必须采取额外的步骤来保存状态,但我不太确定如何最好地解决这个问题。

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class CardHolder extends StatefulWidget {
  CardHolder({Key key}) : super(key: key);

  @override
  _CardHolderState createState() => new _CardHolderState();
}

class _CardHolderState extends State<CardHolder> {
  List _cardData;

  _getCards() async {
    String endpoint = 'https://jsonplaceholder.typicode.com/posts';
    var httpClient = createHttpClient();
    var response = await httpClient.read(endpoint);

    List data = JSON.decode(response);

    if (!mounted) return;

    setState(() {
      _cardData = data;
    });
  }

  @override
  void initState() {
    super.initState();

    _getCards();
  }

  @override
  Widget build(BuildContext context) {
    return new GridView.extent(
        maxCrossAxisExtent: 250.0,
        mainAxisSpacing: 4.0,
        crossAxisSpacing: 4.0,
        children: _buildGridList(_cardData)
      );
  }
}

List<Card> _buildGridList(data) {
  if (data == null) return [];
  List<Card> cards = [];
  for (var card in data) {
    cards.add(new Card(title: card['title']));
  }
  return cards;
}

class Card extends StatefulWidget {
  Card({Key key, this.title}) : super(key: key);

  final String title;

  @override
  CardState createState() => new CardState();
}

class CardState extends State<Card> {

  String title;
  bool _isSelected = false;

  _toggleSelected() {
    setState(() {
      _isSelected = !_isSelected;
      print('Toggled to ' + _isSelected.toString());
    });
  }

  CardState({this.title = "No Title!"});

  @override
  Widget build(BuildContext context) {
    print('Rendering card: ' + widget.title);
    return new GestureDetector(
        onTap: _toggleSelected,
        child: new Container(
            constraints: new BoxConstraints(minHeight: 120.0, minWidth: 100.0, maxWidth: 100.0),
            decoration: new BoxDecoration(
                color: _isSelected ? Colors.red : Colors.white,
                borderRadius: new BorderRadius.all(new Radius.circular(2.5)),
                boxShadow: [new BoxShadow(color: Colors.black45, blurRadius: 5.0, spreadRadius: 0.0, offset: new Offset(0.0, 3.0))]
            ),
            margin: new EdgeInsets.all(5.0),
            padding: new EdgeInsets.all(10.0),
            child: new Text(widget.title, style: new TextStyle(color: Colors.black))
        )
    );
  }
}

从 Flutter 旨在清理您的屏幕外 State 的意义上讲,此行为正在按预期工作。您可以通过在树的更高级别维护 isSelected 布尔值来获得所需的行为,例如在 CardHolder 或模型中 class。对于简单的情况,ValueNotifier 可能就足够了。这是一个例子。

import 'dart:collection';
import 'package:flutter/scheduler.dart';
import 'package:flutter/material.dart';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/foundation.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
        primaryColorBrightness: Brightness.light,
      ),
      home: new CardHolder(),
    );
  }
}

class CardHolder extends StatefulWidget {
  CardHolder({Key key}) : super(key: key);

  @override
  _CardHolderState createState() => new _CardHolderState();
}

class _CardHolderState extends State<CardHolder> {
  List _cardData;

  _getCards() async {
    String endpoint = 'https://jsonplaceholder.typicode.com/posts';
    var httpClient = createHttpClient();
    var response = await httpClient.read(endpoint);

    List data = JSON.decode(response);

    if (!mounted) return;

    setState(() {
      _cardData = data;
    });
  }

  @override
  void initState() {
    super.initState();

    _getCards();
  }

  @override
  Widget build(BuildContext context) {
    return new GridView.extent(
      maxCrossAxisExtent: 250.0,
      mainAxisSpacing: 4.0,
      crossAxisSpacing: 4.0,
      children: _buildGridList(_cardData)
    );
  }
}

List<Card> _buildGridList(data) {
  if (data == null) return [];
  List<Card> cards = [];
  for (var card in data) {
    cards.add(new Card(
      title: card['title'],
      isSelected: new ValueNotifier<bool>(false),
    ));
  }
  return cards;
}

class Card extends AnimatedWidget {
  Card({Key key, this.title, this.isSelected }) : super(key: key, listenable: isSelected);

  final String title;
  final ValueNotifier<bool> isSelected;

  @override
  Widget build(BuildContext context) {
    print('Rendering card: ' + title);
    return new GestureDetector(
      onTap: () {
        isSelected.value = !isSelected.value;
      },
      child: new Container(
        constraints: new BoxConstraints(minHeight: 120.0, minWidth: 100.0, maxWidth: 100.0),
        decoration: new BoxDecoration(
          color: isSelected.value ? Colors.red : Colors.white,
          borderRadius: new BorderRadius.all(new Radius.circular(2.5)),
          boxShadow: [new BoxShadow(color: Colors.black45, blurRadius: 5.0, spreadRadius: 0.0, offset: new Offset(0.0, 3.0))]
        ),
        margin: new EdgeInsets.all(5.0),
        padding: new EdgeInsets.all(10.0),
        child: new Text(title, style: new TextStyle(color: Colors.black))
      )
    );
  }
}