Flutter 使用bloc模拟首页实现下拉刷新

这篇具有很好参考价值的文章主要介绍了Flutter 使用bloc模拟首页实现下拉刷新。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


前言

思考:这篇文章主要是记录bloc与下拉刷新功能的使用
本篇文章是延续上一篇文章中所包含的bloc和自定义http库的使用,使用发现在上一篇文章中对bloc的理解有些误区导致最后实现好几个方式都感觉不尽人意,最终尝试查看官方示例与个人示例进行比对后发现了问题所在, 下面是效果图: (注:所使用的api来源于wanandroid api)

flutter bloc与上下拉刷新使用记录演示


一、项目结构

├── bloc                                 - 业务逻辑
│   ├── banner                           - 轮播图
│   │   ├── bloc.dart
│   │   ├── contract.dart
│   │   └── view.dart
│   ├── homeList                         - 首页列表
│   │   ├── bloc.dart                    - 具体业务逻辑
│   │   ├── contract.dart                - 包含start与enevt
│   │   └── view.dart                    - 子布局
│   └── index.dart                       - 导出配置
└── view.dart                            - 布局

二、示例演示

1. 定义 Repository

‘est_repository.dart’:用于调用主页面对应接口

class TestRepository extends BaseRepository {
  final _repository = MyHttpRepository();

  /// 获取轮播图
  Future<RepositoryResult<List<BannerModel>>> getBanner({
    // 请求结束,可用于关闭加载动画
    void Function(bool isShow)? onLoading,
  }) async {
    final request = RockRequestBuilder(url: '/banner/json');
    final response = await _repository.baseRequest(
        onLoading: onLoading,
        request: () => RockNetHelp.get(request),
        onDataConversion: (jsonData) => BaseModel.fromJson(
            jsonData, (json) => BannerModel.fromJsonArray(json)));
    return response;
  }

  /// 获取首页列表
  Future<RepositoryResult<HomeArticleModel>> getArticleList(
    int page,
    int size, {
    // 请求结束,可用于关闭加载动画
    void Function(bool isShow)? onLoading,
  }) async {
    final request = RockRequestBuilder(url: '/article/list/$page/json')
        .addQuery('page_size', size);
    return await _repository.baseRequest(
        onLoading: onLoading,
        request: () => RockNetHelp.get(request),
        onDataConversion: (jsonData) => BaseModel.fromJson(
            jsonData, (json) => HomeArticleModel.fromJson(json)));
  }
}

‘my_http_repository.dart‘:请求成功逻辑失败的处理,并将http 401也放在了同一个地方处理

class MyHttpRepository extends HttpRepository {

  /// 请求失败处理, 独立个体方便使用, 上面代码也使用到了这个,这里的封装需要根据对应项目业务来展开
 static void requestFailureError(int errCode, String? errMsg, void Function()? onAuth) {
    if (errCode == 401 || errCode == -1001) {
      onAuth?.call();
    }
  }
}

2. 实现banner状态管理与布局

contract.dart:定义start与enevt

part of 'bloc.dart';

/// 状态
enum BannerStatus { init, success, noData, onAuthority }
final class BannerState extends Equatable {

  final BannerStatus status;
  final List<BannerModel> body;

  const BannerState({this.status = BannerStatus.init, this.body = const []});

  
  List<Object?> get props => [status, body];

  BannerState copyWith({
    BannerStatus? status,
    List<BannerModel>? body,
  }) {
    return BannerState(
      status: status ?? this.status,
      body: body ?? this.body,
    );
  }
}

// 事件
abstract class BannerEvent {

  /// 发送请求
  static void send(BuildContext context) {
    context.read<BannerBloc>()
        .add(BannerSend());
  }
}

/// 发送请求
final class BannerSend extends BannerEvent {}

bloc.dart:具体业务逻辑

part 'contract.dart';

/// 轮播图
class BannerBloc extends Bloc<BannerEvent, BannerState> {
  /// 网络请求
  final TestRepository _repository;

  BannerBloc({required TestRepository repository})
      : _repository = repository,
        super(const BannerState()) {
    on<BannerSend>(_initBannerSend);
  }

