基于GetX 搭建通用flutter 项目《二》(界面规范抽象类)

800 阅读9分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

基于GetX 搭建通用flutter 项目《一》

基于GetX 搭建通用flutter 项目《三》(暗黑模式)

基于GetX 搭建通用flutter 项目《四》(国际化)

基于GetX 搭建通用flutter 项目《五》(基于GetX 进行动态刷新)

最近由于一直在写小程序开发,也是没有太多的时间,就耽误了这个文章的更新,实在是不好意思,好了,废话不多说了,直接上主题

  • DEMO更新日志

    2022-07-14 完成国际化
    2022-06-22 完成暗黑模式功能开发
    2022-06-16 完成项目基础架构
    

您能在这里看到啥

  1. 抽象类
  2. 通用属性抽象类
  3. 通用界面抽象类
  4. 通用列表页抽象类
  5. 通用列表页刷新事件抽象类
  6. 网络事件抽象类
  7. 项目地址

这些抽象类,是在自己的项目中,不断试错,自己总结的一些让自己偷懒的方法,一直以来,我对自己的写代码的要求就是,能“偷懒“就“偷懒“,减少代码中重复且可以抽象的方法的出现。喜欢折腾,会把之前写过的东西,不断的换着不同的方式,方法,去重写,在不断的重写中,慢慢的积累,不断的提升自己的思维能力,让自己变得更“懒”。

送给自己一句话

慢就是快

抽象类介绍

通用属性抽象类

abstract class AbstractAttribute {
    /// 导航栏标题
    final String? title = null;
    /// 导航栏颜色
    final Color? navbackcolor = null;
    /// 默认安全区顶部 忽略
    final bool safeAreatop = true;
    /// 默认安全区底部 忽略
    final bool safeAreabottm = true;
    /// 网络加载状态
    final PageState pageState = PageState.initializedState;
    /// 背景颜色
    final Color? backgroundColor = null;
    /// 滚动属性
    final bool? resizeToAvoidBottomInset = null;
}

从代码里,我们会发现,这里面只有一个属性非系统自带的,那就是PageState,其实这是我自己定义的界面显示的状态值,这里面包含了网络请求的状态值,以及界面初始化,加载完成等等,往👇看

enum PageState {
    // 初始状态
    initializedState,
    // 错误状态,显示失败界面
    errorState,
    // 错误状态,只弹错误信息
    erroronlyTotal,
    // 错误状态,显示刷新按钮
    errorshowRelesh,
    // 没有更多数据
    noMoreDataState,
    // 空数据状态
    emptyDataState,
    // 数据获取成功状态
    dataFetchState,
}

相对来说,这是我一般项目中,界面的一般状态值,这是我自己的总结,不代表,只能这样写,这个是需要根据您的业务场景,自己去归纳总结,灵活使用就行,主要是要学会,把界面通用的事件,和状态,抽离出来。方便使用者使用,且要简单易懂。不能只能你自己知道,这样的抽离,是没有意义。万物归一,简单至上。具体这个属性和状态值的是使用,我会在基于GetX 搭建通用flutter 项目《五》,这边文章里,详细介绍。 当然,有了属性,必然会有使用的地方,下面我们就来看看这些属性,到底能做些什么呢,快看界面抽象类

通用界面抽象类

这里会相对来说多一些,请您慢慢看,上代码👇

// 普通界面 规范

