Flutter - notify sibling widgets inside Column

2019-08-31 12:50发布

问题:

I'm trying to create a functionality, where when I click on a FlatButton inside an Expanded widget, its flex changes to 2 and other siblings FlatButtons flex change to 1.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Directionality(
      textDirection: TextDirection.ltr,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          new ButtonWidget(text: "Hello",selectedColor: Colors.yellow),
          new ButtonWidget(text: "This is", selectedColor: Colors.red),
          new ButtonWidget(text: "Button", selectedColor: Colors.blue),
        ],
      ),
    );
  }
}

class ButtonWidget extends StatefulWidget {
  String text;
  MaterialColor selectedColor;

  ButtonWidget({this.text, this.selectedColor});

  createState() =>
      ButtonState(text: this.text, selectedColor: this.selectedColor);
}

class ButtonState extends State<ButtonWidget> {
  String text;
  MaterialColor selectedColor;
  int _flexValue = 1;

  ButtonState({this.text, this.selectedColor});

  @override
  Widget build(BuildContext context) {
    return Expanded(
      flex: _flexValue,
      child: FlatButton(
        color: selectedColor,
        child: Text(
          text,
        ),
        onPressed: () {
          setState(() {
            _flexValue = 2;
          });
        },
      ),
    );
  }
}

I was trying to find a way, maybe keep track of all of them in an array or something. I searched and found that InheritedWidget approach is for inherited widgets, not siblings.

I'm sure there's a clean way to do it. But just can't get my hands on it.

回答1:

Don't try to keep the state of whether it's selected in the button itself. As you've seen it's difficult to keep the state of 3 buttons in sync. Find a place higher up the widget tree where you can maintain that state once. In this case, it's in your App. Make App stateful so that it can remember the state and then your buttons don't need to remember it. They can be told whether they are selected (big) or unselected (small) in their constructor.

So, how does the button tell the parent that it's now become the selected one? There are various strategies, but they all involve:

  • calling a method that's been passed down
  • calling a method on an InheritedWidget
  • sending a message through a stream
  • etc

Try this:

import 'package:flutter/material.dart';

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

class MyApp extends StatefulWidget {
  @override
  State createState() => MyAppState();
}

class MyAppState extends State<MyApp> {
  int selected = 0;

  @override
  Widget build(BuildContext context) {
    return Directionality(
      textDirection: TextDirection.ltr,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          ButtonWidget(0, flexValue(0), 'Hello', Colors.yellow, onClick),
          ButtonWidget(1, flexValue(1), 'This is', Colors.red, onClick),
          ButtonWidget(2, flexValue(2), 'Button', Colors.blue, onClick),
        ],
      ),
    );
  }

  void onClick(int i) {
    setState(() {
      selected = i;
    });
  }

  int flexValue(int index) => (index == selected) ? 2 : 1;
}

class ButtonWidget extends StatelessWidget {
  ButtonWidget(this.index, this._flexValue, this.text, this.selectedColor,
      this.notifyClick);

  final Function notifyClick;
  final int index;
  final int _flexValue;
  final String text;
  final MaterialColor selectedColor;

  @override
  Widget build(BuildContext context) {
    return Expanded(
      flex: _flexValue,
      child: FlatButton(
        color: selectedColor,
        child: Text(
          text,
        ),
        onPressed: () {
          notifyClick(index);
        },
      ),
    );
  }
}


回答2:

Here you have another way to do that , change your main widget to Stateful and your Button to Stateless.

 class MyApp2 extends StatefulWidget {
      @override
      _MyApp2State createState() => _MyApp2State();
    }

    class _MyApp2State extends State<MyApp2> {
      int selectedIndex = -1;

      onIndexChanged(int index){
              setState(() {
                selectedIndex = index;
              });
      }

      @override
      Widget build(BuildContext context) {
        return Directionality(
          textDirection: TextDirection.ltr,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: <Widget>[
              new ButtonWidget(
                index: 1,
                text: "Hello",
                selectedColor: Colors.yellow,
                selectedIndex: selectedIndex,
                onChanged: onIndexChanged
              ),
              new ButtonWidget(
                  index: 2,
                  text: "This is",
                  selectedColor: Colors.red,
                  selectedIndex: selectedIndex,
                  onChanged: onIndexChanged),
              new ButtonWidget(
                  index: 3,
                  text: "Button",
                  selectedColor: Colors.blue,
                  selectedIndex: selectedIndex,
                  onChanged: onIndexChanged),
            ],
          ),
        );
      }
    }

    class ButtonWidget extends StatelessWidget {
      final String text;
      final MaterialColor selectedColor;
      final int index;
      final ValueChanged<int> onChanged;
      final int selectedIndex;
      ButtonWidget(
          {this.text,
          this.selectedColor,
          this.index,
          this.onChanged,
          this.selectedIndex});

      @override
      Widget build(BuildContext context) {
        return Expanded(
          flex: selectedIndex != null && selectedIndex == index ? 2 : 1,
          child: FlatButton(
            color: selectedColor,
            child: Text(
              text,
            ),
            onPressed: () => onChanged(index),
          ),
        );
      }
    }