【问题标题】:Two widgets one re-renders the other does not两个小部件一个重新渲染另一个不
【发布时间】:2018-10-15 20:46:02
【问题描述】:

关于重新渲染,我似乎“失去了泡沫”!我不确定我的应用程序的当前实现有什么问题。它是在一些 SO 成员的帮助下得出的。

它应该做什么:渲染 9 个绿色圆圈,然后依次以递增或递减的顺序将每个圆圈渲染为黄色(圆圈小部件)。显示计数的当前值(计数器小部件)。在 AppBar(Home_Page 小部件)中:识别 + 的点击并增加计数,识别 - 的点击并减少计数。在这两种情况下,都在 setState 方法的主体中执行递增/递减。 Circles 和 Counter 小部件都应该重新渲染。在此实现中,所有圆圈都参与了从绿色到黄色到绿色的颜色变化(实际上忽略了计数)。

它的作用:Circles 和 Counter 小部件的初始渲染按需要显示。但是 AppBar 图标(+ 和 -)虽然可以识别,但不会导致重新渲染 Circles 小部件。 Counter 小部件确实会重新呈现其计数显示。在 Circles 小部件中有一个 RaisedButton,当点击它时会导致重新渲染 Circles 小部件。但该按钮在最终实现中并不需要,仅用于测试。

让我感到困惑的是,用于 Circles 小部件的模板与用于 Counter 小部件的模板相同。然而,它们的执行方式似乎不同。

整个应用程序的源代码如下。它是一个单一的 .dart 文件(抱歉,它太长了,但过去遗漏了一些东西会引起问题)。

想法?

// ignore_for_file: camel_case_types
// ignore_for_file: constant_identifier_names
// ignore_for_file: non_constant_identifier_names

import 'package:flutter/material.dart';

import 'dart:async';
import 'dart:math';

const int     NUMBER_TILES = 9;
final int     CROSS_AXIS_COUNT = (sqrt(NUMBER_TILES)).toInt();
const double  CROSS_AXIS_SPACING = 4.0;
const int     INITIAL_COUNT = 9;        // for testing; should be 1
const double  MAIN_AXIS_SPACING = CROSS_AXIS_SPACING;
const int     MILLISECOND_MULTIPLIER = 500;

// ************************************************************** main

void main() {
  final AppState app_state = new AppState(counter: INITIAL_COUNT);
  runApp(new Home_Page(app_state: app_state));
} // main

// **************************************************** class AppState

class AppState {
  int       counter = 0;
  List<int> flash_indices = [];
  bool      forward = false;

  AppState({this.counter});             // AppState

  String toString() {                   // toString
    return ( 'AppState{' +
             'counter: $counter, ' +
             'flash_indices: $flash_indices}');
  } // toString
                                        // following is a mock
  void randomize_flash_indices ( ) {    // randomize_flash_indices

    forward = !forward;
    if ( forward){
      flash_indices = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, -1];
    }
    else {
      flash_indices = [ 8, 7, 6, 5, 4, 3, 2, 1, 0, -1];
    }
    flash_indices.add (-1);             // restore to normal colors
  } // randomize_flash_indices

} // class AppState

// *************************************************** class Home_Page

class Home_Page
      extends StatefulWidget {
  final AppState app_state;

  Home_Page({
    @required this.app_state,
    Key key,
  }) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return Home_Page_State();
  }

} // class Home_Page

// ********************************************* class Home_Page_State

class Home_Page_State extends State<Home_Page>{

