Scenario

我有一个创建5个项目的ListView.Builder.每个磁贴都有一个文本和一个图标.当图标被点击时,它需要在粗体和普通之间切换文本的字体,以及将图标从已读切换到未读.

我正在使用BLOC进行国家管理.

Code

tile_state.dart

abstract class TileState {}

class UnreadState extends TileState {
  final bool isUnread;
  UnreadState({
    required this.isUnread,
  });
}

class ReadState extends TileState {
  final bool isUnread;
  ReadState({
    required this.isUnread,
  });
}

tile_event.dart

abstract class TileEvent {}

class UnreadEvent extends TileEvent {
  final bool unreadStatus;

  UnreadEvent(this.unreadStatus);
}

class ReadEvent extends TileEvent {
  final bool unreadStatus;

  ReadEvent(this.unreadStatus);
}

tile_bloc.dart

class TileBloc extends Bloc<TileEvent, TileState> {
  TileBloc() : super(UnreadState(isUnread: true)) {
    on<UnreadEvent>((event, emit) => emit(UnreadState(isUnread: true)));
    on<ReadEvent>((event, emit) => emit(ReadState(isUnread: false)));
  }
}

tile.dart

class CustomTile extends StatelessWidget {
  CustomTile({
    Key? key,
    required this.text,
    required this.isUnread,
    required this.onTap,
  }) : super(key: key);

  final String text;
  bool isUnread;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 7.5),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(
            text,
            style: TextStyle(
              fontWeight: isUnread ? FontWeight.w600 : FontWeight.w200,
            ),
          ),
          InkWell(
            onTap: onTap,
            child: Icon(
              isUnread ? Icons.mail_rounded : Icons.mail_outline_rounded,
            ),
          ),
        ],
      ),
    );
  }
}

tile_page.dart

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

  @override
  State<TilesPage> createState() => _TilesPageState();
}

class _TilesPageState extends State<TilesPage> {
  @override
  Widget build(BuildContext context) {
    final TileBloc bloc = context.read<TileBloc>();
    return Scaffold(
      appBar: AppBar(
        title: const Text("Tile Screen"),
      ),
      body: Center(
        child: Column(
          children: [
            Expanded(
              child: ListView.builder(
                itemCount: 5,
                itemBuilder: (context, index) {
                  return BlocBuilder<TileBloc, TileState>(
                    builder: (context, state) {
                      if (state is UnreadState) {
                        return CustomTile(
                          text: "Message $index",
                          isUnread: state.isUnread,
                          onTap: () {
                            bloc.add(ReadEvent(false));
                          },
                        );
                      } else if (state is ReadState) {
                        return CustomTile(
                          text: "Message $index",
                          isUnread: state.isUnread,
                          onTap: () {
                            bloc.add(UnreadEvent(true));
                          },
                        );
                      } else {
                        return const SizedBox();
                      }
                    },
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Problem

当我点击一个特定的磁贴,而不是切换该磁贴的字体宽度和图标,所有的磁贴都会同时切换它们的字体宽度和图标,也就是说,整个ListView正在重建.

Request

我想知道我的代码中有什么错误导致了这一点,以及如何只重建单个磁贴而不是整个列表.

推荐答案

您需要一个包含N个bool的列表来控制这N个项目,或者只需要一个包含所选项目的列表.模范班更好.

class Item extends Equatable { ///for value equality, equtable package
  const Item({
    required this.message,
    required this.isRead,
  });
  final String message;
  final bool isRead;

  @override
  List<Object?> get props => [message, isRead];

  Item copyWith({
    String? message,
    bool? isRead,
  }) {
    return Item(
      message: message ?? this.message,
      isRead: isRead ?? this.isRead,
    );
  }
}

现在只需要一个事件来切换读取更新.暂时跳过删除和其他事件.

abstract class TileEvent {}

class ToggleRead extends TileEvent {
  final Item item;
  ToggleRead(this.item);
}

只需要状态即可保存项目

class TileState extends Equatable {
  const TileState({required this.items});
  final List<Item> items;
  @override
  List<Object?> get props =>
      [items, identityHashCode(this)]; //not using listEquality , some reason spreed operation isn't working
}

和区块逻辑将是

class TileBloc extends Bloc<TileEvent, TileState> {
  TileBloc({List<Item> initItems = const []})
      : super(TileState(items: initItems)) {
    on<ToggleRead>((event, emit) {
      final selectedItem = event.item;
      final currentItems = state.items;

      final selectedItemIndex = currentItems
          .indexOf(selectedItem); //you can pass just index instead of item
      if (selectedItemIndex > -1) {
        bool isRead = state.items[selectedItemIndex].isRead;

        currentItems[selectedItemIndex] =
            currentItems[selectedItemIndex].copyWith(isRead: !isRead);

        /// ... because state value is list
        emit(TileState(items: [...currentItems]));
      }
    });
  }
}

和测试代码段

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: BlocProvider(
          create: (_) => TileBloc(
              initItems: List.generate(5,
                  (index) => Item(message: "message $index", isRead: false))),
          child: TilesPage()),
    );
  }
}

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

  @override
  State<TilesPage> createState() => _TilesPageState();
}

class _TilesPageState extends State<TilesPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Tile Screen"),
      ),
      body: Center(
        child: Column(
          children: [
            Expanded(
              child: BlocBuilder<TileBloc, TileState>(
                builder: (context, state) {
                  return ListView.builder(
                    itemCount: state.items.length,
                    itemBuilder: (context, index) {
                      final isRead = state.items[index].isRead;
                      return CustomTile(
                        text: "Message $index",
                        isRead: isRead,
                        onTap: () {
                          final event = ToggleRead(state.items[index]);
                          BlocProvider.of<TileBloc>(context).add(event);
                        },
                      );
                    },
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class CustomTile extends StatelessWidget {
  const CustomTile({
    Key? key,
    required this.text,
    required this.isRead,
    required this.onTap,
  }) : super(key: key);

  final String text;
  final bool isRead;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 7.5),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(
            text,
            style: TextStyle(
              fontWeight: isRead ? FontWeight.w600 : FontWeight.w200,
            ),
          ),
          InkWell(
            onTap: onTap,
            child: Icon(
              isRead ? Icons.mail_rounded : Icons.mail_outline_rounded,
            ),
          ),
        ],
      ),
    );
  }
}

Flutter相关问答推荐

创建多个具有适当填充和对齐的按钮的最佳方法

ShaderMask上的文本渐变问题

我如何才能收到每天重复的预定通知?

底部导航栏项目在抖动中不更改 colored颜色

TextFormfield无法与自动路由一起正常工作

无效参数(S):隔离消息中的非法参数:try 旋转图像时对象不可发送

应为Map String,dynamic类型的值,但得到的值为type()= Map String,dynamic'

从future 列表到流列表Flutter Firebase

如何对齐 AppBar Actions 中的文本?

找不到任何与 com.google.firebase:firebase-sessions:[15.0.0, 16.0.0) 匹配的版本

FutureProvider 不返回数据给 UI

围绕父组件旋转位置组件

ListView 后台可见

小部件堆栈未显示顶层

有没有办法在没有收缩包装的情况下在列中使用 ListView.Builder?

Positioned 的宽度或 SizedBox 的宽度都不适用于堆栈小部件 - Flutter

我几乎每次都将 Stateless Widget 与 BLoC 一起使用.我错了吗?

Flutter 本地通知在最新版本中不起作用

在Flutter 中将数字转换为印度文字样式

如何在Flutter 的 initState 中初始化 Firestore 查询