【问题标题】:Change card color based on alertdialog option根据 alertdialog 选项更改卡片颜色
【发布时间】:2021-07-05 01:31:51
【问题描述】:

我有一张卡片列表,每张卡片都有一个长按功能,点击后会弹出一个警告对话框。我希望卡片根据警报对话框中选择的选项更改颜色。我的警报对话框有 3 个选项: 已完成(卡片应变为绿色), 进行中(橙色), 取消(灰色)。

首先,当屏幕加载时,它应该显示一张卡片列表,每张卡片都根据数据库中保存的值涂上颜色。然后,当用户长按卡片并从警报对话框中选择一个选项时,卡片的颜色应根据所选选项而改变。只有该特定卡片的颜色应该改变。

我在某处读到,这可能可以使用 valuechangenotifier 来实现。所以这是我到目前为止所做的:

首先我创建了如下所示的 changenotifier 类:

import 'package:flutter/material.dart';

class ColorChanger with ChangeNotifier{

  Color _color = Colors.white;

  ColorChanger(this._color);

  getColor() => _color;

  setTheme (Color color) {
    _color = color;
    notifyListeners();
  }

}

然后我在飞镖课上使用了它。但是,颜色似乎没有变化。我在这里错过了什么?

class OrderItem extends StatefulWidget {
  final ord.OrderItem order;
  OrderItem(this.order);

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

class _OrderItemState extends State<OrderItem> {
  var _expanded = false;
  var mycolor = Colors.white;

  @override
  Widget build(BuildContext context) {
    ColorChanger _color = Provider.of<ColorChanger>(context);
    var listProducts = widget.order.products;
    return Card(
      color: widget.order.orderStatus=='completed'
             ?Colors.lightGreen:widget.order.orderStatus=='inprogress'?
            Colors.orangeAccent:
             widget.order.orderStatus=='cancelled'?Colors.grey:mycolor,
      margin: EdgeInsets.all(10),
      child: Column(
        children: <Widget>[
          ListTile(
            title: RichText(
              text: new TextSpan(
                style: new TextStyle(
                  fontSize: 14.0,
                  color: Colors.black,
                ),
                children: <TextSpan>[
                  new TextSpan(
                      text: 'Order Number : ',
                      style: new TextStyle(fontWeight: FontWeight.bold)),
                  new TextSpan(text: widget.order.uniqueOrderNumber),
                ],
              ),
            ),
            trailing: IconButton(
              icon: Icon(_expanded ? Icons.expand_less : Icons.expand_more),
              onPressed: () {
                setState(() {
                  _expanded = !_expanded;
                });
              },
            ),
            onLongPress: toggleSelection,
          ),
        ],
      ),
    );
  }

  void toggleSelection() {
     ColorChanger _color = Provider.of<ColorChanger>(context,listen:false);
     Widget completeOrder = FlatButton(
                    child: Text('Completed'),
                    onPressed: () async {
                      try {
                        Navigator.of(context).pop(true);
                       // setState(() {
                             _color.setTheme(Colors.lightGreen); 
                       // });
                        await Provider.of<Orders>(context, listen: false)
                            .updateOrder(widget.order,'completed');
                      } catch (error) {
                         
                      }
    });

    Widget startOrder = FlatButton(
                    child: Text('In progress'),
                    onPressed: () async {
                      try {
                        Navigator.of(context).pop(true);
                       // setState(() {
                          _color.setTheme(Colors.orangeAccent); 
                        //});
                        //Update Db to mark order in progress
                        await Provider.of<Orders>(context, listen: false)
                            .updateOrder(widget.order,'inprogress');
                      } catch (error) {
                         
                      }
    });

    Widget cancelOrder = FlatButton(
                    child: Text('Cancel'),
                    onPressed: () async {
                      try {
                        Navigator.of(context).pop(false);
                      //  setState(() {
                            _color.setTheme(Colors.grey); 
                      //  });
                        //Update Db to mark order as cancelled
                        await Provider.of<Orders>(context, listen: false)
                            .updateOrder(widget.order,'cancelled');
                      } catch (error) {
                         
                      }
    });
          showDialog(
            context: context,
            builder: (ctx) => AlertDialog(
              title: Text('Take Action'),
              content: Text('What do you want to do with the order?'),
              actions: <Widget>[
                startOrder,
                completeOrder,
                cancelOrder
              ],
            ),
          );
      });
  }
}

根据 Loren 的回答进行第二次尝试。