abstract class AbstractWidget {
    /// 创建scaffoll
    Widget createScaffol({
        required BuildContext context,
        required bool safeAreatop,
        required bool safeAreabottm,
        required PageState pageState,
        required String? title,
        required Color? backgroundColor,
        required Color? navbackcolor,
        required bool? resizeToAvoidBottomInset,
    }) {
        return Scaffold(
            resizeToAvoidBottomInset: resizeToAvoidBottomInset,
            backgroundColor: backgroundColor,
            appBar: createAppBar(
                context,
                title: title,
                navbackcolor: navbackcolor,
            ),
            body: createSafeArea(
                context,
                safeAreatop,
                safeAreabottm,
                pageState,
            ),
        );
    }
    /// 创建safe
    Widget createSafeArea(
        BuildContext context,
        bool safeAreatop,
        bool safeAreabottm,
        PageState pageState,
    ) {
        return SafeArea(
            child: createCommBaseWidget(
                context,
                pageState,
            ),
            top: safeAreatop,
            bottom: safeAreabottm,
        );
    }
    /// 创建导航栏
    PreferredSize? createAppBar(
        BuildContext context, {
        Color? navbackcolor,
        String? title,
    }) {
        if (title != null) {
            return AppBarGenerator.getNoramlAppBar(
                context: context,
                title: title,
                backgroundColor: navbackcolor,
                actions: createAppBaractions(),
                leading: createAppBarleading(),
                textColor: createAppBarTextColor(),
                leadingIconColor: createLeadingIconColor(),
                leadingCallback: () {
                configleadingCallbak(context);
                },
                titlew: createAppBarTitleWidget()
            );
        }
        return null;
    }
    /// 创建AppBar titleWidget
    Widget? createAppBarTitleWidget() {
        return null;
    }
    /// 设置系统自带的返回按钮颜色
    Color? createLeadingIconColor() {
        return null;
    }
    /// 设置AppBar text 字体颜色
    Color? createAppBarTextColor() {
        return null;
    }
    /// 创建导航栏 右边按钮集合
    List<Widget>? createAppBaractions() {
        return null;
    }
    /// 重写返回按钮控件
    Widget? createAppBarleading() {
        return null;
    }
    /// 创建通用站位界面
    Widget createCommBaseWidget(
        BuildContext context,
        PageState pageState,
    ) {
        /// CommonBasePage 是我自己定义的通用界面暂位页。
        return CommonBasePage(
            pageState: pageState,
            child: createColumWidget(context),
            errorWidget: createErrorWidget(),
        );
    }
    /// 创建界面Colum
    Widget createColumWidget(BuildContext context) {
        return Column(
            children: [
                createHeaderWidget(),
                Expanded(
                    child: createBody(context),
                ),
            ],
        );
    }
    /// 创建头部
    Widget createHeaderWidget() {
        return Container();
    }
    /// 创建真实body
    @protected
    Widget createBody(BuildContext context);
    /// 创建失败 界面
    Widget? createErrorWidget() {
        return null;
    }
    /// 点击通用返回按钮点击事件
    configleadingCallbak(BuildContext context) {
        configgoback(context);
    }
    /// 执行返回界面
    configgoback(BuildContext context) {
        Navigator.of(context).pop();
    }

}

  • Widget createScaffol() 创建界面入口方法

    下面是函数需要的参数

    /// 上下文,这就不再讲了
    required BuildContext context,
    /// 是否关闭顶部安全区域,这是当你需要沉浸式,和顶部置顶的时候,需要设置
    required bool safeAreatop,
    /// 是否关闭底部安全取悦,一般我都用于列表类界面。
    required bool safeAreabottm,
    /// 界面状态值
    required PageState pageState,
    /// 导航栏标题。我这边做的处理是,当title == null 就没有导航栏
    required String? title,
    /// 背景颜色
    required Color? backgroundColor,
    /// 导航栏背景颜色
    required Color? navbackcolor,
    /// 键盘启动 界面弹性 配置
    required bool? resizeToAvoidBottomInset,
  • PreferredSize? createAppBar() 创建导航栏 从👆的代码里,我们很快就找到了创建导航栏 的方法,为了方便在使用的时候,更好的自定义导航栏,我在自己的使用过程中,增加了几个可以自定义导航栏的方法,👇,当然,如果你不喜欢我定义的,您也可以直接重写这个方法。来到达您的预期。

     /// 自定义默认返回Icon 的颜色,我这里默认用的 Icons.arrow_back_ios
     - Color? createLeadingIconColor()
     /// 自定义导航栏字体颜色
     - Color? createAppBarTextColor()
     /// 创建导航栏👉按钮集合
     - List<Widget>? createAppBaractions()
     /// 重写返回按钮控价
     Widget? createAppBarleading()
    

    其实这些方法,也是可以在通用属性抽象类 添加属性,由于这里为了体现两种替换方式,我这边,把只属于appBar的部分自定义属性,以重写方法的形式,来达到替换默认属性的效果,当然在重写通用属性抽象类中的属性的时候,我推荐👇的方式来重写

    
     /// 第一种方式,直接重写get 方法,返回默认值
     @override
     Color? get backgroundColor => Colors.white;
     /// 第二种方式,也是重写get 方法,但获取的可能是动态变更的,两种方式
     /// 都是一样效果,不过是不是觉得第二种。和上面的定义抽象接口,再重写,
     /// 其实是一样的
     /// 不管哪种方式,都只是一种方式,最终是我们形成体系,大家都按照我们规定好的方式
     /// 来进行开发。节省沟通成本
     @override
     Color? get navbackcolor => configbackColor();
     Color? configbackColor() {
         return null;
     }
    
  • Widget createSafeArea() 创建安全区域body 这里面主要做了一件事,就是我们可视界面都被SafeArea 包裹着,然后我们的child的载体就是我们封装的展位界面,这个暂位界面会根据,你设定的界面状态值pagestate 进行了逻辑处理,到底界面是显示我们的空数据界面网络请求失败,还是业务界面

/// 创建通用站位界面
/// 这里面就需要您来传递,界面的状态值,
/// 有了状态值,站位界面,才能根据事先约定好的状态,来完成界面显示
Widget createCommBaseWidget(
    BuildContext context,
    /// 界面状态,可以查看👆
    /// 我的状态值的定义
    PageState pageState,
) {
    /// 这里就是我封装的 站位界面的 工具
    return CommonBasePage(
        pageState: pageState,
        child: createColumWidget(context),
        /// 为了方便扩展,我在这里对外公开了一个方法
        /// 用于创建你自己的失败界面.
        errorWidget: createErrorWidget(),
    );
}
  • Widget createColumWidget(BuildContext context) 创建界面分成
/// 创建界面Colum
Widget createColumWidget(BuildContext context) {
    return Column(
        children: [
            createHeaderWidget(),
            Expanded(
                child: createBody(context),
            ),
        ],
    );
}

