2
\$\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\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.