目录
在短时间的接触flutter之后,有一个问题一直摆在了明面上,那就是,flutter中的widget确实没有android中的控件好用,在android中,比如textview,imageview等等或者其他view,都有着自己非常广泛的属性和方法,比如宽,高,margin和padding,以及相关的点击事件,这在flutter,对应的控件中,却少了这些基础又常用的属性,以至于每写一个widget,如果想要实现点击事件,或者margin,padding,不得不用其他的widget包裹一层,使用起来很是不方便,基于以上的背景,便萌生了一个封装基类的想法。
虽然之前接触过flutter,但也是许久不用了,今再拾起,难免有些许不足,如果在封装上有哪些问题,还望不吝赐教。
一、需要封装哪些属性
具体需要哪些属性,不是越多越好,也不是越少越好,而是基于实际的开发需求,拓展出常用的即可。
一个文本或者图片控件又或者是其他控件,在实际的开发中,哪些是我们需要考虑的?是不是最常见的就是自身的宽高,这是最常见且必须需要的,除了宽高,其自身的点击事件,也是频次居高不下的一个属性,所以,在基类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("kb88凯时d88尊龙官网手机app官网登录首页"), 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? gradientcolorlist; //渐变颜色 final list ? 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的资料请关注其它相关文章!