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