  Home_Page_State();

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Periodic',
      theme: new ThemeData(primarySwatch: Colors.indigo),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Periodic'),
          actions: <Widget>[
            IconButton(
              icon: Icon(Icons.add),    // increment counter
              onPressed: () {
                if (widget.app_state.counter < NUMBER_TILES){
                  setState(() {
                    widget.app_state.counter++;
                  });
                }
              }
            ),
            IconButton(
              icon: Icon(Icons.remove), // decrement counter
              onPressed: () {
                if (widget.app_state.counter > 1){
                  setState(() {
                    widget.app_state.counter--;
                  });
                }
              }
            ),
          ]
        ),
        body: Column(
          children: [
            Circles (
              app_state: widget.app_state,
            ),
            Counter (
              app_state: widget.app_state,
            )
          ],
        ),
      ),

    );
  } // Home_Page_State build

} // class Home_Page_State

// ***************************************************** class Circles

class Circles extends StatefulWidget {
  final AppState app_state;

  Circles({
    @required this.app_state,
    Key key,
  }) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return Circles_State();
  }
} // class Circles

// *********************************************** class Circles_State

class Circles_State extends State<Circles>{

  Circles_State();

  int                     flash_tile = -1;
  List<GridTile>          grid_tiles = <GridTile>[];
  StreamController<int>   tick_controller;
  StreamSubscription<int> tick_listener;

  Stream<int> start_ticking() {         // start_ticking
    tick_controller = new StreamController();

    for ( int tick = 0; (tick < widget.app_state.counter); tick++ ) {
      Future.delayed(Duration(milliseconds:
                     MILLISECOND_MULTIPLIER * tick),() {

print('start_ticking() tick: $tick');

        tick_controller.add(tick);
      });
    }
    return tick_controller.stream;
  } // start_ticking

  @override
  void initState() {                    // initState
    super.initState();
    widget.app_state.randomize_flash_indices();
    tick_listener = start_ticking().listen(on_tick);
  } // initState

  @override
  void dispose() {                      // dispose
    if (tick_listener != null) {
      tick_listener.cancel();
      tick_listener = null;
    }
    super.dispose();
  } // dispose

  on_tick(int tick) async { // on_tick

print('listen_for_tick() tick: $tick');

    this.setState(() => this.flash_tile =
                        widget.app_state.
                               flash_indices[tick]);
  } // on_tick

  GridTile new_circle_tile(             // new_circle_tile
                    Color tile_color,
                    int   index) {
    GridTile tile = GridTile(
        child: GestureDetector(
          child: Container(
            decoration: BoxDecoration(
              color: tile_color,
              shape: BoxShape.circle,
            ),
          ),
        )
      );
    return (tile);
  } // new_circle_tile

  List<GridTile> create_circle_tiles() {// create_circle_tiles
    grid_tiles = new List<GridTile>();

    for (int i = 0; (i < NUMBER_TILES); i++) {
      Color tile_color =
              ( this.flash_tile == i) ?
                        Colors.yellow :
                        Colors.green;

      grid_tiles.add(new_circle_tile(tile_color, i));
    }
    return (grid_tiles);
  } // create_circle_tiles

  @override // Circles_State
  Widget build(BuildContext context) {

print('Circles_State Build ' +
      widget.app_state.toString() +
      ' flash_tile: $flash_tile');

    return Column(
      children: [
        GridView.count(
          shrinkWrap: true,
          crossAxisCount: CROSS_AXIS_COUNT,
          childAspectRatio: 1.0,
          padding: const EdgeInsets.all(4.0),
          mainAxisSpacing: MAIN_AXIS_SPACING,
          crossAxisSpacing: CROSS_AXIS_SPACING,
          children: create_circle_tiles(),
        ),
        RaisedButton(
          child: Text("restart"),
          onPressed: () {
            widget.app_state.randomize_flash_indices();
            tick_listener = start_ticking().listen(on_tick);
          }
        ),
      ] // children
    );
  } // Circles_State build

} // class Circles_State

// ***************************************************** class Counter

class Counter extends StatefulWidget {
  final AppState app_state;

  Counter({
    @required this.app_state,
    Key key,
  }) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return Counter_State();
  }
} // class Counter

// *********************************************** class Counter_State

class Counter_State extends State<Counter> {

