Flutter - Как изменить цвет кнопки при нажатии и отключить другие кнопки?

У меня есть список с несколькими зелеными кнопками, и мне нужно изменить цвет кнопки на красный при нажатии. Проблема в том, что при этом все остальные кнопки должны вернуться к своему основному зеленому цвету.

В приведенном ниже примере (рабочая версия на https://www.dartpad.dev/?id=b4ea6414b6a4ffcc7135579e673be845) все кнопки меняют цвет при нажатии независимо от других кнопок, но желаемый эффект заключается в том, что все остальные кнопки должны быть зелеными, когда нажатая кнопка красная.

import 'package:flutter/material.dart';

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
            child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            MyWidget(
              text: 'Button 1',
              onPressed: () => print('Click'),
            ),
            MyWidget(
              text: 'Button 2',
              onPressed: () => print('Click'),
            ),
            MyWidget(
              text: 'Button 3',
              onPressed: () => print('Click'),
            ),
            MyWidget(
              text: 'Button 4',
              onPressed: () => print('Click'),
            ),
          ],
        )),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  const MyWidget({
    Key? key,
    required this.text,
    required this.onPressed,
  }) : super(key: key);

  @override
  State<MyWidget> createState() => _MyWidgetState();

  final String text;
  final VoidCallback onPressed;
}

class _MyWidgetState extends State<MyWidget> {
  bool isFavourte = false;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
        style: ElevatedButton.styleFrom(
          primary: isFavourte ? Colors.red : Colors.green,
        ),
        onPressed: () {
          setState(() => isFavourte = !isFavourte);
          widget.onPressed();
        },
        child: Text(widget.text));
  }
}

Как это можно сделать?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
68
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Вот пример, делающий именно то, чего вы хотите достичь, сохраняя состояние каждой кнопки в списке и обновляя их все по мере изменения:

class Home extends StatefulWidget {
  const Home({Key? key}) : super(key: key);

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  List<Map> buttonList = [
    {
      'label': 'button1',
      'active': true,
    },
    {
      'label': 'button2',
      'active': true,
    },
    {
      'label': 'button3',
      'active': true,
    },
    {
      'label': 'button4',
      'active': true,
    },
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView.builder(
        itemCount: buttonList.length,
        itemBuilder: (context, index){
          return ElevatedButton(
            onPressed: () => onPressed(buttonList[index]),
            style: ButtonStyle(
              backgroundColor: buttonList[index]['active']
                  ? MaterialStateProperty.all(Colors.green)
                  : MaterialStateProperty.all(Colors.red),
            ),
            child: Text(buttonList[index]['label']),
          );
        },
      ),
    );
  }

  void onPressed(Map button){
    setState(() {
      for (var element in buttonList) {
        element['active'] = false;
      }
      button['active'] = true;
    });
  }
}

Жизнеспособен ли этот подход, если у меня, например, 30 кнопок или более?

ramenknight 08.05.2022 01:13

Конечно. Важна техника. Сохраните состояние каждой кнопки, чтобы вы могли изменить его.

João Soares 08.05.2022 01:25

создал переменную selectedValue в myWidget2 и идентификатор для каждой кнопки, поэтому, когда вы нажимаете кнопку, она будет устанавливать selectedValue = id, так что только кнопка с id = selectedValue станет красной

import 'package:flutter/material.dart';

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

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

class MyApp extends StatelessWidget {
int selectedValue = 0 ;
@override
Widget build(BuildContext context) {
return MaterialApp(
  theme: ThemeData.dark().copyWith(
    scaffoldBackgroundColor: darkBlue,
  ),
  debugShowCheckedModeBanner: false,
  home: MyWidget2() ,
  );
  }
  }


 class ValueChanged extends Notification {
  final int selectedValue  ;
  ValueChanged(this.selectedValue);

 }


class MyWidget2 extends StatefulWidget {
const MyWidget2({
Key? key,
}) : super(key: key);

@override
State<MyWidget2> createState() => _MyWidget2State();
}

class _MyWidget2State extends State<MyWidget2> {

int selectedValue = 0 ;

@override
Widget build(BuildContext context) {
return Scaffold(
  body: NotificationListener<ValueChanged>(
    onNotification: (n) {
      setState(() {
        selectedValue = n.selectedValue ;
        // Trigger action on parent via setState or do whatever you like.
      });
      return true;
    },
    child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            MyWidget(
              text: 'Button 1',
              onPressed: () => print('Click'),
              id: 1,
                selectedValue :selectedValue ,
            ),
            MyWidget(
              text: 'Button 2',
              onPressed: () => print('Click'),
              id: 2,
              selectedValue :selectedValue ,

            ),
            MyWidget(
              text: 'Button 3',
              onPressed: () => print('Click'),
              id:3,
              selectedValue :selectedValue ,

            ),
            MyWidget(
              text: 'Button 4',
              onPressed: () => print('Click'),
              id:4,
              selectedValue :selectedValue ,

            ),
          ],
        )),
      ),
     );
   }
  }


  class MyWidget extends StatefulWidget {
  const MyWidget({
  Key? key,
  required this.text,
  required this.onPressed,
  required this.id,
  required this.selectedValue,

  }) : super(key: key);

  @override
 State<MyWidget> createState() => _MyWidgetState();
 final int id;
 final String text;
 final VoidCallback onPressed;
 final int selectedValue ;
 }

 class _MyWidgetState extends State<MyWidget> {

 @override
 Widget build(BuildContext context) {
 return ElevatedButton(
    style: ElevatedButton.styleFrom(
      primary: widget.id == widget.selectedValue ? Colors.red : Colors.green,
    ),
    onPressed: () {
      setState(() => ValueChanged(widget.id).dispatch(context));
      widget.onPressed();
    },
    child: Text(widget.text));
   }
  }
