【问题标题】:Fluter Bloc Remove Item From GridFlutter Bloc 从网格中删除项目
【发布时间】:2021-01-29 14:01:01
【问题描述】:

我有一个由集团状态控制的小部件。作为开始,一个 url 列表被传递给用于创建初始网格的小部件。然后,一旦必须添加或删除 url,就会调用 bloc 事件来更新列表并产生新的状态。然而,我的问题是,当一个项目被删除时,打印到屏幕上的列表是正确的(删除了项目),但 gridview 似乎只删除了最后一个图像而不是删除的图像。我在这里做错了什么?

Related question(我确实从 Gridview.count 更改为 Gridview.builder),但无济于事。

小部件:

class ItemImageGrid extends StatelessWidget {


  final List<String> urls;

  const ItemImageGrid({Key key, this.urls }) : super(key: key);


  @override
  Widget build(BuildContext context) {
    return Container(
      child: BlocBuilder<ItemGridBloc, ItemGridState>(
        builder: (context, state) {
          if(state is ItemGridInitialState){
            print("state is ItemGridInitialState");
            return _showCarouselAndStartLoading(context);

          }else  if(state is ImagesUpdatedState){
            print("state is ImagesUpdatedState");
            return _showGrid(context, state.urls);

          }
          return Center(child: CircularProgressIndicator());
        },
      ),
    );
  }

  Widget _showCarouselAndStartLoading(BuildContext context){
    BlocProvider.of<ItemGridBloc>(context).add( // add or dispatch??? try both to see difference - seems dispatch cannot be used here...
      LoadImageEvent(urls),
    );
    return Center(child: CircularProgressIndicator());
  }



  Widget _showGrid(BuildContext context, List<String> urls) {

    if(urls.isEmpty){
      return Center(
          child: Text("No photos yet")
      );
    }

  return GridView.builder(
    itemCount: urls.length,
      physics: NeverScrollableScrollPhysics(),
      shrinkWrap: true,
      // You must use the GridDelegate to specify row item count
      // and spacing between items
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
       // childAspectRatio: 1.0,
        crossAxisSpacing: 5.0,
        mainAxisSpacing: 5.0,
      ),
      itemBuilder: (BuildContext context, int index) {
        return  BlocProvider(
          create: (context) => CachedImageBloc( ),
          child: GestureDetector(
            onTap: () => {
              Navigator.of(context).push(
                  MaterialPageRoute(
                      builder: (context2) {
                        //   List<String> clonedList;
                        return BlocProvider(
                          create: (context) => new CachedImageBloc( ),
                          child: new BasicImagePreview(
                            imageFilePath: urls[index],
                            //  bloc: BlocProvider.of<InspectionItemAddEditBloc>(context),
                            //   image_paths: imagePaths,
                            onDelete: () =>{
             
                              BlocProvider.of<ItemGridBloc>(context).add(ImageRemovedEvent( urls[index])),

                            },
                          ),
                        );
                      }))
            },
            child: LocalImageViewer(
                url:  urls[index],
                errorAssetPath: 'assets/error_loading.png' ),
          ),

        );
      },
  );


  }
  
}

集团:

class ItemGridBloc extends Bloc<ItemGridEvent, ItemGridState> {

  List<String> urls;

  ItemGridBloc();

  @override
  Stream<ItemGridState> mapEventToState(
      ItemGridEvent event,
      ) async* {
    print('event = $event');

    if(event is ImageAddedEvent){
      yield* _mapImageAddedEventToState(event);
    }
    else if(event is ImageRemovedEvent){
      yield* _mapImageRemovedEventToState(event);
    } else if(event is LoadImageEvent){
      yield* _mapLoadImageEventToState(event);
    }
  }

  @override
  ItemGridState get initialState => ItemGridInitialState();

  Stream<ItemGridState> _mapImageAddedEventToState(ImageAddedEvent event) async*{

    urls.add(event.url);
    yield ImagesUpdatedState(List.of(urls));
  }

  Stream<ItemGridState>_mapImageRemovedEventToState(ImageRemovedEvent event) async*{

    print('urls b4 remove:');
    urls.forEach((element) {print('$element');});

    print('removing url: ${event.url}');
    urls.remove(event.url);
    print('urls after remove:');

    urls.forEach((element) {print('$element');});

    yield ImagesUpdatedState(urls);

  }

