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

相关文章
|
12月前
|
Android开发 iOS开发 容器
Flutter控件封装之轮播图Banner
Flutter中实现轮播图的方式有很多种,比如使用三方flutter_swiper,card_swiper等等,使用这些三方,可以很快很方便的实现一个轮播图展示,基本上也能满足我们日常的开发需求,如果说,想要一些定制化的操作,那么就不得不去更改源码或者自己自定义一个,自己定义的话,Flutter中提供了原生组件PageView,可以使用它很方便的来实现一个轮播图。
242 0
|
9天前
Flutter StreamBuilder 实现局部刷新 Widget
Flutter StreamBuilder 实现局部刷新 Widget
10 0
|
11天前
|
移动开发 小程序 安全
基础入门-APP架构&小程序&H5+Vue语言&Web封装&原生开发&Flutter
基础入门-APP架构&小程序&H5+Vue语言&Web封装&原生开发&Flutter
|
2月前
|
设计模式 JavaScript 前端开发
flutter组件封装技巧
工厂函数不会自动调用,需要手动调用
28 3
|
2月前
|
Android开发
Flutter完整开发实战详解(六、 深入Widget原理),2024百度Android岗面试真题收录解析
Flutter完整开发实战详解(六、 深入Widget原理),2024百度Android岗面试真题收录解析
|
2月前
|
开发框架 前端开发 搜索推荐
【Flutter前端技术开发专栏】Flutter中的自定义Widget与渲染流程
【4月更文挑战第30天】探索Flutter的自定义Widget与渲染流程。自定义Widget是实现复杂UI设计的关键,优点在于个性化设计、功能扩展和代码复用,但也面临性能优化和复杂性管理的挑战。创建步骤包括设计结构、定义Widget类、实现构建逻辑和处理交互。Flutter渲染流程涉及渲染对象树、布局、绘制和合成阶段。实践案例展示如何创建带渐变背景和阴影的自定义按钮。了解这些知识能提升应用体验并应对开发挑战。查阅官方文档以深入学习。
【Flutter前端技术开发专栏】Flutter中的自定义Widget与渲染流程
|
2月前
|
JavaScript 前端开发 开发者
【Flutter前端技术开发专栏】Flutter中的Widget与状态管理
【4月更文挑战第30天】本文探讨了Flutter的Widget和状态管理。Widget是Flutter构建UI的基础,分为有状态和无状态两种。状态管理确保UI随应用状态变化更新,影响应用性能和可维护性。文章介绍了`setState`、`Provider`、`Riverpod`、`Bloc`和`Redux`等状态管理方法,并通过计数器应用展示了其实现。选择合适的状态管理策略对高效开发至关重要。
【Flutter前端技术开发专栏】Flutter中的Widget与状态管理
|
2月前
|
编解码 算法 开发者
Flutter的布局系统:深入探索布局Widget与布局原则
【4月更文挑战第26天】Flutter布局系统详解,涵盖布局Widget(Row/Column、Stack、GridView/ListView、CustomSingleChildLayout)和布局原则(弹性布局、约束优先、流式布局、简洁明了)。文章旨在帮助开发者理解并运用Flutter的布局系统,创建适应性强、用户体验佳的界面。通过选择合适的布局Widget和遵循原则,可实现复杂且高效的UI设计。
|
2月前
|
前端开发 开发者 UED
Flutter的自定义Painter:深入探索自定义绘制Widget的技术实现
【4月更文挑战第26天】Flutter的自定义Painter允许开发者根据需求绘制独特UI,通过继承`CustomPaint`类和重写`paint`方法实现。在`paint`中使用`Canvas`绘制图形、路径等。创建自定义Painter类后,将其作为`CustomPaint` Widget的`painter`属性使用。此技术可实现自定义形状、渐变、动画等复杂效果,提升应用视觉体验。随着Flutter的进化,自定义Painter将提供更丰富的功能。
|
2月前
|
开发框架 搜索推荐 Android开发
Flutter的Widget基础:概念、分类与深入探索
【4月更文挑战第26天】Flutter Widget详解:基础、分类与工作原理。Widget是Flutter UI的核心,描述界面外观而非直接渲染。分为基础、布局、可滚动及状态管理四大类。基于响应式编程,状态变化时自动更新UI。了解其概念、分类和原理,能助开发者高效构建精美应用。随着Flutter生态发展,Widget系统潜力无限。