  void _initBannerSend(BannerSend event, Emitter<BannerState> emit) async {
    // http请求
    final response = await _repository.getBanner();
    // 判断当前请求结果
    switch (response) {
      case Success():
        // 返回加载成功的数据
        emit(state.copyWith(
          status: BannerStatus.success,
          body: response.success,
        ));
        break;
      case Error():
        final error = response.error;
        if (error case NoData()) {
          // 返回无数据
          emit(state.copyWith(status: BannerStatus.noData));
        }
        if (error case RequestFailure()) {
          // 在下面使用中有对这个函数的定义,这个函数对应于具体的业务项目的业务逻辑处理
          MyHttpRepository.requestFailureError(
            error.code,
            error.msg,
            () => emit(
              state.copyWith(status: BannerStatus.onAuthority),
            ),
          );
        }
        break;
    }
  }
}

view.dart:banner布局,使用到了card_swiper、cached_network_image

import 'bloc.dart';

/// 轮播图
class BannerViewWidget extends StatelessWidget {
  const BannerViewWidget({super.key});

  
  Widget build(BuildContext context) {
    return SliverToBoxAdapter(
      child: SizedBox(
        height: 200,
        child: BlocBuilder<BannerBloc, BannerState>(
          builder: (context, state) {
            if (state.status == BannerStatus.onAuthority) {
              // 跳转路由
            }
            if (state.status == BannerStatus.success) {
              final urlArray = state.body.map((m) => m.imagePath).toList();
              return _banner(urlArray);
            }
            return const Center();
          },
        ),
      ),
    );
  }

  /// 轮播图具体实现
  Widget _banner(List<String> urls) => BannerWidget(
        urls: urls,
        widget: (BuildContext context, int index, String url) => Padding(
          padding: const EdgeInsets.fromLTRB(10, 10, 10, 0),
          child: CachedNetworkImage(
            imageUrl: url,
            imageBuilder: (context, imageProvider) => Container(
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(10),
                //设置圆角
                image: DecorationImage(
                  image: imageProvider,
                  fit: BoxFit.fill,
                ),
              ),
            ),
          ),
        ),
      );
}

3. 实现文章列表状态管理与布局

contract.dart:定义start与enevt

part of 'bloc.dart';

// 状态
enum HomeListStatus { init, success, onAuthority }

final class HomeListState extends Equatable {
  final HomeListStatus status;

  /// 数据
  final HomeArticleModel? body;

  const HomeListState({this.status = HomeListStatus.init, this.body});

  
  List<Object?> get props => [status, body];

  HomeListState copyWith({
    HomeListStatus? status,
    HomeArticleModel? body,
  }) {
    return HomeListState(
      status: status ?? this.status,
      body: body ?? this.body,
    );
  }
}

/// 事件
abstract class HomeListEvent {
  /// 发送请求
  static void send(
    BuildContext context,
    RefreshController refreshController, {
    int page = 0,
    int pageSize = 10,
  }) {
    context
        .read<HomeListBloc>()
        .add(HomeListSend(refreshController, page, pageSize));
  }
}

final class HomeListSend extends HomeListEvent {
  final RefreshController refreshController;
  final int page;
  final int pageSize;

  HomeListSend(this.refreshController, this.page, this.pageSize);
}

bloc.dart:具体业务逻辑

part 'contract.dart';

class HomeListBloc extends Bloc<HomeListEvent, HomeListState> {
  /// 网络请求
  final TestRepository _repository;

  HomeListBloc({required TestRepository repository})
      : _repository = repository,
        super(const HomeListState()) {
    on<HomeListSend>(_initHomeListSend);
  }

