0
\$\begingroup\$

This is what I wan't to do:

enter image description here

In my app I've 2 custom widgets called SelectItems. If the user clicks on item, it changes the background color. Everything is working just fine, but a code, which manages the state is a bit cumbersome:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Home(),
    );
  }
}

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  List<SelectItemModel> selectItemModels = [
    SelectItemModel(
      text: 'First item',
      isSelected: true,
    ),
    SelectItemModel(
      text: 'Second item',
      isSelected: false,
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        padding: EdgeInsets.only(
          top: 60,
        ),
        color: Colors.white,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: _buildSelectItems(),
        ),
      ),
    );
  }

  List<SelectItem> _buildSelectItems() {
    List<SelectItem> items = [];

    for (var i = 0; i < selectItemModels.length; i++) {
      var item = selectItemModels[i];
      items.add(SelectItem(
        text: item.text,
        isSelected: item.isSelected,
        onPressed: () => {_select(i)},
      ));
    }

    return items;
  }

  void _select(int index) {
    for (var i = 0; i < selectItemModels.length; i++) {
      setState(() {
        i == index
            ? selectItemModels[i].isSelected = true
            : selectItemModels[i].isSelected = false;
      });
    }
  }
}

class SelectItem extends StatelessWidget {
  const SelectItem({
    @required this.text,
    @required this.isSelected,
    @required this.onPressed,
  });

  final String text;
  final bool isSelected;
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onPressed,
      child: Container(
        width: 150,
        height: 150,
        decoration: BoxDecoration(
          color: _getColor(isSelected),
          borderRadius: BorderRadius.circular(20),
          border: Border.all(
            color: Colors.black38,
            width: 2,
          ),
        ),
        child: Center(
          child: Text(text),
        ),
      ),
    );
  }

  Color _getColor(bool isSelected) {
    return isSelected ? Colors.blueAccent : Colors.white;
  }
}

class SelectItemModel {
  SelectItemModel({
    this.text,
    this.isSelected,
  });

  String text;
  bool isSelected;
}

So how this works:

I've created a custom widget SelectItem. It just displays a square shape with text in it. If it's selected, then the color is blue, otherwise white.

class SelectItem extends StatelessWidget {
  const SelectItem({
    @required this.text,
    @required this.isSelected,
    @required this.onPressed,
  });

  final String text;
  final bool isSelected;
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onPressed,
      child: Container(
        width: 150,
        height: 150,
        decoration: BoxDecoration(
          color: _getColor(isSelected),
          borderRadius: BorderRadius.circular(20),
          border: Border.all(
            color: Colors.black38,
            width: 2,
          ),
        ),
        child: Center(
          child: Text(text),
        ),
      ),
    );
  }

  Color _getColor(bool isSelected) {
    return isSelected ? Colors.blueAccent : Colors.white;
  }
}

Then I've a SelectItemModel:

class SelectItemModel {
  SelectItemModel({
    this.text,
    this.isSelected,
  });

  String text;
  bool isSelected;
}

which I use in Home StateFullWidget to fill a List of SelectItems.

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  List<SelectItemModel> selectItemModels = [
    SelectItemModel(
      text: 'First item',
      isSelected: true,
    ),
    SelectItemModel(
      text: 'Second item',
      isSelected: false,
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        padding: EdgeInsets.only(
          top: 60,
        ),
        color: Colors.white,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: _buildSelectItems(),
        ),
      ),
    );
  }

  List<SelectItem> _buildSelectItems() {
    List<SelectItem> items = [];

    for (var i = 0; i < selectItemModels.length; i++) {
      var item = selectItemModels[i];
      items.add(SelectItem(
        text: item.text,
        isSelected: item.isSelected,
        onPressed: () => {_select(i)},
      ));
    }

    return items;
  }

  void _select(int index) {
    for (var i = 0; i < selectItemModels.length; i++) {
      setState(() {
        i == index
            ? selectItemModels[i].isSelected = true
            : selectItemModels[i].isSelected = false;
      });
    }
  }
}

I think this part is the most problematic. Is there a simpler way of filling the widget with child widgets and managing the click behavior?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.