import 'package:flutter/material.dart';

class ColorChanger with ChangeNotifier{

  Color color = Colors.white;

  setTheme (Color newColor) {
    color = newColor;
    notifyListeners();
  }

}


class OrderItem extends StatefulWidget {
      final ord.OrderItem order;
      OrderItem(this.order);
    
      @override
      _OrderItemState createState() => _OrderItemState();
    }
    
    class _OrderItemState extends State<OrderItem> {
      var _expanded = false;
      
      
      //Set the color based on what was last saved in the DB 
      void didChangeDependencies() async {
     var colorChanger = Provider.of<ColorChanger>(context, listen: false);
     if(widget.order.orderStatus=='completed')
        colorChanger.setTheme(Colors.lightGreen);
     else if(widget.order.orderStatus=='inprogress')
        colorChanger.setTheme(Colors.orangeAccent);
      else if(widget.order.orderStatus=='cancelled')
        colorChanger.setTheme(Colors.grey);
    super.didChangeDependencies();
  }
    
      @override
      Widget build(BuildContext context) {
        var listProducts = widget.order.products;
          return  Consumer<ColorChanger>(
       builder: (context, colorChanger, child) {
        return Card(
          color: widget.order.orderStatus=='completed'
                 ?Colors.lightGreen:widget.order.orderStatus=='inprogress'?
                Colors.orangeAccent:
                 widget.order.orderStatus=='cancelled'?Colors.grey:mycolor,
          margin: EdgeInsets.all(10),
          child: Column(
            children: <Widget>[
              ListTile(
                title: RichText(
                  text: new TextSpan(
                    style: new TextStyle(
                      fontSize: 14.0,
                      color: Colors.black,
                    ),
                    children: <TextSpan>[
                      new TextSpan(
                          text: 'Order Number : ',
                          style: new TextStyle(fontWeight: FontWeight.bold)),
                      new TextSpan(text: widget.order.uniqueOrderNumber),
                    ],
                  ),
                ),
                trailing: IconButton(
                  icon: Icon(_expanded ? Icons.expand_less : Icons.expand_more),
                  onPressed: () {
                    setState(() {
                      _expanded = !_expanded;
                    });
                  },
                ),
                onLongPress: toggleSelection,
              ),
            ],
          ),
        )};
      }
    
      void toggleSelection() {
         ColorChanger _color = Provider.of<ColorChanger>(context,listen:false);
         Widget completeOrder = FlatButton(
                        child: Text('Completed'),
                        onPressed: () async {
                          try {
                            Navigator.of(context).pop(true);
                           // setState(() {
                                 _color.setTheme(Colors.lightGreen); 
                           // });
                            await Provider.of<Orders>(context, listen: false)
                                .updateOrder(widget.order,'completed');
                          } catch (error) {
                             
                          }
        });
    
        Widget startOrder = FlatButton(
                        child: Text('In progress'),
                        onPressed: () async {
                          try {
                            Navigator.of(context).pop(true);
                           // setState(() {
                              _color.setTheme(Colors.orangeAccent); 
                            //});
                            //Update Db to mark order in progress
                            await Provider.of<Orders>(context, listen: false)
                                .updateOrder(widget.order,'inprogress');
                          } catch (error) {
                             
                          }
        });
    
        Widget cancelOrder = FlatButton(
                        child: Text('Cancel'),
                        onPressed: () async {
                          try {
                            Navigator.of(context).pop(false);
                          //  setState(() {
                                _color.setTheme(Colors.grey); 
                          //  });
                            //Update Db to mark order as cancelled
                            await Provider.of<Orders>(context, listen: false)
                                .updateOrder(widget.order,'cancelled');
                          } catch (error) {
                             
                          }
        });
              showDialog(
                context: context,
                builder: (ctx) => AlertDialog(
                  title: Text('Take Action'),
                  content: Text('What do you want to do with the order?'),
                  actions: <Widget>[
                    startOrder,
                    completeOrder,
                    cancelOrder
                  ],
                ),
              );
          });
      }
    }

当我这样做时,它会改变所有卡片的颜色,而不仅仅是一张卡片。我在这里做错了什么?

分享order.dart

class OrderItem {
  final String id;
  final double amount;
  final int deliveryFee;
  final List<CartItem> products;
  final DateTime dateTime;
  final String deliveryMethod;
  final String uniqueOrderNumber;
  final String orderStatus; 
  final String userId;
  final String customMessage;
  final String customerName; 
  final String phoneNumber; 