  Counter_State();

  @override // Counter_State
  Widget build(BuildContext context) {
    int counter_value = widget.app_state.counter;
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Expanded(
          child: SizedBox(
            width: 24.0,
            child: Center(
              child: Text(
                'Counter $counter_value',
                style: TextStyle(
                  color: Colors.blue,
                  fontWeight: FontWeight.bold,
                  fontSize: 24.0,
                ),
              ),
            ),
          ),
        ),
      ],
    );
  } // Counter_State build

} // class Counter_State

【问题讨论】:

  • 要筛选出很多有用的信息(在我看来)。如果您能清楚地指出问题所在,您就更有可能获得帮助。也就是说,小部件未重新渲染通常是因为在更改 StatefulWidget 的属性时缺少对 setState() 的调用。
  • 在 Home_Page_State 中,两个 Icon on_pressed 事件处理程序都在 setState 主体中声明。这就是我认为我会误入歧途的地方。我希望 Circles 和 Counter 小部件都被重新渲染,但只有 Counter 小部件被重新渲染。
  • this.setState(() =&gt; this.flash_tile = widget.app_state.flash_indices[tick]); 这是你设置圈子状态的地方吗?
  • @JacobPhilips 我认为我正在设置 Circles 状态的三个位置:一个在 Circles_State 类的 on_tick 方法中(您已确定),两个在 Home_Page_State AppBar 操作中,其中我编写了“setState( (){widget.app_state.counter++;});"用于计数器的增量和“setState((){widget.app_state.counter--;});”因为它的减量。请注意,最后两个不在 Circles_State 类中,而是通过 AppState 类中的变量 counter 传达更改。

标签: dart flutter


【解决方案1】:

我对我找到的解决方案感到非常失望 - 将所有小部件组合到一个类中。原始架构的骨架如下所示:

void main(){
  :
} // main()

class AppState {
  :
} // class AppState

class HomePage extends StatefulWidget {
  :
} // class HomePage

class HomePageState extends State<HomePage>{ 
  :
} // class HomePageState

class Circles extends StatefulWidget {
  :
} // class Circles

class CirclesState extends State<Circles>{}
  :
} // class CirclesState

class Counter extends StatefulWidget {
  :
} // class Counter

class CounterState extends State<Counter>{}
  :
} // class CounterState

每个类都可以放在自己的 .dart 文件中。但是,为了达到我想要的结果,我不得不删除除了 HomePageHomePageState 之外的所有类。所有其他类(变量、方法和函数)的内容都需要放入 HomePageState 中,以便在状态更改时,小部件会正确更新。修改后的实现框架如下所示:

void main(){
  :
} // main()

class HomePage extends StatefulWidget {
  :
} // class HomePage

class HomePageState extends State<HomePage>{ 
  :
  void randomize_flash_indices ( ) {...

  Stream<int> start_ticking() { ...

  @override
  void initState() { ...

  @override
  void dispose() { ...

  on_tick(int tick) async { ...

  Circles(){ ...

  Counter(){ ...

  @override
  Widget build(BuildContext context) {...

  :
} // class HomePageState

这个实现是单片的,违背了良好的编程(和设计)(和架构)原则。此类软件无法在生产环境中维护。

Dart 语言的架构师似乎错过了关于 setState() 的重要一点,或者我完全错过了正确的编码实践。由于我是一名经验丰富的程序员(超过 42 年),我倾向于怀疑后者(当然,因为我是一名经验丰富的程序员,我认识到我可能遗漏了关于 setState 的重要一点)。

我对颤振/飞镖感到失望。我曾希望从 Xamarin 中解脱 - 我猜我不会从颤振/飞镖中得到它。

【讨论】:

猜你喜欢
  • 2023-04-06
  • 2019-04-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-16
  • 1970-01-01
  • 2021-08-17
  • 2020-08-09
相关资源
最近更新 更多