  Stream<ItemGridState> _mapLoadImageEventToState(LoadImageEvent event) async*{
    urls = List.of(event.urls);
    yield ImagesUpdatedState(event.urls);
  }
}

事件类:

abstract class ItemGridEvent extends Equatable {
  const ItemGridEvent();
}

class ImageAddedEvent extends ItemGridEvent{

  final String url;

  ImageAddedEvent(this.url);

  @override
  List<Object> get props => [url];

}

class LoadImageEvent extends ItemGridEvent{

  final List<String> urls;

  LoadImageEvent(this.urls);

  @override
  List<Object> get props => [urls];

}

class ImageRemovedEvent extends ItemGridEvent{

  final String url;

  ImageRemovedEvent(this.url);

  @override
  List<Object> get props => [url];

}

状态类:

abstract class ItemGridState extends Equatable {
  const ItemGridState();
}

class ItemGridInitialState extends ItemGridState {
  @override
  List<Object> get props => [];
}

class ImagesUpdatedState extends ItemGridState {

  final List<String> urls;

  ImagesUpdatedState(this.urls);

  @override
  List<Object> get props => [urls];
}

【问题讨论】:

  • 您需要将更新后的 URL 传递给 ItemImageGrid() -- 这实际上是在渲染网格
  • @jitsm555 试过了,我注意到问题与我没有为小部件分配密钥有关。请参阅我的答案以获得进一步的解释。

标签: flutter bloc


【解决方案1】:

经过几个小时的搜索,我发现答案与 Flutter 中的 KEYS 相关。

简而言之,Flutter 仅按类型而不是状态来比较小部件。因此,当 GridView 中表示的 List 的状态发生变化时,Flutter 不知道应该删除哪些子项,因为它们的类型仍然相同并签出。 Flutter 发现的唯一问题是项目的数量,这就是为什么它总是删除 Grid 中的最后一个小部件。

因此,如果您想在 Flutter 中操作包含有状态子级的列表,请为每个子级的顶级小部件分配一个 Key。 this article 有更详细的解释。

我对代码所做的唯一更改是在生成项目时如下:

itemBuilder: (BuildContext context, int index) {
        return  BlocProvider(
          key: UniqueKey(), // assign the key
          create: (context) => CachedImageBloc( ),
          child: GestureDetector(
            onTap: () => {
              Navigator.of(context).push(
                  MaterialPageRoute(
                      builder: (context2) {
                        //   List<String> clonedList;
                        return BlocProvider(
                          create: (context) => new CachedImageBloc( ),
                          child: new BasicImagePreview(
                            imageFilePath: urls[index],
                            //  bloc: BlocProvider.of<InspectionItemAddEditBloc>(context),
                            //   image_paths: imagePaths,
                            onDelete: () =>{
             
                              BlocProvider.of<ItemGridBloc>(context).add(ImageRemovedEvent( urls[index])),

                            },
                          ),
                        );
                      }))
            },
            child: LocalImageViewer(
                url:  urls[index],
                errorAssetPath: 'assets/error_loading.png' ),
          ),

        );
      },

【讨论】:

    【解决方案2】:

    非常感谢您对 TM00 的回答。

    我的 ListView.builder 在删除时没有正确更新,感谢您,我得到了它的工作。

    对于遇到列表视图或网格视图构建器未使用 BLoC 删除正确项目的问题的任何其他人:

    只需将你的建筑物品包装在一个容器中并添加一个 UniqueKey

    ListView.builder(
                          itemBuilder: (context, index) => 
                              Container(
                                key: UniqueKey(), 
                                child: Widget(),
                          itemCount: array.length,
                        );
    

    【讨论】:

      【解决方案3】:

      在你删除一个项目时,你应该将列表的一个新实例传递给 ImagesUpdatedState

      为什么? 因为您正在将对象的引用传递给您的状态,并且 bloc 会将新状态与前一个状态进行比较,并且它不会看到前一个状态 urls 属性和新状态之间没有区别,因此它不会重建小部件 传递对象的引用是 dengares,因为当您更改对象属性时,它会反映您使用的所有引用

      【讨论】:

      • 谢谢,我尝试按照建议传递列表的新实例,但没有成功。唯一有效的方法是为每个子小部件分配一个 Key。
      猜你喜欢
      • 2020-07-19
      • 2014-07-06
      • 2021-01-19
      • 2023-03-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多