  OrderItem( 
      {@required this.id,
      @required this.amount,
      @required this.products,
      @required this.dateTime,
      @required this.deliveryMethod,
      @required this.uniqueOrderNumber,
      @required this.isOrderComplete,
      this.orderStatus,
      @required this.customMessage,
      @required this.deliveryFee,
      this.customerName,
      this.phoneNumber,
      @required this.userId});
}

class Orders with ChangeNotifier {
  final String authToken;
  final String userId;

  Orders(this.authToken, this.userId);

  List<OrderItem> _orders = [];
  List<OrderItem> get orders {
    return [..._orders];
  }
  Future<void> updateOrder(OrderItem order,String orderStatus) async {
final id = order.id;
final customerId = order.userId;
final url =
    'https://cv.firebaseio.com/orders/$customerId/$id.json?auth=$authToken';
     try {
         await http.patch(url,
           body: json.encode({
             'orderStatus':orderStatus
          }));
     } catch (error) {
     print(error);
   }
notifyListeners();

}

【问题讨论】:

    标签: flutter dart flutter-change-notifier


    【解决方案1】:

    更新答案:

    因此,当尝试使用 Provider 执行此操作时,我不断收到错误,这需要我不断地打扰您,以获得越来越多的代码来尝试复制您正在进行的所有事情,而我不想参与其中。

    所以这个解决方案可能会或可能不会被您接受,因为它使用GetX State Management,但它确实有效。此外,它不需要将您的整个应用程序包装在提供程序小部件中,因此处理范围等......不是问题。

    让我们将statusColor 属性添加到您的OrderItem 模型。这就是将要改变的。

     Color statusColor = Colors.white; // or whatever you you want the default color to be
    

    您更新后的Orders 类使用 GetX 而不是 ChangeNotifier(同样,不是因为 Provider 不能这样做,而是因为我处理了太多错误,坦率地说 GetX 在我看来更容易)

    class Orders extends GetxController {
      final String authToken;
      final String userId;
    
      Orders(this.authToken, this.userId);
    
      List<OrderItem> orders = []; // back to what I said earlier about no point in getters and setters here
    
    // temp function just to test this on my end
      void addOrder(OrderItem order) {
        orders.add(order);
        update();
      }
    
    // this loops through the list to find the matching order number,
    // then updates the color for just that order
      void updateOrderStatusColor({OrderItem updatedOrder, String status}) {
        for (final order in orders) {
          if (order.uniqueOrderNumber == updatedOrder.uniqueOrderNumber) {
            switch (status) {
              case 'completed':
                {
                  order.statusColor = Colors.greenAccent;
                }
                break;
              case 'inprogress':
                {
                  order.statusColor = Colors.orangeAccent;
                }
                break;
              case 'cancelled':
                {
                  order.statusColor = Colors.grey;
                }
                break;
            }
          }
        }
        update(); // equivelent of notifyListeners();
      }
      // ...the rest of your class
    }
    

    对您的卡进行一些小改动。 didChangeDependencies 可以完全消失。

    // it seems like you had 2 classes with the same name, which is not recommended
    class OrderItemCard extends StatefulWidget {
      final OrderItem order;
    
      OrderItemCard(this.order);
    
      @override
      _OrderItemCardState createState() => _OrderItemCardState();
    }
    
    class _OrderItemCardState extends State<OrderItemCard> {
      var _expanded = false;
      final controller = Get.find<Orders>(); // equivilent of Provider.of... finds the same instance without needing context
    
