Base class Widget encapsulation of Flutter controls

After a short period of contact with Flutter, there is a problem that has always been on the bright side, that is, Widgets in Flutter are not as easy to use as controls in Android. In Android, such as TextView, ImageView, etc. or other Views, all It has its own very wide range of properties and methods, such as width, height, margin and padding, and related click events. In Flutter, the corresponding controls lack these basic and commonly used properties, so that every time you write a Widget, If you want to implement click events, or margin, padding, you have to wrap a layer with other Widgets, which is very inconvenient to use. Based on the above background, an idea of ​​encapsulating the base class was born.

Although I have been in touch with Flutter before, I haven’t used it for a long time. If I pick it up again today, there will inevitably be some shortcomings. If there are any problems in the packaging, I hope you can enlighten me.

A broad overview of this article is as follows:

1. Which attributes need to be encapsulated

2. Determine the base class Widget

3. Base class implementation

4. Relevant summary

1. What attributes need to be encapsulated

Which attributes are specifically needed, neither the more the better, nor the less the better, but based on the actual development needs, it is enough to expand the commonly used ones.

A text or image control or other controls, in the actual development, what should we consider? Is the most common thing is its own width and height? This is the most common and necessary. In addition to width and height, its own click event is also an attribute with high frequency. Therefore, in the base class Widget, its width, High and click events must exist. When it comes to events, in addition to click events, double-click or long-press events in some requirements also exist. Therefore, try to seal them in the base class to facilitate the use of subclass controls.

In addition, margins and inner margins are also essential attributes. I dare not say that nine out of ten controls are used, at least more than half of the probability, so this is also to be encapsulated in the base class Medium; as for background attributes, such as rounded corners, circles, hollows, solids, these depend on the actual project, and can also be placed in the base class if necessary.

A preliminary list, the general package attributes are as follows, of course, everyone's package is different, mainly depends on the actual needs.

Attributes

type

overview

width

double

Width

height

double

high

margin

double

Uniform margin settings (upper left, lower right)

marginLeft

double

margin (left)

marginTop

double

Margin (Top)

marginRight

double

margin (right)

marginBottom

double

Margin (bottom)

padding

double

Unified padding settings (upper left, lower right)

paddingLeft

double

padding (left)

paddingTop

double

padding (top)

paddingRight

double

padding (right)

paddingBottom

double

padding (bottom)

onClick

method

click event

onDoubleClick

method

double click event

onLongPress

method

long press event

backgroundColor

Color

One of background color and decoration

strokeWidth

double

Uniform width of the background border

strokeColor

Color

The color of the background border

solidColor

Color

background fill color

radius

double

The angle of the background, set uniformly

leftTopRadius

double

background top left angle

rightTopRadius

double

background top right angle

leftBottomRadius

double

background bottom left angle

rightBottomRadius

double

background bottom right angle

isCircle

bool

Whether the background is a circle

childWidget

Widget

passed child control

alignment

Alignment

Location

gradientColorList

List<Color>

Gradient color collection

gradientColorStops

List<double>

Gradient color value gradient, value range [0,1]

gradientBegin

Alignment

gradient start position

gradientEnd

Alignment

Gradient end position

2. Determine the base class Widget

The Widget of the base class mainly determines the following aspects. The first is whether to customize an abstract class or a non-abstract class. Second, the inheritance method is stateful or stateless. Third, how to implement the click method of the component .

At the beginning, I wrote an abstract base class. After all, in the next operation, I will re-encapsulate each control on the basis of the original, instead of using it independently. In this case, the abstract class It is the most suitable, just extend the method that must be implemented to the subclass, but in this case, there is a disadvantage, that is, the native control cannot enjoy the various properties of the base class, there is no way, and finally changed to Non-abstract classes, in this way, both ways can be satisfied.

Regarding the inheritance method, for a page, it is more or less necessary to render data and update the UI. In this case, it is certain to inherit StatefulWidget, but generally a control is triggered by others, and it itself is very Less active triggering, so, generally speaking, we can inherit StatelessWidget.

Regarding the click method of components, if it is not at the Button level, few controls have their own click events, so we have to implement them ourselves. In Flutter, there are many components that can help realize clicks, such as InkWell, GestureDetector, InkResponse, and original The pointer event Listener provides us with a wealth of touch events, as follows:

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("双击事件");
      },
    );

raw pointer events

return Listener(
      child: Container(
        child: const Text("测试"),
      ),
  		//手指按下回调
      onPointerDown: (PointerDownEvent event) {},
  		//手指移动回调
      onPointerMove: (PointerMoveEvent event) {},
  		//手指抬起回调
      onPointerUp: (PointerUpEvent event) {},
  		//触摸事件取消回调
      onPointerCancel: (PointerCancelEvent event) {},
    );

There are many related properties, you can look at the relevant source code, which one to use, I think, the first three are all right, after all, there are related click, double click, long press events, if you want to get more touch events , then you can use GestureDetector. If you just click, long-press and double-click, InkWell is recommended, which is more sensitive than clicking. Of course, which one to use depends on yourself.

3. Base class implementation

The implementation of the base class is relatively simple. In the build method, the outermost layer is wrapped with a click event, and then wrapped with a Container component, which is used for width, height, margin, padding, background, etc., rounded corners, circles, and gradients The attribute decoration of Container is used.

All the source codes are as follows, they are all system api calls, not particularly difficult.

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;
  }
}

Specific use

There are two ways to use it, one is to use it directly, just wrap your component with BaseWidget, and the related properties and methods can be called directly.

return BaseWidget(
      childWidget: const Text("测试文本"),
      margin: 10,
      onClick: () {
        //点击事件
      },
    );

The second is to define components by yourself, inherit BaseWidget, extend the properties you want to achieve, and then directly use the components you define. For example, I want to customize a Text, as shown below:

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!);
  }
}

When using it directly, you don’t need to wrap BaseWidget in the outer layer, and you can also expand your own properties in the custom class at will.

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

4. Relevant summary

In actual development, the base class of Widget is still necessary to exist, otherwise there will be a lot of redundant nested codes. How to package it depends on the actual operation according to the relevant needs and business. Alright guys, this article ends here, no matter the package is good or bad, I hope it can help you.

Guess you like

Origin blog.csdn.net/ming_147/article/details/130752881
Recommended