Ответ принят как подходящий
import 'package:flutter/material.dart';

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

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

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

class MyAppState extends State<MyApp> {
  final selectedIndexNotifier = ValueNotifier<int?>(null);
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
            child: ValueListenableBuilder<int?>(
              valueListenable: selectedIndexNotifier,
 builder: (_, selectedIndex, __) => Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            for (int i = 1; i <= 4; i++)
              MyWidget(
              key: ValueKey(i),
              text: 'Button $i',
                isFavorite: selectedIndex == i,
                onPressed: () => selectedIndex == i ? selectedIndexNotifier.value = null : selectedIndexNotifier.value = i 
              )
          ],
        ))),
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  const MyWidget({
    Key? key,
    required this.text,
    required this.isFavorite,
    required this.onPressed,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) =>  ElevatedButton(
        style: ElevatedButton.styleFrom(
          primary: isFavorite ? Colors.red : Colors.green,
        ),
        onPressed: onPressed,
        child: Text(text));

  final String text;
  final bool isFavorite;
  final VoidCallback onPressed;
}

Сначала я сделал MyWidget без состояния и создал две новые вещи:

  1. Класс ButtonData: отдельные фактические данные, которые необходимо контролировать, и сделать их масштабируемыми.
  2. MyButtonList: StatefulWidget, содержащий список логических значений для отслеживания текущей активной кнопки. вот пример: result

Создайте новый файл и скопируйте следующий код и посмотрите результат:

class TestPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: Colors.blue[800],
      ),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: MyButtonList(
            buttons: [
              ButtonData(text: 'Test'),
              ButtonData(text: 'Test'),
              ButtonData(text: 'Test'),
              ButtonData(text: 'Test'),
              ButtonData(text: 'Test'),
            ],
          ),
        ),
      ),
    );
  }
}

class MyButtonList extends StatefulWidget {
  const MyButtonList({Key? key, required this.buttons}) : super(key: key);

  final List<ButtonData> buttons;

  @override
  State<MyButtonList> createState() => _MyButtonListState();
}

class _MyButtonListState extends State<MyButtonList> {
  late List<bool> favoriateState;

  @override
  void initState() {
    favoriateState = List.generate(
        widget.buttons.length, (index) => widget.buttons[index].isFavorite);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        for (var i = 0; i < widget.buttons.length; i++)
          MyWidget(
            text: widget.buttons[i].text,
            onPressed: () {
              for (var j = 0; j < favoriateState.length; j++) {
                favoriateState[j] = false;
              }
              setState(() {
                favoriateState[i] = true;
                if (widget.buttons[i].onPressed != null) {
                  widget.buttons[i].onPressed!();
                }
              });
            },
            isFavourte: favoriateState[i],
          ),
      ],
    );
  }
}

class ButtonData {
  final String text;
  final Function()? onPressed;
  final bool isFavorite;

  ButtonData({required this.text, this.onPressed, this.isFavorite = false});
}

class MyWidget extends StatelessWidget {
  const MyWidget(
      {Key? key,
      required this.text,
      required this.onPressed,
      this.isFavourte = false})
      : super(key: key);

  final String text;
  final Function()? onPressed;
  final bool isFavourte;
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
        style: ElevatedButton.styleFrom(
          primary: isFavourte ? Colors.red : Colors.green,
        ),
        onPressed: onPressed,
        child: Text(text));
  }
}

Кажется, вы хотите эмулировать RadioButtons с помощью TextButtons.

В группе RadioListTile-ов может быть активен только один. И это то, чего вы хотите добиться, если я правильно вас понял.

Могу ли я предложить вместо этого использовать RadioListTile-s, а затем стилизовать (или тему) их по своему усмотрению: зеленый для неактивных плиток, красный для активных плиток.

Следующее просто демонстрирует использование RadioListTile, дополнительную информацию о стилизации активных/неактивных плиток можно легко найти.

import 'package:flutter/material.dart';

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

/// main application widget
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  static const String _title = 'Flutter Application';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: Scaffold(
        appBar: AppBar(title: const Text(_title)),
        body: const MyStatefulWidget(),
      ),
    );
  }
}

/// stateful widget that the main application instantiates
class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

enum Fruit { apple, banana }

/// private State class that goes with MyStatefulWidget
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  Fruit? _fruit = Fruit.apple;
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        children: <Widget>[
          RadioListTile<Fruit>(
            title: const Text('Apple'),
            value: Fruit.apple,
            groupValue: _fruit,
            onChanged: (Fruit? value) {
              setState(() {
                _fruit = value;
              });
            },
          ),
          RadioListTile<Fruit>(
            title: const Text('Banana'),
            value: Fruit.banana,
            groupValue: _fruit,
            onChanged: (Fruit? value) {
              setState(() {
                _fruit = value;
              });
            },
          ),
        ],
      ),
    );
  }
}

Источник

https://googleflutter.com/flutter-radiolisttile/

Другие вопросы по теме