      void toggleSelection() {
        Widget completeOrder = TextButton(
            child: Text('Completed'),
            onPressed: () async {
              try {
                Navigator.of(context).pop(true);
    
                controller.updateOrderStatusColor(
                    updatedOrder: widget.order, status: 'completed'); // calling new function here
              } catch (error) {}
            });
    
        Widget startOrder = FlatButton(
            child: Text('In progress'),
            onPressed: () async {
              try {
                Navigator.of(context).pop(true);
                controller.updateOrderStatusColor(
                    updatedOrder: widget.order, status: 'inprogress');
              } catch (error) {}
            });
    
        Widget cancelOrder = FlatButton(
            child: Text('Cancel'),
            onPressed: () async {
              controller.updateOrderStatusColor(
                  updatedOrder: widget.order, status: 'cancelled');
              try {
                Navigator.of(context).pop(false);
              } catch (error) {}
            });
    
        showDialog(
          context: context,
          builder: (ctx) => AlertDialog(
            title: Text('Take Action'),
            content: Text('What do you want to do with the order?'),
            actions: <Widget>[startOrder, completeOrder, cancelOrder],
          ),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return Card(
          margin: EdgeInsets.all(10),
          color: widget.order.statusColor, // new color property added to your model
          child: Column(
            children: <Widget>[
              ListTile(
                title: RichText(
                  text: new TextSpan(
                    style: new TextStyle(
                      fontSize: 14.0,
                      color: Colors.black,
                    ),
                    children: <TextSpan>[
                      new TextSpan(
                          text: 'Order Number : ${widget.order.uniqueOrderNumber} ',
                          style: new TextStyle(fontWeight: FontWeight.bold)),
                    ],
                  ),
                ),
                trailing: IconButton(
                  icon: Icon(_expanded ? Icons.expand_less : Icons.expand_more),
                  onPressed: () {
                    setState(() {
                      _expanded = !_expanded;
                    });
                  },
                ),
                onLongPress: toggleSelection,
              ),
            ],
          ),
        );
      }
    }
    

    不确定您的 UI 中发生了什么,但这里有一个快速演示它如何在 GetX 中工作。这是一个简单的ListView.builder,由GetX 类的orders 列表填充。 GetBuilder&lt;Orders&gt; 小部件在调用 update() 时重建。还有一个简单的按钮,用于添加用于演示目的的虚拟项目。我不知道您是如何生成唯一订单的 # 但我只是为此使用列表索引。两者都在演示页面的脚手架内的列内。

    // Equivilent of Consumer but doesn't need context nor any provider widget above it
     GetBuilder<Orders>(
                  builder: (controller) => Expanded(
                    child: ListView.builder(
                        itemCount: controller.orders.length,
                        itemBuilder: (context, index) =>
                            OrderItemCard(controller.orders[index])),
                  ),
                ),
                TextButton(
                  onPressed: () {
                    final controller = Get.find<Orders>();
                    final orderItem = OrderItem(
                      orderStatus: ' ',
                      uniqueOrderNumber: controller.orders.length
                          .toString(), // just a hack to generate a unique order # for demo
                    );
                    controller.addOrder(orderItem);
                  },
                  child: Text('Add Item'),
                )
    

    最后一件事就是初始化 GetX 控制器。只要在您尝试使用它之前,它就可以在任何地方完成。

    void main() {
      // initialing the GetX GetxController
      // not sure how you're generating the required auth and user id
      // but I'm just passing in empty strings for now
      Get.put(Orders('', ''));
      runApp(MyApp());
    }
    

