美文网首页
Flutter之基类逻辑 2025-04-21 周一

Flutter之基类逻辑 2025-04-21 周一

作者: 松哥888 | 来源:发表于2025-04-22 21:01 被阅读0次

简介

  • GetX的最大价值是简化了页面导航的写法,去掉了耦合的context,使用起来方便很多。

  • 其次,GetBuilder和update()的组合,可以将页面和逻辑分开,这也在一定程度上简化了业务。

  • BasePage基于GetxController,复用的内容还是太少,所以考虑增加一个BaseLogic与之对应,进一步增加基类内容,增加代码复用。

上拉下拉

  • 将下拉刷新插件改为pull_to_refresh之后,代码简化了不少。

  • 如果想基类中整合下拉刷新组件,那么BaseLogic可以继承自BaseRefreshLogic,因为后者就是继承自GetxController

  • 对于不需要下拉刷新功能的页面,只要不激活这部分代码,不展示这部分页面就可以了。基类中多一些冗余代码,问题不大。

页面分类

  • 根据工程中出现过的页面进行分类,划分页面类型;

  • 把一些共性代码放到基类中,增加复用;

  • 根据页面类型参数PageType pageType,调用不同的_buildXXX方法

  • 将一些自定义的部分,一占位函数的方法buildXXX()供子类重写

  • 最外层套一个手势,加上一个去焦点收键盘的方法

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      behavior: HitTestBehavior.opaque,
      onTap: () {
        FocusScope.of(context).requestFocus(FocusNode());
      },
      child: GetBuilder<T>(
        tag: tag,
        builder: (_) {
          switch (pageType) {
            case PageType.scaffold:
              return _buildScaffold();
            case PageType.custom:
              return _buildCustom();
            case PageType.gradientNavigationBar:
              return _buildGradientNavigationBar();
            case PageType.refresh:
              return _buildRefresh();
          }
        },
      ),
    );
  }

1. 普通页面

  • 就是通常所说的Scaffold页面,当然实际使用时,会在Scaffold外面套一层,提供一个XXScaffold的组件,将一些背景色之类的统一写了。
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        FocusScope.of(Get.context!).requestFocus(FocusNode());
      },
      child: RepaintBoundary(
        child: Scaffold(
          backgroundColor: widget.backgroundColor ?? ColorUtils.c_f8f8f8,
          // backgroundColor: Colors.white,
          appBar: AppBar(
            flexibleSpace: Container(
              color: widget.flexibleSpaceColor ?? ColorUtils.c_ffffff,
            ),
            centerTitle: true,
            elevation: 0,
            backgroundColor: widget.backgroundColor ?? ColorUtils.c_ffffff,
            // backgroundColor: Colors.white,
            title: widget.title != null ? Text(widget.title!, style: StyleUtils.ts_1f_17_600) : widget.titleWidget,
            iconTheme: IconThemeData(color: ColorUtils.c_1f1f1f),
            leading: widget.leading,
            leadingWidth: widget.leadingWidth,
            actions: widget.actions,
            bottom: widget.bottom,
          ),
          body: widget.dataReady ? widget.body : buildEmptyWidget(),
          floatingActionButton: widget.floatingActionButton,
          floatingActionButtonLocation: widget.floatingActionButtonLocation,
        ),
      ),
    );
  }
  • 这是用得最多的页面,大多数时候只要改一下title和body就可以了。少部分时候,action和bottom也会用到。至于leading和floatingButton之类的用的时候很少,还不如直接隐藏掉。
abstract class BasePage<T extends BaseLogic> extends StatelessWidget {
  const BasePage({super.key, this.tag});

  final String? tag;

  T get logic => GetInstance().find<T>(tag: tag);

  @override
  Widget build(BuildContext context) {
    return GetBuilder<T>(
      tag: tag,
      builder: (_) {
        return _buildScaffold();
      },
    );
  }

  /// 重写这个切换页面类型
  final PageType pageType = PageType.scaffold;

  Widget _buildScaffold() {
    return GBScaffold(
      title: title,
      body: buildBody(),
      actions: buildActions(),
      bottom: buildBottom(),
    );
  }

  final String title = "";

  Widget buildBody() {
    return Container();
  }

  List<Widget>? buildActions() {
    return null;
  }

  PreferredSizeWidget? buildBottom() {
    return null;
  }
}

enum PageType {
  scaffold,
  custom,
}
Scaffold页面
  • 使用的时候,只需要重写title,body等变量和方法就可以了。
