我正在参加「启航计划」

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

虽然之前接触过Flutter,但也是许久不必了,今再捡起,不免有些许缺乏,假如在封装上有哪些问题,还望不吝赐教。

本篇文章大致概述如下:

1、需求封装哪些特点

2、确认基类Widget

3、基类完成

4、相关总结

一、需求封装哪些特点

详细需求哪些特点,不是越多越好,也不是越少越好,而是基于实践的开发需求,拓宽出常用的即可。

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

除此之外,像外边距、内边距、也是必不可少的特点,不敢说十个控件有九个用到,起码说也得一半以上的概率,所以,这也是要封装到基类中的;至于布景特点,比方圆角的,圆形的,空心的,实心的,这些看实践的项目运用,假如需求,也能够放到基类中。

开端罗列了一下,大致封装的特点如下,当然了,每个人的封装都有不同,首要仍是要看实践的需求。

特点 类型 概述
width double
height double
margin double 外边距一致设置(左上右下)
marginLeft double 外边距(左)
marginTop double 外边距(上)
marginRight double 外边距(右)
marginBottom double 外边距(下)
padding double 内边距一致设置(左上右下)
paddingLeft double 内边距(左)
paddingTop double 内边距(上)
paddingRight double 内边距(右)
paddingBottom double 内边距(下)
onClick 办法 点击事情
onDoubleClick 办法 双击事情
onLongPress 办法 长按事情
backgroundColor Color 布景色彩和decoration二者取其一
strokeWidth double 布景边框一致的宽度
strokeColor Color 布景边框的色彩
solidColor Color 布景填充色彩
radius double 布景的视点,一致设置
leftTopRadius double 布景左上视点
rightTopRadius double 布景右上视点
leftBottomRadius double 布景左下视点
rightBottomRadius double 布景右下视点
isCircle bool 布景是否是圆形
childWidget Widget 传递的子控件
alignment Alignment 方位
gradientColorList List 突变色彩调集
gradientColorStops List 突变色彩值梯度,取值范围[0,1]
gradientBegin Alignment 突变开端方位
gradientEnd Alignment 突变完毕方位

二、确认基类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的基类仍是很有必要存在的,不然就会存在许多的冗余嵌套代码,详细怎么去封装,还要依据相关的需求和业务来实践的操作。好了铁子们,本篇文章就到这里,不论封装的好与坏,都期望能够帮助到我们。