  void _initHomeListSend(
      HomeListSend event, Emitter<HomeListState> emit) async {
    // http请求
    final response = await _repository.getArticleList(
      event.page,
      event.pageSize,
    );
    // 判断当前请求结果
    switch (response) {
      case Success():
      	final body = response.success;
        if (event.page < body.pageCount) {
          _refreshSuccess(event);

          // 返回加载成功的数据
          emit(state.copyWith(
            status: HomeListStatus.success,
            body: body,
          ));
        } else {
          _refreshNoData(event);
        }
        break;
      case Error():
        final error = response.error;
        switch (error) {
          case NoData():
            // 关闭刷新
            _refreshNoData(event);
            break;
          case RequestFailure():
            // 返回成功
            _refreshSuccess(event);
            // 在下面使用中有对这个函数的定义,这个函数对应于具体的业务项目的业务逻辑处理
            MyHttpRepository.requestFailureError(
              error.code,
              error.msg,
              () => emit(
                state.copyWith(status: HomeListStatus.onAuthority),
              ),
            );
            break;
          default:
            _refreshFailed(event);
            break;
        }
    }
  }

  /// 下拉刷新成功
  void _refreshSuccess(HomeListSend event) {
    if (event.page <= 0) {
      // 关闭下拉刷新
      event.refreshController.refreshCompleted(resetFooterState: true);
    } else {
      // 关闭上拉
      event.refreshController.loadComplete();
    }
  }

  /// 没有数据
  void _refreshNoData(HomeListSend event) {
    if (event.page <= 0) {
      // 关闭下拉刷新
      event.refreshController.refreshCompleted(resetFooterState: true);
    } else {
      // 关闭上拉
      event.refreshController.loadNoData();
      // Future.delayed(const Duration(seconds: 1), () {
      //   // 延迟关闭加载项
      //   event.refreshController.loadComplete();
      // });
    }
  }

  // 数据异常,下拉异常提示
  void _refreshFailed(HomeListSend event) {
    if (event.page <= 0) {
      event.refreshController.refreshFailed();
    } else {
      event.refreshController.loadFailed();
    }
  }
}

view.dart:list布局,使用到了pull_to_refresh

import 'bloc.dart';

class HomeListView extends StatelessWidget {
  /// item 数据
  final List<HomeArticleDataItemModel> itemData = [];

  HomeListView({super.key});

  
  Widget build(BuildContext context) {
    return BlocBuilder<HomeListBloc, HomeListState>(builder: (context, state) {
      if (state.status == HomeListStatus.onAuthority) {
        // 跳转路由
      }
      if (state.status == HomeListStatus.success) {
        state.body?.let((body) {
          // 判断是否需要清除数据
          if (body.offset <= 0) {
            itemData.clear();
          }
          itemData.addAll(body.itemArray);
        });
      }
      return _bodyWidget();
    });
  }

  // list
  Widget _bodyWidget() => SliverList(
          delegate: SliverChildBuilderDelegate(childCount: itemData.length,
              (BuildContext context, int index) {
        final model = itemData[index];
        return _listItem(index, model);
      }));

  /// item 布局
  Widget _listItem(int index, HomeArticleDataItemModel model) {
    return GestureDetector(
      onTap: () {
        LogUtil.debug('$index >>> ${model.title}');
      },
      child: Card(
        margin: const EdgeInsets.fromLTRB(10, 5, 10, 10),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(10),
        ),
        child: Padding(
          padding: const EdgeInsets.all(10),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(model.title),
              const SizedBox(height: 15),
              Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
                Text(model.author == '' ? model.shareUser : model.author),
                Text(model.niceDate)
              ])
            ],
          ),
        ),
      ),
    );
  }
}

4. 具体使用布局, 放入main.dart中即可看到效果

view.dart:定义具体页面

import 'bloc/index.dart';

/// 首页测试页面2
class HomeTwoPage extends StatelessWidget {
  final _repository = TestRepository();

  HomeTwoPage({super.key});

  
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider(
          create: (BuildContext context) => BannerBloc(repository: _repository),
        ),
        BlocProvider(
          create: (BuildContext context) =>
              HomeListBloc(repository: _repository),
        ),
      ],
      child: const _HomePageView(),
    );
  }
}

class _HomePageView extends StatefulWidget {
  const _HomePageView();

  
  State<_HomePageView> createState() => _HomePageViewState();
}