class CartPage extends BasePage<CartLogic> {
  CartPage({super.key, super.tag}) {
    Get.put(CartLogic(), tag: tag);
  }

  @override
  String get title => "tab_cart".tr;

  @override
  Widget buildBody() {
    return Column(
      children: [
        messageRow(),
        logic.dataList.isNotEmpty
            ? Expanded(child: listWidget())
            : Expanded(
            child: Kong(
              img: R.assetsImgKongNocontent,
              title: logic.dataReady ? 'cart_empty_tip'.tr : "",
            )),
        operatorWidget(),
      ],
    );
  }

  @override
  PreferredSizeWidget? buildBottom() {
    return CartActionWidget(
      onEditClick: logic.onEditClick,
      onCouponClick: logic.onCouponClick,
      number: logic.productNumber,
      onFinishClick: logic.onFinishClick,
      showEdit: logic.showEdit,
    );
  }

2. 自定义页面

  • leading和floatingButton这些虽然用的不多,但是有时候也需要用到的

  • 这里干脆就不限制,基类只提供一个空白函数,让使用者完全自定义。

abstract class BasePage<T extends BaseLogic> extends StatelessWidget {
  const BasePage({super.key, this.tag});

  final String? tag;

  T get logic => GetInstance().find<T>(tag: tag);

  @override
  Widget build(BuildContext context) {
    return GetBuilder<T>(
      tag: tag,
      builder: (_) {
        switch (pageType) {
          case PageType.scaffold:
            return _buildScaffold();
          case PageType.custom:
            return buildCustom();
        }
      },
    );
  }

  /// 重写这个切换页面类型
  final PageType pageType = PageType.scaffold;

  /// 自定义页面
  Widget buildCustom() {
    return Container();
  }
}

enum PageType {
  scaffold,
  custom,
}

3. 渐变导航栏

  • 默认是透明的导航栏,随着上划,导航栏变成不透明的灰色。


    透明导航栏
灰色导航栏
  • SliverAppBar感觉很接近这种效果,但是实践下来总感觉力不从心,效果总是差那么一点。

  • 所以准备采用stack,分成背景,内容,头部三层视图,并且通过滑动监听来达到效果。

  /// 渐变导航栏
  Widget _buildGradientNavigationBar() {
    return Scaffold(
      body: Stack(
        children: [
          buildBackground(),
          buildContent(),
          buildNavigationBar(),
        ],
      ),
    );
  }

  /// 第1层:背景
  Widget buildBackground() {
    return Image.asset(
      backgroundImageName,
      width: ScreenUtil().screenWidth,
      fit: BoxFit.fitWidth,
    );
  }

  /// 第2层内容
  Widget buildContent() {
    return NotificationListener(
      onNotification: (notification) {
        if (notification is ScrollUpdateNotification) {
          /// 监听滚动位置,导航栏渐变
          double scrollPosition = notification.metrics.pixels;
          logic.onScrollChange(scrollPosition);
        }
        return true;
      },
      child: CustomScrollView(
        slivers: buildSlivers(),
      ),
    );
  }



