Flutter控件之基类Widget封装

简介: 基类的Widget主要确定以下几个方面,第一就是,自定义一个抽象类还是非抽象类,第二、继承方式,采取有状态还是无状态,第三、关于组件的点击方式,如何进行实现。

在短时间的接触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});
@overrideWidgetbuild(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});
@overrideWidget?getWidget(BuildContextcontext) {
returnText(text!);
  }
}


具体使用的时候,直接使用,就不用在外层包裹BaseWidget,而且你还可以在自定义类中随意扩展自己的属性。


returnSelfText(
"测试文本",
margin: 10,
onClick: () {
//点击事件      },
    );

四、相关总结


在实际的开发中,Widget的基类还是很有必要存在的,不然就会存在很多的冗余嵌套代码,具体如何去封装,还要根据相关的需求和业务来实际的操作。好了铁子们,本篇文章就到这里,不管封装的好与坏,都希望可以帮助到大家。

相关文章
|
3月前
|
Android开发
Flutter控件的显示与隐藏
Flutter控件的显示与隐藏
151 3
|
17天前
深入理解Flutter鸿蒙next版本 中的Widget继承:使用extends获取数据与父类约束
本文详细介绍了Flutter中如何通过继承其他Widget来创建自定义组件。首先解释了Widget继承的基本概念,包括StatelessWidget和StatefulWidget的区别。接着通过具体示例展示了如何继承StatelessWidget和StatefulWidget,并在子类中访问父类的build方法和状态。最后,结合多个自定义Widget展示了如何在实际应用中灵活使用继承和组合来构建复杂的UI。
67 8
|
15天前
|
容器
flutter&鸿蒙next 使用 InheritedWidget 实现跨 Widget 传递状态
在 Flutter 中,状态管理至关重要。本文详细介绍了如何使用 InheritedWidget 实现跨 Widget 的状态传递。InheritedWidget 允许数据在 Widget 树中向下传递,适用于多层嵌套的场景。通过一个简单的计数器示例,展示了如何创建和使用 InheritedWidget,包括其基础概念、工作原理及代码实现。虽然 InheritedWidget 较底层,但它是许多高级状态管理解决方案的基础。
91 2
|
17天前
|
存储 缓存 Dart
Flutter&鸿蒙next 封装 Dio 网络请求详解:登录身份验证与免登录缓存
本文详细介绍了如何在 Flutter 中使用 Dio 封装网络请求,实现用户登录身份验证及免登录缓存功能。首先在 `pubspec.yaml` 中添加 Dio 和 `shared_preferences` 依赖,然后创建 `NetworkService` 类封装 Dio 的功能,包括请求拦截、响应拦截、Token 存储和登录请求。最后,通过一个登录界面示例展示了如何在实际应用中使用 `NetworkService` 进行身份验证。希望本文能帮助你在 Flutter 中更好地处理网络请求和用户认证。
132 1
|
17天前
|
Dart UED 开发者
Flutter&鸿蒙next中的按钮封装:自定义样式与交互
在Flutter应用开发中,按钮是用户界面的重要组成部分。Flutter提供了多种内置按钮组件,但有时这些样式无法满足特定设计需求。因此,封装一个自定义按钮组件变得尤为重要。自定义按钮组件可以确保应用中所有按钮的一致性、可维护性和可扩展性,同时提供更高的灵活性,支持自定义颜色、形状和点击事件。本文介绍了如何创建一个名为CustomButton的自定义按钮组件,并详细说明了其样式、形状、颜色和点击事件的处理方法。
67 1
|
17天前
|
开发工具 UED
Flutter&鸿蒙next中封装一个输入框组件
本文介绍了如何创建一个简单的Flutter播客应用。首先,通过`flutter create`命令创建项目;接着,在`lib`目录下封装一个自定义输入框组件`CustomInput`;然后,在主应用文件`main.dart`中使用该输入框组件,实现简单的UI布局和功能;最后,通过`flutter run`启动应用。本文还提供了后续扩展建议,如状态管理、网络请求和UI优化。
94 1
|
18天前
|
存储 缓存 JavaScript
Flutter 学习之封装 WebView
【10月更文挑战第24天】通过以上的探讨,我们可以看出,在 Flutter 中封装 WebView 是非常有必要的,它可以提高代码的复用性、增强可维护性、提供统一接口。在实际应用中,我们需要根据具体的需求和场景,选择合适的封装方法和技术,以实现更好的效果。
|
1月前
|
容器
flutter:第一个flutter&Widget的使用 (二)
本文介绍了Flutter框架下的基本组件及其用法,包括简单的 Stateless Widget 如文本和按钮,以及更复杂的 StatefulWidget 示例。详细解释了如何使用 `context` 获取祖先小部件的信息,并展示了 `MaterialApp` 的属性及用途。此外,还探讨了 `StatefulWidget` 与 `StatelessWidget` 的区别,以及 `AppBar` 的常见属性配置方法。适合Flutter初学者参考学习。
|
15天前
|
Dart JavaScript 前端开发
Flutter 的 Widget 概述与常用 Widgets 与鸿蒙 Next 的对比
Flutter 是 Google 开发的开源 UI 框架,用于快速构建高性能的移动、Web 和桌面应用。Flutter 通过 Widget 构建 UI,每个 UI 元素都是 Widget,包括文本、按钮、图片等。Widget 不仅描述外观,还描述行为,是不可变的。常见的 Widget 包括结构型(Container、Column、Row)、呈现型(Text、Image)、交互型(ElevatedButton)和状态管理型(StatefulWidget)。Flutter 与鸿蒙 Next 在组件化架构、开发语言、布局系统、性能和跨平台支持方面各有优势
67 0
|
15天前
|
开发工具
Flutter&鸿蒙next中封装一个列表组件
Flutter&鸿蒙next中封装一个列表组件
30 0