class _HomePageViewState extends State<_HomePageView> {
  final RefreshController _refreshController =
      RefreshController(initialRefresh: true);
  int page = 1;

  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) => initAsyncState());
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
          title: const Text('测试首页'),
        ),
        body: _bodyWidget());
  }

  /// 页面内容
  Widget _bodyWidget() {
    return ColoredBox(
      color: Colors.white,
      child: _bodyRefresherWidget(),
    );
  }

  /// 下拉刷新控件
  Widget _bodyRefresherWidget() {
    return SmartRefresher(
      controller: _refreshController,
      //默认为true
      enablePullDown: true,
      //默认为false,不设置不支持上拉加载
      enablePullUp: true,
      //这个页面是瀑布流效果,可以更换
      header: const MaterialClassicHeader(),
      // footer: const ClassicFooter(),
      footer: CustomFooter(
        builder: (BuildContext context,LoadStatus? mode){
          Widget body;
          switch(mode) {
            case LoadStatus.idle:
              body = const Text('上拉加载');
              break;
            case LoadStatus.loading:
              body = const CupertinoActivityIndicator();
              break;
            case LoadStatus.failed:
              body = const Text("加载失败, 点击重试!");
              break;
            case LoadStatus.canLoading:
              body = const Text("松手,加载更多!");
              break;
            default:
              body = const Text("没有更多数据了!");
              break;
          }
          return SizedBox(
            height: 55.0,
            child: Center(child:body),
          );
        },
      ),
      onRefresh: onRefresh,
      onLoading: loadMore,
      child: _bodyListViewWidget(),
    );
  }

  /// 列表布局
  Widget _bodyListViewWidget() {
    return CustomScrollView(
      slivers: [
        const BannerViewWidget(),
        HomeListView(),
      ],
    );
  }

  void initAsyncState() {
    // 发送请求
    BannerEvent.send(context);
    HomeListEvent.send(context, _refreshController);
  }

  /// 下拉刷新
  void onRefresh() {
    page = 1;
    // 发送请求
    BannerEvent.send(context);
    HomeListEvent.send(context, _refreshController);
  }

  // 上拉加载
  void loadMore() {
    page++;
    HomeListEvent.send(context, _refreshController, page: page - 1);
  }
}

总结

关于bloc的使用后续会继续有延伸,本篇主要记录两点,一个是关于bloc多状态拆分细化,一个是下拉刷新控件搭配bloc的使用,其中也包含对 CustomScrollView的使用,注意:pull_to_refresh中SmartRefresher的child必须是Listview或CustomScrollView等,这个坑由于一开始使用bloc就没分开使用所以并没踩到但需要注意,同时本篇文章主要是将上一篇文章中对bloc的使用做了纠正,可以将多状态放在一个state中使用,但是没办法局部刷新子布局,控制粒度低,另外结合了官方示例,改变上一篇文章中书写state比较麻烦的情况,最开始的思路是将多个状态放到一个state文件中实现,通过sealed class来判断具体是哪个数据,但结果是最终只返回了列表数据,其实想想也能理解,毕竟只发起了一次事件不可能有两次结果,即便我响应了两次第一次结果也会被后面的覆盖,解决方案是放两个参数分别接收,但同时我考虑到这种方式不优雅,也不是一定需要两个状态同时判断,又将状态拆分成独立状态搭配子布局使用,通过这种方式也完美解决不够优雅的问题,也许最开始拆分思路是对的,但是方向错了,其实只需要将RefreshController 传递过来内部处理即可实现刷新控件无法正常收起的情况,如有更好的思路或不对的地方欢迎交流与指正。文章来源地址https://www.toymoban.com/news/detail-512312.html

