Roger Cuesta
Roger Cuesta

Reputation: 942

How to color selected ListItem onTap

I want to color select value from a ListView like this :

enter image description here

The problem is I print all when I try it , code :

class _ProcedureList extends State<ProcedureList> {

bool isSelected  = true;

   _isSelected() {
    setState(() {
      if (isSelected) {
        isSelected = false;
      } else {
        isSelected = true;
      }
    });
  }



  @override
  Widget build(BuildContext context) {
    var procedureList = widget.filteredkits
        .where((kit) => kit.brand == widget.brand)
        .map((kit) => kit.procedure)
        .toSet()
        .toList();

    return Expanded(
      flex: 2,
      child: Container(
        padding: EdgeInsets.only(left: 35.0),
        child: new ListView.builder(
          itemCount: procedureList.length,
          itemBuilder: (BuildContext context, int index) {
            return Padding(
              padding: EdgeInsets.all(6.0),
              child: Column(
                children: <Widget>[
                  new Container(
                    width: 300.0,
                    height: 30.0,
                    color: Colors.grey[700],
                    padding: EdgeInsets.all(6.0),
                    child: new Text(widget.brand),
                  ),
                  GestureDetector(
                    onTap: () => widget.getProcedureSelectedandList(procedureList[index].toString()) & _isSelected(),
                    child: Container(
                      width: 300.0,
                      padding: EdgeInsets.all(3.0),
                      color: !isSelected ? Colors.white : Colors.orange,
                      child: 
                      new Text(procedureList[index])
                    ),
                  ),
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

And this is what I achieve , all colored :

enter image description here

I don't know how to only color one item when the event happens and if we can change the text color with the same event better .

Upvotes: 3

Views: 8925

Answers (3)

SoloWolf93
SoloWolf93

Reputation: 1089

sorry for the late reply. I have a better solution and i have changed in your code

    class _ProcedureList extends State<ProcedureList> {



        int isSelected  = -1; // changed bool to int and set value to -1 on first time if you don't select anything otherwise set 0 to set first one as selected. 


       _isSelected(int index) { //pass the selected index to here and set to 'isSelected'
        setState(() {
            isSelected = index;
        });
      }



      @override
      Widget build(BuildContext context) {
        var procedureList = widget.filteredkits
            .where((kit) => kit.brand == widget.brand)
            .map((kit) => kit.procedure)
            .toSet()
            .toList();

    return Expanded(
      flex: 2,
      child: Container(
        padding: EdgeInsets.only(left: 35.0),
        child: new ListView.builder(
          itemCount: procedureList.length,
          itemBuilder: (BuildContext context, int index) {
            return Padding(
              padding: EdgeInsets.all(6.0),
              child: Column(
                children: <Widget>[
                  new Container(
                    width: 300.0,
                    height: 30.0,
                    color: Colors.grey[700],
                    padding: EdgeInsets.all(6.0),
                    child: new Text(widget.brand),
                  ),
                  GestureDetector(
                    onTap: () => widget.getProcedureSelectedandList(procedureList[index].toString()) & _isSelected(index), //pass index value to '_isSelected' 
                    child: Container(
                      width: 300.0,
                      padding: EdgeInsets.all(3.0),
                      color: isSelected != null && isSelected == index //set condition like this. voila! if isSelected and list index matches it will colored as white else orange.
                             ? Colors.white 
                             : Colors.orange,
                      child: 
                      new Text(procedureList[index])
                    ),
                  ),
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

Still Confused check this blog

Upvotes: 1

Amir Hossein Mirzaei
Amir Hossein Mirzaei

Reputation: 2375

you should have a isSelected value per every single item you have in the list an then when the user clicks on the one of the items in the list you will change the isSelected value just for taped item index and in the build statement you should do the action base on the isSelected value of the passed in index

here is example :

enter image description here

class MyListWidgetState extends State<MyListWidget> {
  List<String> items = ["A", "B", "C", "D", "E", "F"];

  Map<int, bool> itemsSelectedValue = Map();

  @override
  Widget build(BuildContext context) {
    return new ListView.builder(
        itemCount: items.length,
        itemBuilder: (context, index) {
          bool isCurrentIndexSelected = itemsSelectedValue[index] == null
              ? false
              : itemsSelectedValue[index];

          Container contianer;

          if (isCurrentIndexSelected) {
            contianer = new Container(
              alignment: Alignment.center,
              height: 100.0,
              color: Colors.blue,
              child: new Text(
                items[index],
                style: new TextStyle(color: Colors.red, fontSize: 18.0),
                textAlign: TextAlign.center,
              ),
            );
          } else {
            contianer = new Container(
              alignment: Alignment.center,
              height: 100.0,
              color: Colors.red,
              child: new Text(
                items[index],
                style: new TextStyle(color: Colors.blue, fontSize: 18.0),
                textAlign: TextAlign.center,
              ),
            );
          }

          return GestureDetector(
            onTap: () {
              print("${!isCurrentIndexSelected}");
              itemsSelectedValue[index] = !isCurrentIndexSelected;

              setState(() {
                print("OnClick : $index + ${itemsSelectedValue[index]}");
              });
            },
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: contianer,
            ),
          );
        });
  }
}

or you can create StateFull widget which keeps own isSelected Value for every item of your list like the below example :

List<String> items = [
  "A",
  "B",
  "C",
  "D",
  "E",
  "F",
  "D",
  "J",
  "K",
  "L",
  "M",
  "P"
];

class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new Scaffold(
          body: new ListView.builder(
        itemCount: items.length,
        itemBuilder: (context, index) {
          return new SelectableWidget(
            new SelectableWidgetViewModel(
              items[index],
              isSelected: false,
            ),
          );
        },
      )),
    );
  }
}

class SelectableWidget extends StatefulWidget {
  final SelectableWidgetViewModel viewModel;

  SelectableWidget(this.viewModel);

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

class SelectableWidgetState extends State<SelectableWidget> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    Container container;

    if (widget.viewModel.isSelected) {
      container = new Container(
        alignment: Alignment.center,
        height: 100.0,
        color: Colors.blue,
        child: new Text(
          widget.viewModel.title,
          style: new TextStyle(color: Colors.red, fontSize: 18.0),
          textAlign: TextAlign.center,
        ),
      );
    } else {
      container = new Container(
        alignment: Alignment.center,
        height: 100.0,
        color: Colors.red,
        child: new Text(
          widget.viewModel.title,
          style: new TextStyle(color: Colors.blue, fontSize: 18.0),
          textAlign: TextAlign.center,
        ),
      );
    }

    return GestureDetector(
      onTap: () {
        setState(() {
          widget.viewModel.isSelected = !widget.viewModel.isSelected;
        });
      },
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: container,
      ),
    );
  }
}

class SelectableWidgetViewModel {
  bool isSelected;
  String title;

  SelectableWidgetViewModel(this.title, {this.isSelected = false});
}

i think second option is better for performance reasons

Upvotes: 5

Shady Aziza
Shady Aziza

Reputation: 53347

What you need to do is to refactor your code such that the widget that is being passed into the ListView builder function have it is own StatefulWidget. So move the widget tree inside the builder function into its own separate StatefulWidget and handle the state manipulation logic in there instead of ProcedureList. The reason you are getting this behavior is because every instance that is generated from the ListView.builder is being exposed to the same state value of isSelected by moving your logic into a separate StatefulWidget each instance will have its own state.

Upvotes: 0

Related Questions