Flutter控件之實現(xiàn)Widget基類的封裝
在短時間的接觸Flutter之后,有一個問題一直擺在了明面上,那就是,F(xiàn)lutter中的Widget確實沒有Android中的控件好用,在Android中,比如TextView,ImageView等等或者其他View,都有著自己非常廣泛的屬性和方法,比如寬,高,margin和padding,以及相關(guān)的點擊事件,這在Flutter,對應(yīng)的控件中,卻少了這些基礎(chǔ)又常用的屬性,以至于每寫一個Widget,如果想要實現(xiàn)點擊事件,或者margin,padding,不得不用其他的Widget包裹一層,使用起來很是不方便,基于以上的背景,便萌生了一個封裝基類的想法。
雖然之前接觸過Flutter,但也是許久不用了,今再拾起,難免有些許不足,如果在封裝上有哪些問題,還望不吝賜教。
一、需要封裝哪些屬性
具體需要哪些屬性,不是越多越好,也不是越少越好,而是基于實際的開發(fā)需求,拓展出常用的即可。
一個文本或者圖片控件又或者是其他控件,在實際的開發(fā)中,哪些是我們需要考慮的?是不是最常見的就是自身的寬高,這是最常見且必須需要的,除了寬高,其自身的點擊事件,也是頻次居高不下的一個屬性,所以,在基類Widget中,其寬、高、點擊事件是必須要存在的,說到事件,除了點擊事件之外,一些需求中的雙擊或者長按事件也是存在的,所以,盡量也封到基類中,便于子類控件的使用。
除此之外,像外邊距、內(nèi)邊距、也是必不可少的屬性,不敢說十個控件有九個用到,起碼說也得一半以上的概率,所以,這也是要封裝到基類中的;至于背景屬性,比如圓角的,圓形的,空心的,實心的,這些看實際的項目使用,如果需要,也可以放到基類中。
初步羅列了一下,大致封裝的屬性如下,當(dāng)然了,每個人的封裝都有不同,主要還是要看實際的需求。
| 屬性 | 類型 | 概述 |
|---|---|---|
| width | double | 寬 |
| height | double | 高 |
| margin | double | 外邊距統(tǒng)一設(shè)置(左上右下) |
| marginLeft | double | 外邊距(左) |
| marginTop | double | 外邊距(上) |
| marginRight | double | 外邊距(右) |
| marginBottom | double | 外邊距(下) |
| padding | double | 內(nèi)邊距統(tǒng)一設(shè)置 |
| paddingLeft | double | 內(nèi)邊距(左) |
| paddingTop | double | 內(nèi)邊距(上) |
| paddingRight | double | 內(nèi)邊距(右) |
| paddingBottom | double | 內(nèi)邊距(下) |
| onClick | 方法 | 點擊事件 |
| onDoubleClick | 方法 | 雙擊事件 |
| onLongPress | 方法 | 長按事件 |
| backgroundColor | Color | 背景顏色 和 decoration 二者取其一 |
| strokeWidth | double | 背景邊框統(tǒng)一的寬度 |
| strokeColor | Color | 背景邊框的顏色 |
| solidColor | Color | 背景填充顏色 |
| radius | double | 背景的角度,統(tǒng)一設(shè)置 |
| leftTopRadius | double | 背景左上角度 |
| rightTopRadius | double | 背景右上角度 |
| leftBottomRadius | double | 背景左下角度 |
| rightBottomRadius | double | 背景右下角度 |
| isCircle | bool | 背景是否是圓形 |
| childWidget | Widget | 傳遞的子控件 |
| alignment | Alignment | 位置 |
| gradientColorList | List | 漸變顏色集合 |
| gradientColorStops | List | 漸變顏色值梯度,取值范圍[0,1] |
| gradientBegin | Alignment | 漸變起始位置 |
| gradientEnd | Alignment | 漸變結(jié)束位置 |
二、確定基類Widget
基類的Widget主要確定以下幾個方面,第一就是,自定義一個抽象類還是非抽象類,第二、繼承方式,采取有狀態(tài)還是無狀態(tài),第三、關(guān)于組件的點擊方式,如何進行實現(xiàn)。
一開始自己寫的是一個抽象基類,畢竟在接下來的操作中,對于各個控件,我都會重新在原生的基礎(chǔ)之上進行再次的封裝,而不是獨立的使用,這種情況下,抽象類是最合適的,向子類拓展出必須要實現(xiàn)的方法即可,但是這種情況下就有一個弊端,那就是,原生的控件無法享有這個基類的各個屬性,沒辦法,最后又改為了非抽象類,這樣,兩種方式均可滿足。
關(guān)于繼承方式,對于一個頁面而言,或多或少都是需要渲染數(shù)據(jù),更新UI的,這種情況下繼承StatefulWidget是肯定的,但是一般一個控件,都是別人來觸發(fā)它,而它自己很少主動觸發(fā),所以,一般而言,我們繼承StatelessWidget即可。
關(guān)于組件的點擊方式,如果是非Button級別的,很少有控件自帶點擊事件,所以我們不得不自行實現(xiàn),而在Flutter中提供了很多可以協(xié)助實現(xiàn)點擊的組件,比如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("手指按下回調(diào)");
},
onPanUpdate: (DragUpdateDetails detail) {
print("手指滑動回調(diào)");
},
onPanEnd: (DragEndDetails detail) {
print("手指停止滑動回調(diào)");
},
// 垂直方向拖動事件
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("測試"),
),
//手指按下回調(diào)
onPointerDown: (PointerDownEvent event) {},
//手指移動回調(diào)
onPointerMove: (PointerMoveEvent event) {},
//手指抬起回調(diào)
onPointerUp: (PointerUpEvent event) {},
//觸摸事件取消回調(diào)
onPointerCancel: (PointerCancelEvent event) {},
);相關(guān)的屬性有很多,大家可以看下相關(guān)源碼,具體用哪個,我是認為,前三個都可以,畢竟都有相關(guān)的點擊,雙擊,長按事件,如果你想要獲取更多的觸摸事件,那么就可以使用GestureDetector,如果只是點擊,長按和雙擊,比較推薦InkWell,相對點擊比較靈敏,當(dāng)然了,具體使用哪個,還是要看自己。
三、基類實現(xiàn)
基類實現(xiàn)就比較的簡單了,build方法中最外層用點擊事件包裹,再往下用Container組件來包裹,目的用于寬高,margin,padding和背景等實現(xiàn),圓角和圓形以及漸變用的是Container的屬性decoration。
全部的源碼如下,都是系統(tǒng)的api調(diào)用,沒有特別難的。
import 'package:flutter/material.dart';
///AUTHOR:AbnerMing
///DATE:2023/5/11
///INTRODUCE:控件無狀態(tài)基類
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; //內(nèi)邊距,左上右下
final double? paddingLeft; //內(nèi)邊距,距離左邊
final double? paddingTop; //內(nèi)邊距,距離上邊
final double? paddingRight; //內(nèi)邊距,距離右邊
final double? paddingBottom; //內(nèi)邊距,距離下邊
final Color? backgroundColor; //背景顏色 和 decoration 二者取其一
final double? strokeWidth; //背景邊框統(tǒng)一的寬度
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; //漸變方式,為支持后續(xù)拓展,用int類型
final List<Color>? gradientColorList; //漸變顏色
final List<double>? gradientColorStops; //顏色值梯度,取值范圍[0,1]
final Alignment? gradientBegin; //漸變起始位置
final Alignment? gradientEnd; //漸變結(jié)束位置
//邊框的顏色
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! : [],
// 設(shè)置有哪些漸變色
begin: gradientBegin != null ? gradientBegin! : Alignment.centerLeft,
// 漸變色開始的位置,默認 centerLeft
end: gradientEnd != null ? gradientEnd! : Alignment.centerRight,
// 漸變色結(jié)束的位置,默認 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包裹你的組件即可,相關(guān)屬性和方法就可以直接調(diào)用了。
return BaseWidget(
childWidget: const Text("測試文本"),
margin: 10,
onClick: () {
//點擊事件
},
);第二種就是,自己定義組件,繼承BaseWidget,可擴展自己想要實現(xiàn)的屬性,之后直接用自己定義的組件即可,比如我想自定義一個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: () {
//點擊事件
},
);四、相關(guān)總結(jié)
在實際的開發(fā)中,Widget的基類還是很有必要存在的,不然就會存在很多的冗余嵌套代碼,具體如何去封裝,還要根據(jù)相關(guān)的需求和業(yè)務(wù)來實際的操作。好了鐵子們,本篇文章就到這里,不管封裝的好與壞,都希望可以幫助到大家。
以上就是Flutter控件之實現(xiàn)Widget基類的封裝的詳細內(nèi)容,更多關(guān)于Flutter Widget的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Flutter?將Dio請求轉(zhuǎn)發(fā)原生網(wǎng)絡(luò)庫的實現(xiàn)方案
這篇文章主要介紹了Flutter?將Dio請求轉(zhuǎn)發(fā)原生網(wǎng)絡(luò)庫,需要注意添加NativeNetInterceptor,如果有多個攔截器,例如LogInterceptors等等,需要將NativeNetInterceptor放到最后,需要的朋友可以參考下2022-05-05