到了这里,关于Flutter 使用bloc模拟首页实现下拉刷新的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Flutter Bloc和StreamController结合实现登录功能

    本篇博文通过Flutter Bloc自己提供的登录demo来分析Bloc的用法(源码点此),虽然只是个小小的登录demo,但是麻雀虽小,五脏俱全。通过该demo,你可以了解到: 1、Dart yield yield*的用法 2、Flutter StreamController的作用 3、StreamController和Bloc之间的通信(博主认

    2024年02月12日
    浏览(42)
  • Flutter 库:强大的下拉刷新上拉加载框架——EasyRefresh

    EasyRefresh 是一个用于 Flutter 应用程序的简单易用的 下拉刷新 和 上拉加载 框架。它支持几乎所有的 Flutter 可滚动小部件。它的功能与Android 的 SmartRefreshLayout 非常相似,并吸收了许多第三方库的优点。EasyRefresh 集成了各种样式的页眉和页脚,但没有任何限制,您可以轻松自定

    2024年01月19日
    浏览(39)
  • Flutter 之Bloc入门指南实现倒计时功能

    使用Bloc开发Flutter的项目,其项目结构都很简单明确,需要创建状态,创建事件,创建bloc,创建对应的View。flutter_timer项目来分析下Bloc的使用方法。 通过这篇博客,你

    2024年02月14日
    浏览(52)
  • uniapp使用自带【刷新方法】与使用【scroll-view】实现下拉刷新上拉加载

    前言:uniapp自带下拉刷新,上拉加载功能基本可以满足刷新需求,但是顶部有状态栏的页面就得进行特殊处理,使用scroll-view解决,状态栏会连带被下拉问题   1、uniapp自带下拉刷新、上拉加载 在page.json中对应页面路由设置【enablePullDownRefresh】值为true(开启下拉刷新) 代码:

    2024年02月11日
    浏览(47)
  • uniapp开发使用插件z-paging实现页面下拉刷新、上拉加载,分页加载

    在uniapp官网有一个比较好用的插件z-paging,可以实现多条数据滚动显示或者自定义下拉刷新,分页显示......在开发消息页面或者app开发需要大量的页面刷新,页面内容过长,减轻服务器的负担就可以使用此插件,进入app智慧加载部分内容,等到再次需要之后的内容就再次加载

    2024年02月11日
    浏览(52)
  • Flutter Bloc组件buildWhen的妙用

    在Flutter中当状态发生改变的时候,Widget会重新build刷新页面。但是当状态发生改变的时候后,我们指向让有关联的Widget重绘,与之无关的Widget保持不变,比如对于登录页面,有用户名和密码两个组件:如下图。 构建代码如下: 当我们输入用户名的时候,仅仅希望 _UserNameInp

    2024年02月12日
    浏览(44)
  • 小程序下拉刷新的实现

    1.使用onPullDownRefresh()这个方法来实现下拉刷新 例子如下:  注意事项:需要将要加载的函数在 onPullDownRefresh() 这个事件方法里面调用。 2.使用wx.stopPullDownRefresh()停止下拉刷新 使用方式需要在你页面调用接口的最后一个方法中执行,就可实现。 3.其中最重要的一步,最容易被忽

    2024年02月15日
    浏览(41)
  • 小程序如何实现下拉刷新

    要在小程序中实现下拉刷新功能,您可以使用小程序提供的页面生命周期函数和相关 API 来实现。下面是一般的步骤: 在页面的 JSON 配置文件中开启下拉刷新 在对应页面的 JavaScript 文件中,监听下拉刷新事件 onPulDownRefresh: 在onPullDownRefresh 函数中编写下拉刷新的逻辑,例如发

    2024年02月06日
    浏览(33)
  • 微信小程序实现下拉刷新

    (1)把\\\"backgroundTextStyle\\\":“light\\\"改为\\\"backgroundTextStyle”:“dark” (2)添加\\\"enablePullDownRefresh\\\":true,开启下拉刷新。 1.在app.js中增加两个生命周期函数 1、首先在页面的json文件中添加设置:  “enablePullDownRefresh”: true  2、在js文件中写一个onRefresh()生命周期: 3、在onPullDownRefresh

    2024年02月13日
    浏览(45)
  • Flutter之hydrated_bloc源码分析

    Flutter_Bloc是状态管理组件,hydrated_bloc是 Flutter_Bloc的扩展,它可以在APP重启的情况下,自动记录上次APP的状态。android中可以使用SharePreference来实现状态记录,在Flutter之hydrate_bloc组件入门指南一文中已经讲解了其基本用法,本篇博文就不对其原理进行简单分析,以计数器demo为

    2024年02月09日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包