欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

Flutter控件之实现Widget基类的封装,

来源: 开发者 投稿于  被查看 7889 次 评论:146

Flutter控件之实现Widget基类的封装,


目录
  • 一、需要封装哪些属性
  • 二、确定基类Widget
    • InkWell
    • GestureDetector
    • InkResponse
    • 原始指针事件
  • 三、基类实现
    • 具体使用
  • 四、相关总结

    在短时间的接触Flutter之后,有一个问题一直摆在了明面上,那就是,Flutter中的Widget确实没有Android中的控件好用,在Android中,比如TextView,ImageView等等或者其他View,都有着自己非常广泛的属性和方法,比如宽,高,margin和padding,以及相关的点击事件,这在Flutter,对应的控件中,却少了这些基础又常用的属性,以至于每写一个Widget,如果想要实现点击事件,或者margin,padding,不得不用其他的Widget包裹一层,使用起来很是不方便,基于以上的背景,便萌生了一个封装基类的想法。

    虽然之前接触过Flutter,但也是许久不用了,今再拾起,难免有些许不足,如果在封装上有哪些问题,还望不吝赐教。

    一、需要封装哪些属性

    具体需要哪些属性,不是越多越好,也不是越少越好,而是基于实际的开发需求,拓展出常用的即可。

    一个文本或者图片控件又或者是其他控件,在实际的开发中,哪些是我们需要考虑的?是不是最常见的就是自身的宽高,这是最常见且必须需要的,除了宽高,其自身的点击事件,也是频次居高不下的一个属性,所以,在基类Widget中,其宽、高、点击事件是必须要存在的,说到事件,除了点击事件之外,一些需求中的双击或者长按事件也是存在的,所以,尽量也封到基类中,便于子类控件的使用。

    除此之外,像外边距、内边距、也是必不可少的属性,不敢说十个控件有九个用到,起码说也得一半以上的概率,所以,这也是要封装到基类中的;至于背景属性,比如圆角的,圆形的,空心的,实心的,这些看实际的项目使用,如果需要,也可以放到基类中。

    初步罗列了一下,大致封装的属性如下,当然了,每个人的封装都有不同,主要还是要看实际的需求。

    属性类型概述
    widthdouble
    heightdouble
    margindouble外边距统一设置(左上右下)
    marginLeftdouble外边距(左)
    marginTopdouble外边距(上)
    marginRightdouble外边距(右)
    marginBottomdouble外边距(下)
    paddingdouble内边距统一设置
    paddingLeftdouble内边距(左)
    paddingTopdouble内边距(上)
    paddingRightdouble内边距(右)
    paddingBottomdouble内边距(下)
    onClick方法点击事件
    onDoubleClick方法双击事件
    onLongPress方法长按事件
    backgroundColorColor背景颜色 和 decoration 二者取其一
    strokeWidthdouble背景边框统一的宽度
    strokeColorColor背景边框的颜色
    solidColorColor背景填充颜色
    radiusdouble背景的角度,统一设置
    leftTopRadiusdouble背景左上角度
    rightTopRadiusdouble背景右上角度
    leftBottomRadiusdouble背景左下角度
    rightBottomRadiusdouble背景右下角度
    isCirclebool背景是否是圆形
    childWidgetWidget传递的子控件
    alignmentAlignment位置
    gradientColorListList渐变颜色集合
    gradientColorStopsList渐变颜色值梯度,取值范围[0,1]
    gradientBeginAlignment渐变起始位置
    gradientEndAlignment渐变结束位置

    二、确定基类Widget

    基类的Widget主要确定以下几个方面,第一就是,自定义一个抽象类还是非抽象类,第二、继承方式,采取有状态还是无状态,第三、关于组件的点击方式,如何进行实现。

    一开始自己写的是一个抽象基类,毕竟在接下来的操作中,对于各个控件,我都会重新在原生的基础之上进行再次的封装,而不是独立的使用,这种情况下,抽象类是最合适的,向子类拓展出必须要实现的方法即可,但是这种情况下就有一个弊端,那就是,原生的控件无法享有这个基类的各个属性,没办法,最后又改为了非抽象类,这样,两种方式均可满足。

    关于继承方式,对于一个页面而言,或多或少都是需要渲染数据,更新UI的,这种情况下继承StatefulWidget是肯定的,但是一般一个控件,都是别人来触发它,而它自己很少主动触发,所以,一般而言,我们继承StatelessWidget即可。

    关于组件的点击方式,如果是非Button级别的,很少有控件自带点击事件,所以我们不得不自行实现,而在Flutter中提供了很多可以协助实现点击的组件,比如InkWell,GestureDetector,InkResponse,原始指针事件Listener,都为我们提供了丰富的触摸事件,下面简单的列举一下:

    InkWell

    InkWell(
          onLongPress: (){
            print("长按事件");
          },
          onDoubleTap: (){
            print("双击事件");
          },
          onTap: (){
            print("点击事件");
          }
          child: Container()
    )

    GestureDetector

    return GestureDetector(
          child: const Text("首页"),
      		onLongPress: (){
            print("长按事件");
          },
          onDoubleTap: (){
            print("双击事件");
          },
          onTap: (){
            print("点击事件");
          },
          onPanDown: (DragDownDetails detail) {
            // 手指按下的相对于屏幕的位置
            print("手指按下回调");
          },
          onPanUpdate: (DragUpdateDetails detail) {
            print("手指滑动回调");
          },
          onPanEnd: (DragEndDetails detail) {
            print("手指停止滑动回调");
          },
      		// 垂直方向拖动事件
          onVerticalDragUpdate: (DragUpdateDetails details) {
          },
          // 水平方向拖动事件
          onHorizontalDragUpdate: (DragUpdateDetails details) {
          },
        );

    InkResponse

    return InkResponse(
          child: const Text("点击"),
          onTap: () {
            //点击事件
            print("点击事件");
          },
          onLongPress: () {
            //长按事件
            print("长按事件");
          },
          onDoubleTap: () {
            //双击事件
            print("双击事件");
          },
        );

    原始指针事件

    return Listener(
          child: Container(
            child: const Text("测试"),
          ),
      		//手指按下回调
          onPointerDown: (PointerDownEvent event) {},
      		//手指移动回调
          onPointerMove: (PointerMoveEvent event) {},
      		//手指抬起回调
          onPointerUp: (PointerUpEvent event) {},
      		//触摸事件取消回调
          onPointerCancel: (PointerCancelEvent event) {},
        );

    相关的属性有很多,大家可以看下相关源码,具体用哪个,我是认为,前三个都可以,毕竟都有相关的点击,双击,长按事件,如果你想要获取更多的触摸事件,那么就可以使用GestureDetector,如果只是点击,长按和双击,比较推荐InkWell,相对点击比较灵敏,当然了,具体使用哪个,还是要看自己。

    三、基类实现

    基类实现就比较的简单了,build方法中最外层用点击事件包裹,再往下用Container组件来包裹,目的用于宽高,margin,padding和背景等实现,圆角和圆形以及渐变用的是Container的属性decoration。

    全部的源码如下,都是系统的api调用,没有特别难的。

    import 'package:flutter/material.dart';
    ///AUTHOR:AbnerMing
    ///DATE:2023/5/11
    ///INTRODUCE:控件无状态基类
    class BaseWidget extends StatelessWidget {
      final VoidCallback? onClick; //点击事件
      final VoidCallback? onDoubleClick; //双击事件
      final VoidCallback? onLongPress; //长按事件
      final double? width; //宽度
      final double? height; //高度
      final double? margin; //外边距,左上右下
      final double? marginLeft; //外边距,距离左边
      final double? marginTop; //外边距,距离上边
      final double? marginRight; //外边距,距离右边
      final double? marginBottom; //外边距,距离下边
      final double? padding; //内边距,左上右下
      final double? paddingLeft; //内边距,距离左边
      final double? paddingTop; //内边距,距离上边
      final double? paddingRight; //内边距,距离右边
      final double? paddingBottom; //内边距,距离下边
      final Color? backgroundColor; //背景颜色 和 decoration 二者取其一
      final double? strokeWidth; //背景边框统一的宽度
      final Color? strokeColor; //背景边框的颜色
      final Color? solidColor; //背景填充颜色
      final double? radius; //背景的角度
      final bool? isCircle; //背景是否是圆形
      final double? leftTopRadius; //背景左上角度
      final double? rightTopRadius; //背景 右上角度
      final double? leftBottomRadius; //背景 左下角度
      final double? rightBottomRadius; //背景 右下角度
      final Widget? childWidget; //子控件
      final Alignment? alignment; //位置
      final int? gradient; //渐变方式,为支持后续拓展,用int类型
      final List<Color>? gradientColorList; //渐变颜色
      final List<double>? gradientColorStops; //颜色值梯度,取值范围[0,1]
      final Alignment? gradientBegin; //渐变起始位置
      final Alignment? gradientEnd; //渐变结束位置
      //边框的颜色
      const BaseWidget(
          {super.key,
          this.width,
          this.height,
          this.margin,
          this.marginLeft,
          this.marginTop,
          this.marginRight,
          this.marginBottom,
          this.padding,
          this.paddingLeft,
          this.paddingTop,
          this.paddingRight,
          this.paddingBottom,
          this.backgroundColor,
          this.strokeWidth,
          this.strokeColor,
          this.solidColor,
          this.radius,
          this.isCircle,
          this.leftTopRadius,
          this.rightTopRadius,
          this.leftBottomRadius,
          this.rightBottomRadius,
          this.childWidget,
          this.alignment,
          this.gradient,
          this.gradientColorList,
          this.gradientColorStops,
          this.gradientBegin,
          this.gradientEnd,
          this.onClick,
          this.onDoubleClick,
          this.onLongPress});
      @override
      Widget build(BuildContext context) {
        return InkWell(
            highlightColor: Colors.transparent,
            // 透明色
            splashColor: Colors.transparent,
            // 透明色
            onTap: onClick,
            onDoubleTap: onDoubleClick,
            onLongPress: onLongPress,
            child: Container(
              width: width,
              height: height,
              alignment: alignment,
              margin: margin != null
                  ? EdgeInsets.all(margin!)
                  : EdgeInsets.only(
                      left: marginLeft != null ? marginLeft! : 0,
                      top: marginTop != null ? marginTop! : 0,
                      right: marginRight != null ? marginRight! : 0,
                      bottom: marginBottom != null ? marginBottom! : 0),
              padding: padding != null
                  ? EdgeInsets.all(padding!)
                  : EdgeInsets.only(
                      left: paddingLeft != null ? paddingLeft! : 0,
                      top: paddingTop != null ? paddingTop! : 0,
                      right: paddingRight != null ? paddingRight! : 0,
                      bottom: paddingBottom != null ? paddingBottom! : 0,
                    ),
              color: backgroundColor,
              decoration: backgroundColor != null ? null : getDecoration(),
              child: childWidget ?? getWidget(context),
            ));
      }
      /*
      * 获取Decoration
      * */
      Decoration? getDecoration() {
        BorderRadiusGeometry? borderRadiusGeometry;
        if (radius != null) {
          //所有的角度
          borderRadiusGeometry = BorderRadius.all(Radius.circular(radius!));
        } else {
          //否则就是,各个角度
          borderRadiusGeometry = BorderRadius.only(
              topLeft: Radius.circular(leftTopRadius != null ? leftTopRadius! : 0),
              topRight:
                  Radius.circular(rightTopRadius != null ? rightTopRadius! : 0),
              bottomLeft:
                  Radius.circular(leftBottomRadius != null ? leftBottomRadius! : 0),
              bottomRight: Radius.circular(
                  rightBottomRadius != null ? rightBottomRadius! : 0));
        }
        Gradient? tGradient;
        if (gradient != null) {
          tGradient = LinearGradient(
            colors: gradientColorList != null ? gradientColorList! : [],
            // 设置有哪些渐变色
            begin: gradientBegin != null ? gradientBegin! : Alignment.centerLeft,
            // 渐变色开始的位置,默认 centerLeft
            end: gradientEnd != null ? gradientEnd! : Alignment.centerRight,
            // 渐变色结束的位置,默认 centerRight
            stops: gradientColorStops, // 颜色值梯度,取值范围[0,1],长度要和 colors 的长度一样
          );
        }
        Decoration? widgetDecoration = BoxDecoration(
          gradient: tGradient,
          //背景颜色
          color: solidColor != null ? solidColor! : Colors.transparent,
          //圆角半径
          borderRadius: isCircle == true ? null : borderRadiusGeometry,
          //是否是圆形
          shape: isCircle == true ? BoxShape.circle : BoxShape.rectangle,
          //边框线宽、颜色
          border: Border.all(
              width: strokeWidth != null ? strokeWidth! : 0,
              color: strokeColor != null ? strokeColor! : Colors.transparent),
        );
        return widgetDecoration;
      }
      /*
      * 获取控件
      * */
      Widget? getWidget(BuildContext context) {
        return null;
      }
    }

    具体使用

    使用方式有两种,一种是直接使用,用BaseWidget包裹你的组件即可,相关属性和方法就可以直接调用了。

    return BaseWidget(
          childWidget: const Text("测试文本"),
          margin: 10,
          onClick: () {
            //点击事件
          },
        );

    第二种就是,自己定义组件,继承BaseWidget,可扩展自己想要实现的属性,之后直接用自己定义的组件即可,比如我想自定义一个Text,如下所示:

    class SelfText extends BaseWidget {
      final String? text;
      const SelfText(this.text,
          {super.key,
          super.width,
          super.height,
          super.margin,
          super.marginLeft,
          super.marginTop,
          super.marginRight,
          super.marginBottom,
          super.padding,
          super.paddingLeft,
          super.paddingTop,
          super.paddingRight,
          super.paddingBottom,
          super.backgroundColor,
          super.strokeWidth,
          super.strokeColor,
          super.solidColor,
          super.radius,
          super.isCircle,
          super.leftTopRadius,
          super.rightTopRadius,
          super.leftBottomRadius,
          super.rightBottomRadius,
          super.childWidget,
          super.alignment,
          super.onClick,
          super.onDoubleClick,
          super.onLongPress});
      @override
      Widget? getWidget(BuildContext context) {
        return Text(text!);
      }
    }

    具体使用的时候,直接使用,就不用在外层包裹BaseWidget,而且你还可以在自定义类中随意扩展自己的属性。

    return SelfText(
          "测试文本",
          margin: 10,
          onClick: () {
            //点击事件
          },
        );

    四、相关总结

    在实际的开发中,Widget的基类还是很有必要存在的,不然就会存在很多的冗余嵌套代码,具体如何去封装,还要根据相关的需求和业务来实际的操作。好了铁子们,本篇文章就到这里,不管封装的好与坏,都希望可以帮助到大家。

    以上就是Flutter控件之实现Widget基类的封装的详细内容,更多关于Flutter Widget的资料请关注3672js教程其它相关文章!

    您可能感兴趣的文章:
    • Flutter构建自定义Widgets的全过程记录
    • Flutter基本组件Basics Widget学习
    • 详解Flutter Widget
    • Flutter开发之Widget自定义总结
    • Flutter Widgets之标签类控件Chip详解

    用户评论