在短时间的接触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}); Widgetbuild(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}); Widget?getWidget(BuildContextcontext) { returnText(text!); } }
具体使用的时候,直接使用,就不用在外层包裹BaseWidget,而且你还可以在自定义类中随意扩展自己的属性。
returnSelfText( "测试文本", margin: 10, onClick: () { //点击事件 }, );
四、相关总结
在实际的开发中,Widget的基类还是很有必要存在的,不然就会存在很多的冗余嵌套代码,具体如何去封装,还要根据相关的需求和业务来实际的操作。好了铁子们,本篇文章就到这里,不管封装的好与坏,都希望可以帮助到大家。