    因此,如果您在此处对 GetX 持开放态度,则可以将 Provider 留给您可能拥有的任何其他 ChangeNotifier 课程(如果您愿意)。为此,您只需将任何 Consumer&lt;Orders&gt; 替换为 GetBuilder&lt;Order&gt;,然后完全摆脱 Provider&lt;Orders&gt;(create:... 小部件。

    旧答案:

    为了正确使用 Provider 并按照你想要的方式改变颜色,你错过了几件事。

    首先,您的Card 需要包装在Consumer 小部件中,该小部件会收到更改通知并重建其子级。在Consumer 中,您需要使用ChangeNotifier 类的颜色属性。它不需要知道或关心orderStatus,因为您在调用setTheme 方法时已经明确告诉它改变颜色。

    Consumer<ColorChanger>(  // this is what rebuilds and changes the color
            builder: (context, colorChanger, child) {
          return Card(
            color: colorChanger.color, // colorChanger here is equivalent of declaring final colorChanger = Provider.of<ColorChanger>(context...
            child: Column(
              children: <Widget>[
                ListTile(
                  title: RichText(
                    text: new TextSpan(
                      style: new TextStyle(
                        fontSize: 14.0,
                        color: Colors.black,
                      ),
                      children: <TextSpan>[
                        new TextSpan(
                            text: 'Order Number : ',
                            style: new TextStyle(fontWeight: FontWeight.bold)),
                        new TextSpan(text: widget.order.uniqueOrderNumber),
                      ],
                    ),
                  ),
                  trailing: IconButton(
                    icon: Icon(_expanded ? Icons.expand_less : Icons.expand_more),
                    onPressed: () {
                      setState(() {
                        _expanded = !_expanded;
                      });
                    },
                  ),
                  onLongPress: toggleSelection,
                ),
              ],
            ),
          );
        });
    

    接下来,see this link 解释为什么在 ChangeNotifier 类中使用私有 _color 和公共 getColor 没有任何收获。

    所以让我们稍微简化一下。

    class ColorChanger with ChangeNotifier {
      Color color = Colors.white;
    
      ColorChanger(this.color);
    
      setTheme(Color newColor) {
        color = newColor;
        notifyListeners();
      }
    }
    

    现在,每当您从对话框中调用 setTheme 函数时,该卡片都会更改为您传入其中的任何颜色,因为 Consumer 小部件会收到通知,并将使用 ChangeNotifier 的更新颜色值重建类。

    【讨论】:

    • 请根据您的建议查看我编辑的答案。
    • 我没有意识到你有多张卡片。你有几张卡?还是会改变?
    • 根据订单数量而变化。每个订单都驻留在自己的卡片中,并且每个订单都与订单状态相关联。所以我只想更改特定卡的订单状态。
    • 明白了,抱歉,也许我应该假设不止一个。今天没有时间看这个,但我会在第二天或第二天回来查看,如果您还没有找到解决方案,我会帮助您。
    • 谢谢洛伦。这两天我会搜索更多。如果我不能解决它,我会伸出援手
    【解决方案2】:

    这样的事情将是实现您想要实现的目标的最简单方法:

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
    
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      // define a list of colors:
      final colors = <Color>[
        Colors.white, // this is the inital color
        Colors.green,
        Colors.orange,
        Colors.grey
      ];
      int index = 0;
    
      Future<int> showMyDialog(BuildContext context) async {
        // Since all Navigator.push(...) and showDialog(...) calls are futures
        // we can send values alongside them when we pop the context:
        // final value = await Navigator.push(...);
        // or
        // final value = await showDialog(...);
        // then we do a:
        // Navigator.pop(context, SOME_VALUE,);
        // the value variable will be assigned to the one we sent
        return await showDialog(
          context: context,
          builder: (context) => AlertDialog(
            title: Text('Take Action'),
            content: Text('What do you want to do with the order?'),
            actions: <Widget>[
              TextButton(
                  child: Text('Completed',
                      style: TextStyle(
                        color: Colors.green,
                      )),
                  onPressed: () => Navigator.pop(context, 1)),
              TextButton(
                  child: Text('In progress',
                      style: TextStyle(
                        color: Colors.orange,
                      )),
                  onPressed: () => Navigator.pop(context, 2)),
              TextButton(
                  child: Text('Cancel',
                      style: TextStyle(
                        color: Colors.grey,
                      )),
                  onPressed: () => Navigator.pop(context, 3)),
            ],
          ),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Column(children: <Widget>[
            Card(
              color: colors[index],
              child: Container(width: 50, height: 50),
            ),
            ElevatedButton(
                child: Text('Show dialog'),
                onPressed: () async {
                  // call the showMyDialog function, it returns
                  // a future int so we have to await it
                  final int _index = await showMyDialog(context);
                  
                  // if the returned value (_index) is null we use
                  // the old one value to avoid erros in the code
                  setState(() => index = _index ?? index);
                }),
          ]),
        );
      }
    }
    

    【讨论】:

      【解决方案3】:

      一个非常简单的解决方法是声明一个全局颜色变量 cardColor 并将其分配给卡片的颜色属性。然后在警报对话框中,更改小部件的“onChange”或“onTap”属性,以便在点击时,小部件将全局变量 cardColor 的值更改为不同的颜色。不要忘记执行最后一步,即在 setState() 中更改变量的值

      【讨论】:

      • 这将如何改变卡片本身的颜色?
      • 当您在 setState() 中更改 Flutter 中变量 (cardColor) 的值时,flutter 会更改所有依赖于该变量的可见 UI 元素。因为卡片的 color 属性设置为变量 cardColor,调用 setstate 会改变卡片的活动颜色。
      【解决方案4】:

      使用 AwesomeDialog 实现它的最佳方式 https://pub.dev/packages/awesome_dialog

      AwesomeDialog(
                  context: context,
                  dialogType: DialogType.INFO,
                  animType: AnimType.BOTTOMSLIDE,
                  title: 'Dialog Title',
                  desc: 'Dialog description here.............',
                  btnCancelOnPress: () {},
                  btnOkOnPress: () {},
                  )..show();
      

      【讨论】:

      • 这如何改变卡片的颜色?
      • 您可以使用 btnCancelOnPress 或 btnOkOnPress 更改卡片的颜色。