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

Posted

技术标签:

【中文标题】Gridview 中的小部件在离屏时丢失状态【英文标题】:Widgets in Gridview lose state when off-screen 【发布时间】:2017-10-25 10:17:45 【问题描述】:

我刚开始玩 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))
        )
    );
  

【问题讨论】:

【参考方案1】:

在 Flutter 旨在清理您的屏幕外State 的意义上,此行为按预期工作。您可以通过在树的更高级别维护 isSelected 布尔值来获得所需的行为,例如在CardHolder 或模型类中。对于简单的情况,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))
      )
    );
  

【讨论】:

我认为这与它有关。快速跟进:为什么将 Card 小部件更改为 AnimatedWidget 而不是 StatefulWidget 因为它需要监听ValueNotifier的变化并重建自己。 AnimatedWidget 会自动为您执行此操作。 好东西。而不是打电话给setState?假设我想将其扩展到上述简单用例之外,该模式是什么样的? 你应该做我建议的第一件事(在树的更高级别保持卡片的选择性,即 CardHolder)。据推测,当用户尝试进入应用程序的下一步时,您无论如何都需要能够找出选择了哪些卡片,因此这不会增加复杂性。

以上是关于Gridview 中的小部件在离屏时丢失状态的主要内容,如果未能解决你的问题,请参考以下文章

如何让内部有状态小部件接收到 Flutter 中的滚动事件?

GridView 中的项目在超出视口时会丢失其状态

Flutter/Dart:我的小部件树中可以有多个带有状态的小部件吗?

如何使“堆栈”中的小部件计数取决于变量?

pyqt - 如何从状态栏中的小部件中删除边框?

Google Play 报亭中使用的小部件?