Flutter控件之基类Widget封装

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

在短时间的接触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<Color>

渐变颜色集合

gradientColorStops

List<double>

渐变颜色值梯度,取值范围[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


returnGestureDetector(
child: constText("首页"),
onLongPress: (){
print("长按事件");
      },
onDoubleTap: (){
print("双击事件");
      },
onTap: (){
print("点击事件");
      },
onPanDown: (DragDownDetailsdetail) {
// 手指按下的相对于屏幕的位置print("手指按下回调");
      },
onPanUpdate: (DragUpdateDetailsdetail) {
print("手指滑动回调");
      },
onPanEnd: (DragEndDetailsdetail) {
print("手指停止滑动回调");
      },
// 垂直方向拖动事件onVerticalDragUpdate: (DragUpdateDetailsdetails) {
      },
// 水平方向拖动事件onHorizontalDragUpdate: (DragUpdateDetailsdetails) {
      },
    );


InkResponse


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


原始指针事件


returnListener(
child: Container(
child: constText("测试"),
      ),
//手指按下回调onPointerDown: (PointerDownEventevent) {},
//手指移动回调onPointerMove: (PointerMoveEventevent) {},
//手指抬起回调onPointerUp: (PointerUpEventevent) {},
//触摸事件取消回调onPointerCancel: (PointerCancelEventevent) {},
    );

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


三、基类实现


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


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


import'package:flutter/material.dart';
///AUTHOR:AbnerMing///DATE:2023/5/11///INTRODUCE:控件无状态基类classBaseWidgetextendsStatelessWidget {
finalVoidCallback?onClick; //点击事件finalVoidCallback?onDoubleClick; //双击事件finalVoidCallback?onLongPress; //长按事件finaldouble?width; //宽度finaldouble?height; //高度finaldouble?margin; //外边距,左上右下finaldouble?marginLeft; //外边距,距离左边finaldouble?marginTop; //外边距,距离上边finaldouble?marginRight; //外边距,距离右边finaldouble?marginBottom; //外边距,距离下边finaldouble?padding; //内边距,左上右下finaldouble?paddingLeft; //内边距,距离左边finaldouble?paddingTop; //内边距,距离上边finaldouble?paddingRight; //内边距,距离右边finaldouble?paddingBottom; //内边距,距离下边finalColor?backgroundColor; //背景颜色 和 decoration 二者取其一finaldouble?strokeWidth; //背景边框统一的宽度finalColor?strokeColor; //背景边框的颜色finalColor?solidColor; //背景填充颜色finaldouble?radius; //背景的角度finalbool?isCircle; //背景是否是圆形finaldouble?leftTopRadius; //背景左上角度finaldouble?rightTopRadius; //背景 右上角度finaldouble?leftBottomRadius; //背景 左下角度finaldouble?rightBottomRadius; //背景 右下角度finalWidget?childWidget; //子控件finalAlignment?alignment; //位置finalint?gradient; //渐变方式,为支持后续拓展,用int类型finalList<Color>?gradientColorList; //渐变颜色finalList<double>?gradientColorStops; //颜色值梯度,取值范围[0,1]finalAlignment?gradientBegin; //渐变起始位置finalAlignment?gradientEnd; //渐变结束位置//边框的颜色constBaseWidget(
      {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});
@overrideWidgetbuild(BuildContextcontext) {
returnInkWell(
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,
// 渐变色开始的位置,默认 centerLeftend: gradientEnd!=null?gradientEnd! : Alignment.centerRight,
// 渐变色结束的位置,默认 centerRightstops: 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),
    );
returnwidgetDecoration;
  }
/** 获取控件* */Widget?getWidget(BuildContextcontext) {
returnnull;
  }
}


具体使用


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


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

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


classSelfTextextendsBaseWidget {
finalString?text;
constSelfText(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});
@overrideWidget?getWidget(BuildContextcontext) {
returnText(text!);
  }
}


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


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

四、相关总结


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

相关文章
|
Android开发 iOS开发 容器
Flutter控件封装之轮播图Banner
Flutter中实现轮播图的方式有很多种,比如使用三方flutter_swiper,card_swiper等等,使用这些三方,可以很快很方便的实现一个轮播图展示,基本上也能满足我们日常的开发需求,如果说,想要一些定制化的操作,那么就不得不去更改源码或者自己自定义一个,自己定义的话,Flutter中提供了原生组件PageView,可以使用它很方便的来实现一个轮播图。
335 0
|
2月前
|
Android开发
Flutter控件的显示与隐藏
Flutter控件的显示与隐藏
110 3
|
3月前
|
JSON Dart API
Flutter dio http 封装指南说明
本文介绍了如何实现一个通用、可重构的 Dio 基础类,包括单例访问、日志记录、常见操作封装以及请求、输出、报错拦截等功能。
Flutter dio http 封装指南说明
|
2月前
|
开发者 监控 开发工具
如何将JSF应用送上云端?揭秘在Google Cloud Platform上部署JSF应用的神秘步骤
【8月更文挑战第31天】本文详细介绍如何在Google Cloud Platform (GCP) 上部署JavaServer Faces (JSF) 应用。首先,确保已准备好JSF应用并通过Maven构建WAR包。接着,使用Google Cloud SDK登录并配置GCP环境。然后,创建`app.yaml`文件以配置Google App Engine,并使用`gcloud app deploy`命令完成部署。最后,通过`gcloud app browse`访问应用,并利用GCP的监控和日志服务进行管理和故障排查。整个过程简单高效,帮助开发者轻松部署和管理JSF应用。
41 0
|
3月前
|
移动开发 UED 容器
Flutter-自定义可展开文本控件
Flutter-自定义可展开文本控件
71 0
|
3月前
Flutter-自定义三角形评分控件
Flutter-自定义三角形评分控件
34 0
|
3月前
Flutter-底部弹出框(Widget层级)
文章描述了如何在Flutter中使用DraggableScrollableSheet创建一个底部弹出框,同时保持其可手势滑动关闭。作者遇到问题并提出对原控件进行扩展,以支持头部和列表布局的滑动关闭功能。
154 0
|
4月前
Flutter StreamBuilder 实现局部刷新 Widget
Flutter StreamBuilder 实现局部刷新 Widget
33 0
|
4月前
|
移动开发 小程序 安全
基础入门-APP架构&小程序&H5+Vue语言&Web封装&原生开发&Flutter
基础入门-APP架构&小程序&H5+Vue语言&Web封装&原生开发&Flutter
|
5月前
|
设计模式 JavaScript 前端开发
flutter组件封装技巧
工厂函数不会自动调用,需要手动调用
47 3