  /// 第3层:背景色渐变的导航栏
  Widget buildNavigationBar() {
    return Container(
      color: ColorUtils.c_f8f8f8.withValues(alpha: logic.alpha),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          SizedBox(height: GBUtils.getTopSafeHeight()),
          SizedBox(
            height: 44.r,
            child: Stack(
              children: [
                Container(
                  padding: EdgeInsets.symmetric(horizontal: 40.r),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text(
                        title,
                        style: StyleUtils.ts_1f_17_600,
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                      ),
                    ],
                  ),
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    GestureDetector(
                      behavior: HitTestBehavior.opaque,
                      onTap: () {
                        Get.back();
                      },
                      child: Row(
                        children: [
                          SizedBox(width: 22.r),
                          Icon(
                            Icons.arrow_back_ios,
                            size: 22.r,
                            color: ColorUtils.c_000000,
                          ),
                          SizedBox(width: 22.r),
                        ],
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

4. 下拉刷新的滑动视图

  • 下拉刷新包一个SmartRefresh插件

  • 滑动组件固定使用CustomScrollView,只要重写buildSlivers()就好了。

  • 空视图,网络错误视图也给出默认实现,也可以重写

  /// 刷新视图
  Widget _buildRefresh() {
    return GBScaffold(
      title: title,
      actions: buildActions(),
      bottom: buildBottom(),
      body: Container(
        margin: margin,
        padding: padding,
        child: RefreshWidget(
          controller: logic.refreshController,
          onRefresh: logic.onRefresh,
          onLoad: logic.onLoad,
          isEmpty: logic.isEmpty,
          emptyWidget: buildEmpty(),
          isNetworkError: logic.isNetworkError,
          networkErrorWidget: buildNetworkError(),
          slivers: buildSlivers(),
        ),
      ),
    );
  }

  /// sliver类型的数组
  List<Widget> buildSlivers() {
    return [];
  }

  /// 空视图
  Widget? buildEmpty() {
    return null;
  }

  /// 网络错误
  Widget? buildNetworkError() {
    return null;
  }

5. 普通滑动视图

  • 如果没有超过一屏,不能滑动

  • 如果超出一屏,可以滑动

  • 固定采用SingleChildScrollView,内部套一个Column,只要重写buildChildren()就可以了。

  /// 普通滑动:超出一屏滑动,否则不滑动
  Widget _buildSingleScroll() {
    return GBScaffold(
      title: title,
      actions: buildActions(),
      bottom: buildBottom(),
      body: Container(
        margin: margin,
        padding: padding,
        child: SingleChildScrollView(
          child: Column(
            children: buildChildren(),
          ),
        ),
      ),
    );
  }

  /// children数组
  List<Widget> buildChildren() {
    return [];
  }

6. 嵌套滑动

  • 顶部部分可以收缩的,能省空间


    展开状态
  • 收缩状态
  • 固定采用NestedScrollView;头部可收缩部分重写buildSlivers();内容部分重写buildBody()

枚举类型

  • 采用枚举类型区分各种页面模板
enum PageType {
  scaffold,
  custom,
  gradientNavigationBar,
  refresh,
  singleScroll,
  nestedScroll,
}
  • 使用一个变量确定类型
  /// 重写这个切换页面类型
  final PageType pageType = PageType.scaffold;
  • 根据类型调用不同的build函数,外面套一个收键盘的手势,方便使用
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      behavior: HitTestBehavior.opaque,
      onTap: () {
        FocusScope.of(context).requestFocus(FocusNode());
      },
      child: GetBuilder<T>(
        tag: tag,
        builder: (_) {
          switch (pageType) {
            case PageType.scaffold:
              return _buildScaffold();
            case PageType.custom:
              return buildCustom();
            case PageType.gradientNavigationBar:
              return _buildGradientNavigationBar();
            case PageType.refresh:
              return _buildRefresh();
            case PageType.singleScroll:
              return _buildSingleScroll();
            case PageType.nestedScroll:
              return _buildNestedScroll();
          }
        },
      ),
    );
  }

相关文章

  • flutter-基类

    1.BaseStatelessWidget 用法:代码块 2.BaseStatefulWidget 用法:代码块

  • iOS面试题:假如Controller太臃肿,如何优化?

    原文:iOS面试题大全 1.将网络请求抽象到单独的类中方便在基类中处理公共逻辑;方便在基类中处理缓存逻辑,以及其它...

  • C++ - 继承与复合

    继承:“是” 关系基类 A,B 是基类 A 的派生类逻辑上要求:“一个 B 对象也是一个 A 对象”。 复合:“有...

  • YHM-iOS面试题

    1、假如Controller太臃肿,如何优化1.将网络请求抽象到单独的类中方便在基类中处理公共逻辑;方便在基类中处...

  • Flutter学习记录-汇总

    Flutter学习记录-布局Flutter学习记录-页面跳转Flutter学习记录-交互Flutter学习记录-基...

  • python之抽象基类

    python之抽象基类 抽象基类,在这个类中定义一些方法,所有继承这个类的类必须实现这个方法,并且这个类不能被实例...

  • 你是个低贴现率的人吗

    假设你重新回到选专业那会,你会选哪类? A类:逻辑学、哲学、数学……B类:心理学、软件工程、市场营销…… A类是基...

  • Flutter 与 iOS 原生项目混编配置(二)

    用户的关注列表 主要涉及基类封装、Native与Flutter间相互通信、网络数据请求、列表展示、上下拉刷新,以及...

  • Flutter(八)--Flutter渲染逻辑+源码解读

    Flutter渲染逻辑+源码浅显解读 前言 flutter渲染引擎-flutter.framework,而真正的渲...

  • java中基类指针只向派生类对象的四种方法

    总结: 1.基类指针指向基类对象:简单,只需通过基类指针简单的调用基类的功能。 2.派生类指针只想基类对象:同上。...

网友评论

      本文标题:Flutter之基类逻辑 2025-04-21 周一

      本文链接:https://www.haomeiwen.com/subject/aazmbjtx.html