我这里用的是column,因为大部分界面可以分为

    /// header 可以通过👆的方法,自定header
    /// 一般这个用的不多
    header (头部)
    /// 这里才是我们真正内容的显示
    /// 在这里,我一般是需要使用者,必须实现这个方法的
    body(内容)

好了到这里,这个通用的界面抽象类暂时完成了,由于我们的项目开发中还有有很多组合,我这里也把通用列表,弄了一个简单抽象类请看👇

通用列表抽象类

/// 刷新界面 规范
abstract class AbstracRefreshWidget {
    /// 创建刷新控件
    Widget createRefreshWidget(BuildContext context);
    /// 创建列表
    Widget createListView(BuildContext context);
    /// 创建列表 item
    Widget createListitem(BuildContext context, int index);
    /// 创建缺省页界面
    Widget? createEmptyWidget();
}

相对来说,这里只是抽象了界面的方法,并没有包含事件的方法,我这么做的目的,是为了让我们的抽象类, 更纯粹一些,界面就是界面,事件就是事件.也方便使用MVC.或者MVVM,既然有了界面抽象类,自然也少不了 我们的方法抽象类,请往下看

通用列表刷新事件抽象类

/// 刷新界面 触发方法

abstract class AbstracRefreshMehod {
    int page = 1;
    /// 结束刷新
    void endRefresh(int type, PageState pageState);
    /// 下啦刷新 触发事件
    void configRefresh();
    /// 上啦加载 触发事件
    void configLoading();
}

这里面的代码就相对简单了,就不再啰嗦了,当然下面的网络,我也就不再介绍了,相对来说比较简单

网络事件抽象类

// 配置网络请求规范

abstract class AbstractNetWork {
    @protected
    /// 通用网络参数
    Map<String, dynamic>? configNetWorkParmas({int? type});
    /// 网络请求
    @protected
    void getnetworkdata(int? type, Map<String, dynamic>? info);
}

具体使用事例

state 使用事例

第一步,创建适用你项目的抽象类.主要就是把我们上面定义的抽象类,进行一个组合.

StatefulWidget的抽象类

/// 其实这个到没有太有必要做成抽象类.
abstract class NormalStatefulWidget extends StatefulWidget {
    const TTNormalStatefulWidget({Key? key}) : super(key: key);
    @override
    // ignore: no_logic_in_create_state
    TTNormalState createState() => getState();
    TTNormalState getState();
}


State 抽象类


abstract class NormalState<T extends StatefulWidget> extends State<T>
with AbstractNetWork, AbstractWidget, AbstractAttribute {
/// 生命周期
///
///

/// 界面初始化完成
@override
void initState() {
    initDefaultState();
    super.initState();
}
/// 界面构建视图入口
@override
Widget build(BuildContext context) {
    return createScaffol(
    context: context,
    safeAreatop: safeAreatop,
    safeAreabottm: safeAreabottm,
    pageState: pageState,
    resizeToAvoidBottomInset: resizeToAvoidBottomInset,
    title: title,
    backgroundColor: backgroundColor,
    navbackcolor: navbackcolor,
    );
}
@override
void dispose() {
    initDefaultDispose();
    super.dispose();
}
@override
Widget createColumWidget(BuildContext context) {
    return Container(
    alignment: Alignment.center,
    child: LayoutBuilder(
    builder: (context, constraints) {
    configlayoutbuiderConstraints(constraints);
    return SizedBox(
    width: configSizeBoxWidth(constraints),
        child: createonlyColumWidget(context));
    },
    ),
    );
}
/// getx 真实包裹的colum 方法

Widget createonlyColumWidget(BuildContext context) {
    return Column(
        children: [
            createHeaderWidget(),
            Expanded(
                child: createBody(context),
            ),
        ],
    );
}
/// 网络请求
///
///
@override
void getnetworkdata(int? type, Map<String, dynamic>? info) {}
/// 创建视图
///
///

/// 触发方法
///
///
@override
Map<String, dynamic>? configNetWorkParmas() {
    return null;
}
/// 界面进入
void initDefaultState() {}
/// 界面销毁
void initDefaultDispose() {}
/// 通用刷新state
void configsetState(VoidCallback fn) {
    if (mounted) {
        setState(() {
            fn();
         });
    }
}
/// 获取 屏幕 最大尺寸

configlayoutbuiderConstraints(BoxConstraints constraints) {}
double? configSizeBoxWidth(BoxConstraints constraints) {
    return HzyNormalUtils.configSizeMaxW(constraints.maxWidth);
}
}

好了,今天就写到这里了,喜欢的可以点个赞 对应的基于GetX 封装的将会在 基于GetX 搭建通用flutter 项目《五》(基于GetX 进行动态刷新)这个文章里讲解

hzy_normal_widget 是我在使用GetX搭建项目时,总结的一些通用开发控件,方便我们在开发的时候,减少重复性界面代码的创建.

ttcomment 通用项目的界面接口基类,和一些通用工具类,喜欢的可以点点star.

当然接下的时间里我也会总结OCswift 相应的通用项目文章,喜欢的可以点点关注