如何在Flutter回调中使用可变参数?

3

我有一个简化的flutter控件,类似于一排“单选”按钮或菜单栏。父组件传递一个包含每个按钮标题和回调函数的列表。该控件会触发回调函数并传递被点击按钮的索引。问题在于,这些“按钮”是动态创建的,数量可能由父组件决定。当我在GestureDetector的onTap函数中设置回调时,它总是使用循环中参数(idx)的最后一个值来触发回调。因此,如果有4个按钮,则doCallback始终使用4调用,无论点击哪个按钮。似乎doCallback被调用时是使用idx的引用,而不是idx的值。有没有办法让每个按钮将自己的索引发送到回调函数中?

class CtrlRadioSelector extends StatelessWidget {
  CtrlRadioSelector({Key? key, required this.captions, required this.onTapItem})
      : super(key: key);
  final List<String> captions;
  final ValueSetter<int> onTapItem;

  @override
  Widget build(BuildContext context) {
    List<Widget> selectorItems = [];
    int idx = 0;
    for (var caption in captions) {
      selectorItems.add(Expanded(
          flex: 10,
          child: GestureDetector(
              onTap: () => doCallback(idx),
              child: Text(caption,
                  textAlign: TextAlign.center,
                  style: TextStyle(fontSize: 18)))));
      idx++;
    }
    return Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.center,
        children: selectorItems);
  }

  void doCallback(int idx) {
    onTapItem(idx);
  }
}
3个回答

1

一个解决方法是使用for循环,它通过索引迭代,而你无论如何都需要这个索引:

    for (var idx = 0; idx < captions.length; i += 1) {
      selectorItems.add(Expanded(
          flex: 10,
          child: GestureDetector(
              onTap: () => doCallback(idx),
              child: Text(captions[idx],
                  textAlign: TextAlign.center,
                  style: TextStyle(fontSize: 18)))));
    }

这是因为Dart特别让闭包捕获for循环的索引(而不是所有在作用域内的变量的值)。根据Dart语言指南

Closures inside of Dart’s for loops capture the value of the index, avoiding a common pitfall found in JavaScript. For example, consider:

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());

The output is 0 and then 1, as expected. In contrast, the example would print 2 and then 2 in JavaScript.

更普遍地说,您也可以确保您的闭包引用一个变量,该变量是循环体内部的本地变量,这将避免在每次迭代中重新分配引用的变量。例如,以下内容也可以工作(尽管在您的特定情况下过于冗长):

    int idx = 0;
    for (var caption in captions) {
      var currentIndex = idx;

      selectorItems.add(Expanded(
          flex: 10,
          child: GestureDetector(
              onTap: () => doCallback(currentIndex),
              child: Text(caption,
                  textAlign: TextAlign.center,
                  style: TextStyle(fontSize: 18)))));
      idx++;
    }

1
这是创建带有按钮的动态行的正确方法,其中子元素的实际索引被保留:
import 'package:flutter/material.dart';

class CtrlRadioSelector extends StatelessWidget {
  const CtrlRadioSelector({Key? key, required this.captions, required this.onTapItem})
      : super(key: key);
  final List<String> captions;
  final ValueSetter<int> onTapItem;

  @override
  Widget build(BuildContext context) {

    return Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.center,

        children: List.generate(  //equivalent to your code using for loop and a list
            captions.length, //if length of captions is 4, it'll iterate 4 times
            (idx) {

          return Expanded(
              flex: 10,
              child: GestureDetector(
                  onTap: () => doCallback(
                      idx), //value of idx is the actual index of the button
                  child: Text(captions[idx],
                      textAlign: TextAlign.center,
                      style: const TextStyle(fontSize: 18))));
        }));
  }

  void doCallback(int idx) {
    onTapItem(idx);
  }
}

1
jamesdlin的回答是正确的。我只想补充一点,可以使用collection for来声明式地定义小部件树,而不是命令式地定义。 (此外,这是一篇相当不错的文章,解释了为什么添加了collection for和类似功能到该语言中)
  @override
  Widget build(BuildContext context) {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.center,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        for (var idx = 0; idx < captions.length; idx += 1)
          Expanded(
            flex: 10,
            child: GestureDetector(
              onTap: () => doCallback(idx),
              child: Text(
                captions[idx],
                textAlign: TextAlign.center,
                style: TextStyle(fontSize: 18),
              ),
            ),
          ),
      ],
    );
  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接