Phill Alexakis
Phill Alexakis

Reputation: 1499

How to return a Widget to a previous state

I'm changing the color of a custom menu icon, by using the setState in my Custom Icon Widget Although I'd like to change the previous pressed Icon to the previous state (before the color was Indigo)


Edit 2:

Live demo: https://alexakis97.github.io/webappjs/


By changing the _color variable in onTap method I manage to change the color.

To get a better understanding this an Custom Icon Widget:


class _CustomBottomIconState extends State<CustomBottomIcon> {
  final Function changeMenu;
  final int index;

  _CustomBottomIconState({this.changeMenu, this.index});

  Color _color = Colors.indigo;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        //
        changeMenu(index);
        setState(() {
          _color = Colors.green;
        });
      },
      child: Container(
        decoration: BoxDecoration(
          color: _color,
          borderRadius: BorderRadius.circular(100),
        ),
        height: MediaQuery.of(context).size.height / 20,
        width: MediaQuery.of(context).size.height / 20,
        child: Icon(
          widget.icon,
          color: Colors.white,
        ),
      ),
    );
  }
}

(Middle Icon pressed)

enter image description here

How can I change the first one back to Indigo ?

Edit 1:

Sharing bottom menu widget

class BottomMenu extends StatelessWidget {
  final Function changeMenu;
  final Key key;
  BottomMenu({this.changeMenu, this.key});

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.bottomCenter,
      child: Container(
          height: MediaQuery.of(context).size.height / 15,
          width: MediaQuery.of(context).size.width / 1.5,
          decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.only(
                topLeft: const Radius.circular(20.0),
                topRight: const Radius.circular(20.0),
              )),
          child: Center(
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                CustomBottomIcon(
                    icon: Icons.wallet_travel,
                    index: 0,
                    changeMenu: changeMenu),
                SizedBox(
                  width: 10,
                ),
                CustomBottomIcon(
                    icon: Icons.person, index: 1, changeMenu: changeMenu),
                SizedBox(
                  width: 10,
                ),
                CustomBottomIcon(
                    icon: Icons.email, index: 2, changeMenu: changeMenu),
                SizedBox(
                  width: 10,
                ),
              ],
            ),
          )),
    );
  }
}

and this is what the ChangeMenu function does:

 final List<Widget> list = [
    Text("Work 1"),
    Text("Work 2"),
    Text("Work 3"),
  ];

  int i = 0;

  void changeMenu(index) {
    setState(() {
      i = index;
    });
  }

It's placed on another parent widget

Upvotes: 1

Views: 1208

Answers (3)

David
David

Reputation: 1876

This is a very similar idea to a radio button, which includes two parameters, value and groupValue. You can implement yours in a similar way: change your BottomMenu to a StatefulWidget, add a int groupValue = -1 in your state, and pass this to your CustomIconButton, as well as index.

You can the choose the colour of your button based on whether index == groupIndex inside your CustomIconButton and update groupIndex in your BottomMenu class every time a button is pressed. You can do this by wrapping your changeMenu parameter with something like

changeMenu: (index) {
    setState(() {
        groupIndex = index;
    });
    changeMenu(index);
},

or move this to a separate function.

If you want to turn all of them off when you press one of them a second time, just add this a check to the wrapper function and set groupIndex to -1 if it's the same as index.

Upvotes: 0

Mohammad Kurjieh
Mohammad Kurjieh

Reputation: 1143

In a case like yours it is best to use a state management library such as Provider or BloC. But if you want to stick to your approach, then you will have to modify your code to be the following:

Custom Icon Widget

class _CustomBottomIconState extends State<CustomBottomIcon> {
  final Function changeMenu;
  final int index;
  final int selectedIndex;

  _CustomBottomIconState({this.changeMenu, this.index, this.selectedIndex});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => changeMenu(index),
      child: Container(
        decoration: BoxDecoration(
          color: selectedIndex == index ? Colors.green : Colors.indigo,
          borderRadius: BorderRadius.circular(100),
        ),
        height: MediaQuery.of(context).size.height / 20,
        width: MediaQuery.of(context).size.height / 20,
        child: Icon(
          widget.icon,
          color: Colors.white,
        ),
      ),
    );
  }
}

Bottom Menu

class BottomMenu extends StatelessWidget {
  final Function changeMenu;
  final Key key;
  final int selectedIndex;
  BottomMenu({this.changeMenu, this.key, this.selectedIndex});

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.bottomCenter,
      child: Container(
        height: MediaQuery.of(context).size.height / 15,
        width: MediaQuery.of(context).size.width / 1.5,
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.only(
            topLeft: const Radius.circular(20.0),
            topRight: const Radius.circular(20.0),
          ),
        ),
        child: Center(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              CustomBottomIcon(
                index: 0,
                icon: Icons.wallet_travel,
                selectedIndex: selectedIndex,
                changeMenu: changeMenu,
              ),
              SizedBox(width: 10),
              CustomBottomIcon(
                index: 1,
                icon: Icons.person,
                selectedIndex: selectedIndex,
                changeMenu: changeMenu,
              ),
              SizedBox(width: 10),
              CustomBottomIcon(
                index: 2,
                icon: Icons.email,
                selectedIndex: selectedIndex,
                changeMenu: changeMenu,
              ),
              SizedBox(width: 10),
            ],
          ),
        ),
      ),
    );
  }
}

Change Menu Function

void changeMenu(int _index) {
  setState(() {
    selectedIndex = _index;
  });
}

Explanation

Now for the explanation, what I am doing is that I am doing a technique called state lifting, this technique is highly discouraged since it can get very messy very fast and hard to properly maintain. But for a small portion of the app it is possible to get away with it. 😉

Now what is happening is that we have a main or any class as the parent and is the one managing the state.

Below is the tree representation of your widget structure.

Example

Now when the icon is clicked, the method in the main (changeMenu) is called which takes the index of the icon in the BottomBar and set the selectedIndex to be it. This will cause a rebuild in Flutter since a state was changed. Therefore the BottomBar will be rebuilt, and it will pass the selectedIndex to the icons. Inside the icons, there exist a check which checks if the selectedIndex is equal to the icon index, depending on this check, the color is set.

Note: The initial value of selectedIndex, will determine which icon is selected, set it to -1 to make no icon selected.

Upvotes: 1

Vignesh
Vignesh

Reputation: 215

Try this one,

class _CustomBottomIconState extends State<CustomBottomIcon> {
  final Function changeMenu;
  final int index;

  int _selectedIndex = -1; // by default all button color will be green, if _selectedIndex = 0 the first index color will be indigo

  _CustomBottomIconState({this.changeMenu, this.index});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        //
        changeMenu(index);
        setState(() {
          _selectedIndex = index;
        });
      },
      child: Container(
        decoration: BoxDecoration(
          color: getColor(index),
          borderRadius: BorderRadius.circular(100),
        ),
        height: MediaQuery.of(context).size.height / 20,
        width: MediaQuery.of(context).size.height / 20,
        child: Icon(
          widget.icon,
          color: Colors.white,
        ),
      ),
    );
  }

 Color getColor(int index) {
    if (index == _selectedIndex) {
      return Colors.indigo;
    } else {
      return  Colors.green;
    }
  }
}

Upvotes: 0

Related Questions