You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter:如何复用同一页面并注入不同Bloc实现适配

复用多媒体网格页面并注入不同Bloc的实现方案

核心思路是通过抽象Bloc层让页面依赖抽象而非具体实现,再在不同入口处注入对应的具体Bloc,实现页面复用同时适配不同业务场景。

步骤1:抽象Bloc、事件与状态

先定义通用的抽象类,统一多媒体场景的事件和状态接口,让GroupBloc和PostBloc都遵循这个规范:

抽象事件类

abstract class MediaEvent {}

// 通用上传事件
class MediaUploadEvent extends MediaEvent {
  final List<File> mediaFiles;
  MediaUploadEvent(this.mediaFiles);
}

// 通用加载多媒体列表事件
class MediaLoadEvent extends MediaEvent {}

抽象状态类

abstract class MediaState {}

class MediaLoadingState extends MediaState {}

class MediaLoadedState extends MediaState {
  final List<Media> mediaList;
  MediaLoadedState(this.mediaList);
}

class MediaUploadSuccessState extends MediaState {}

class MediaErrorState extends MediaState {
  final String errorMsg;
  MediaErrorState(this.errorMsg);
}

抽象Bloc类

abstract class MediaBloc extends Bloc<MediaEvent, MediaState> {
  MediaBloc() : super(MediaLoadingState()) {
    on<MediaLoadEvent>(_onLoadMedia);
    on<MediaUploadEvent>(_onUploadMedia);
  }

  // 子类必须实现的加载逻辑
  void _onLoadMedia(MediaLoadEvent event, Emitter<MediaState> emit);

  // 子类必须实现的上传逻辑
  void _onUploadMedia(MediaUploadEvent event, Emitter<MediaState> emit);
}

步骤2:实现具体业务Bloc

分别为群组和帖子场景实现具体的Bloc,继承抽象的MediaBloc:

GroupBloc实现

class GroupBloc extends MediaBloc {
  final Group group;
  GroupBloc(this.group);

  @override
  void _onLoadMedia(MediaLoadEvent event, Emitter<MediaState> emit) async {
    // 调用群组多媒体加载接口
    try {
      final mediaList = await GroupApi.getGroupMedia(group.id);
      emit(MediaLoadedState(mediaList));
    } catch (e) {
      emit(MediaErrorState(e.toString()));
    }
  }

  @override
  void _onUploadMedia(MediaUploadEvent event, Emitter<MediaState> emit) async {
    // 调用群组多媒体上传接口
    try {
      await GroupApi.uploadGroupMedia(group.id, event.mediaFiles);
      emit(MediaUploadSuccessState());
      add(MediaLoadEvent()); // 上传成功后重新加载列表
    } catch (e) {
      emit(MediaErrorState(e.toString()));
    }
  }
}

PostBloc实现

class PostBloc extends MediaBloc {
  final Post post;
  PostBloc(this.post);

  @override
  void _onLoadMedia(MediaLoadEvent event, Emitter<MediaState> emit) async {
    // 调用帖子多媒体加载接口
    try {
      final mediaList = await PostApi.getPostMedia(post.id);
      emit(MediaLoadedState(mediaList));
    } catch (e) {
      emit(MediaErrorState(e.toString()));
    }
  }

  @override
  void _onUploadMedia(MediaUploadEvent event, Emitter<MediaState> emit) async {
    // 调用帖子多媒体上传接口
    try {
      await PostApi.uploadPostMedia(post.id, event.mediaFiles);
      emit(MediaUploadSuccessState());
      add(MediaLoadEvent()); // 上传成功后重新加载列表
    } catch (e) {
      emit(MediaErrorState(e.toString()));
    }
  }
}

步骤3:编写复用的多媒体网格页面

页面中依赖抽象的MediaBloc,不直接引用具体Bloc,这样就能适配两种场景:

class MediaGridView extends StatelessWidget {
  const MediaGridView({super.key});

  @override
  Widget build(BuildContext context) {
    final mediaBloc = context.read<MediaBloc>();

    return Scaffold(
      appBar: AppBar(title: const Text("多媒体网格")),
      body: BlocBuilder<MediaBloc, MediaState>(
        builder: (context, state) {
          if (state is MediaLoadingState) {
            return const Center(child: CircularProgressIndicator());
          } else if (state is MediaLoadedState) {
            return GridView.builder(
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3,
                crossAxisSpacing: 4,
                mainAxisSpacing: 4,
              ),
              itemCount: state.mediaList.length,
              itemBuilder: (context, index) {
                final media = state.mediaList[index];
                return media.isImage 
                    ? Image.network(media.url) 
                    : VideoPlayerWidget(url: media.url);
              },
            );
          } else if (state is MediaErrorState) {
            return Center(child: Text(state.errorMsg));
          }
          return const SizedBox();
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          // 选择图片/视频逻辑
          final files = await pickMedia();
          if (files.isNotEmpty) {
            mediaBloc.add(MediaUploadEvent(files));
          }
        },
        child: const Icon(Icons.add),
      ),
    );
  }

  // 模拟媒体选择方法
  Future<List<File>> pickMedia() async {
    // 实现图片/视频选择逻辑
    return [];
  }
}

步骤4:在不同入口注入具体Bloc

从群组页面跳转时,注入GroupBloc;从帖子页面跳转时,注入PostBloc:

群组入口跳转

// 群组页面中跳转至多媒体网格页
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => BlocProvider(
      create: (context) => GroupBloc(currentGroup)..add(MediaLoadEvent()),
      child: const MediaGridView(),
    ),
  ),
);

帖子入口跳转

// 帖子页面中跳转至多媒体网格页
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => BlocProvider(
      create: (context) => PostBloc(currentPost)..add(MediaLoadEvent()),
      child: const MediaGridView(),
    ),
  ),
);

这样就能实现同一页面组件在不同场景下,自动使用对应的Bloc处理业务逻辑,并且能根据对应Bloc的状态自动重建UI。

内容的提问来源于stack exchange,提问作者kk-at-cbx

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 07:57:42