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