SoFunction
Updated on 2025-04-07

Flutter control implements the Widget base class encapsulation

After a short period of contact with Flutter, there is a problem that has always been on the surface, that is, the Widget in Flutter is indeed not as useful as the controls in Android. In Android, such as TextView, ImageView, etc. or other Views, they all have their own very wide properties and methods, such as width, height, margin and padding, and related click events. In Flutter, the corresponding controls, these basic and commonly used attributes are missing, so that every time a widget is written, if you want to implement click events, or margin or padding, you have to use other widgets to wrap a layer, which is very inconvenient to use. Based on the above background, the idea of ​​encapsulating the base class came into being.

Although I have been in contact with Flutter before, I haven't used it for a long time. I can't help but find some shortcomings when I pick it up now. If there are any problems with the packaging, I hope I will give you some advice.

1. What attributes need to be encapsulated

What attributes are needed in detail? It is not that the more, the better, nor that the fewer, the better. Instead, it only needs to expand the commonly used ones based on actual development needs.

What are the text or picture controls or other controls that we need to consider in actual development? Is it the most common thing to be its own width and height? This is the most common and necessary. In addition to its width and height, its own click events are also a property with constant frequency. Therefore, in the base class widget, its width, height and click events must exist. Speaking of events, in addition to click events, some double-click or long-press events also exist in some requirements. Therefore, try to block them into the base class to facilitate the use of subclass controls.

In addition, external margins and inner margins are also essential properties. I dare not say that nine of the ten controls are used, at least more than half of the probability, so this also needs to be encapsulated into the base class; as for background properties, such as rounded, rounded, hollow, and solid, these depend on actual projects, and can also be placed in the base class if needed.

I have listed it in advance and the general properties of the packaging are as follows. Of course, everyone's packaging is different, and it mainly depends on the actual needs.

property type Overview
width double Width
height double high
margin double Unified margin settings (upper left, lower right)
marginLeft double Margin (left)
marginTop double Margin (Part 1)
marginRight double Margin (right)
marginBottom double Margin (Part 2)
padding double Unified margin settings
paddingLeft double Inner margin (left)
paddingTop double Inner margin (Part 1)
paddingRight double Inner margin (right)
paddingBottom double Inner margin (bottom)
onClick method Click Event
onDoubleClick method Double-click event
onLongPress method Long press event
backgroundColor Color Background color and decoration
strokeWidth double The uniform width of the background border
strokeColor Color Background border color
solidColor Color Background fill color
radius double Background angle, unified setting
leftTopRadius double The upper left angle of the background
rightTopRadius double The upper right angle of the background
leftBottomRadius double The background lower left angle
rightBottomRadius double The lower right angle of the background
isCircle bool Is the background round?
childWidget Widget The passed child control
alignment Alignment Location
gradientColorList List Gradient color collection
gradientColorStops List 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 base class widget mainly determines the following aspects: the first is to customize an abstract class or a non-abstract class, the second is to inherit the method, adopt stateful or stateless, and the third is to implement the click method of the component.

At the beginning, I wrote an abstract base class. After all, in the following operations, I will encapsulate each control again on a native basis instead of using it independently. In this case, abstract classes are the most suitable, and I can expand the methods that must be implemented to the subclass. However, there is a disadvantage in this case, that is, native controls cannot enjoy the various properties of this base class, and there is no way, and finally they are changed to non-abstract classes. In this way, both methods 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, someone else triggers it, but it rarely triggers it on its own initiative. Therefore, generally speaking, we can inherit StatelessWidget.

Regarding the click method of components, if it is not the Button level, few controls come with click events, so we have to implement them by ourselves. Flutter provides many components that can help implement clicks, such as InkWell, GestureDetector, InkResponse, and original pointer event Listener, all provide us with rich touch events. Here is a brief list:

InkWell

InkWell(
      onLongPress: (){
        print("Long press event");
      },
      onDoubleTap: (){
        print("Double-click event");
      },
      onTap: (){
        print("Click Event");
      }
      child: Container()
)

GestureDetector

return GestureDetector(
      child: const Text("front page"),
  		onLongPress: (){
        print("Long press event");
      },
      onDoubleTap: (){
        print("Double-click event");
      },
      onTap: (){
        print("Click Event");
      },
      onPanDown: (DragDownDetails detail) {
        // The position relative to the screen when the finger is pressed        print("Press the callback");
      },
      onPanUpdate: (DragUpdateDetails detail) {
        print("Finger Slide Callback");
      },
      onPanEnd: (DragEndDetails detail) {
        print("Finger stops sliding callback");
      },
  		//Drag the event vertically      onVerticalDragUpdate: (DragUpdateDetails details) {
      },
      // Horizontal drag event      onHorizontalDragUpdate: (DragUpdateDetails details) {
      },
    );

InkResponse

return InkResponse(
      child: const Text("Click"),
      onTap: () {
        //Click event        print("Click Event");
      },
      onLongPress: () {
        //Long press event        print("Long press event");
      },
      onDoubleTap: () {
        //Double-click event        print("Double-click event");
      },
    );

Original pointer event

return Listener(
      child: Container(
        child: const Text("test"),
      ),
  		//Press the callback with your finger      onPointerDown: (PointerDownEvent event) {},
  		//Finger move callback      onPointerMove: (PointerMoveEvent event) {},
  		//Finger lifts up to call back      onPointerUp: (PointerUpEvent event) {},
  		//Touch event cancel callback      onPointerCancel: (PointerCancelEvent event) {},
    );

There are many related attributes. You can check the relevant source code. Which one is used? I think the first three are OK. After all, there are related clicks, double-clicks, long-press events. If you want to get more touch events, you can use GestureDetector. If you just click, long-press and double-click, InkWell is recommended. It is relatively sensitive to clicks. Of course, it depends on yourself which one to use.

III. Base class implementation

The base class implementation is relatively simple. The outermost layer of the build method is wrapped with a click event, and then the Container component is wrapped with a width and height, margin, padding and background implementation. The corners, circles and gradients are used to describe the property decoration of the Container.

All source codes are as follows, all system API calls, nothing particularly difficult.

import 'package:flutter/';
///AUTHOR:AbnerMing
///DATE:2023/5/11
///INTRODUCE: Control stateless base classclass BaseWidget extends StatelessWidget {
  final VoidCallback? onClick; //Click event  final VoidCallback? onDoubleClick; //Double-click event  final VoidCallback? onLongPress; //Long press event  final double? width; //width  final double? height; //high  final double? margin; //Outer margin, upper left, lower right  final double? marginLeft; //Outside margin, distance to the left  final double? marginTop; //Outer margin, distance from the upper edge  final double? marginRight; //Outer margin, distance to the right  final double? marginBottom; //The margin, the distance below  final double? padding; //Inner margin, upper left, lower right  final double? paddingLeft; //Inner margin, distance to the left  final double? paddingTop; //Inner margin, distance from the upper edge  final double? paddingRight; //Inner margin, distance to the right  final double? paddingBottom; //Inner margin, distance from below  final Color? backgroundColor; //Background color and decoration take one of them  final double? strokeWidth; //The uniform width of the background border  final Color? strokeColor; //The color of the background border  final Color? solidColor; //Background fill color  final double? radius; //The background angle  final bool? isCircle; //Is the background round?  final double? leftTopRadius; //The upper left angle of the background  final double? rightTopRadius; //Background upper right angle  final double? leftBottomRadius; //Background lower left angle  final double? rightBottomRadius; //Background lower right angle  final Widget? childWidget; //Subcontrol  final Alignment? alignment; //Location  final int? gradient; //Gradial mode, int type is used to support subsequent expansion  final List<Color>? gradientColorList; //Gradial color  final List<double>? gradientColorStops; //Color value gradient, value range [0,1]  final Alignment? gradientBegin; //The starting position of the gradient  final Alignment? gradientEnd; //Gradial end position  //Border color  const BaseWidget(
      {,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      });
  @override
  Widget build(BuildContext context) {
    return InkWell(
        highlightColor: ,
        // Transparent color        splashColor: ,
        // Transparent color        onTap: onClick,
        onDoubleTap: onDoubleClick,
        onLongPress: onLongPress,
        child: Container(
          width: width,
          height: height,
          alignment: alignment,
          margin: margin != null
              ? (margin!)
              : (
                  left: marginLeft != null ? marginLeft! : 0,
                  top: marginTop != null ? marginTop! : 0,
                  right: marginRight != null ? marginRight! : 0,
                  bottom: marginBottom != null ? marginBottom! : 0),
          padding: padding != null
              ? (padding!)
              : (
                  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),
        ));
  }
  /*
   * Get Decoration
   * */
  Decoration? getDecoration() {
    BorderRadiusGeometry? borderRadiusGeometry;
    if (radius != null) {
      //All angles      borderRadiusGeometry = ((radius!));
    } else {
      // Otherwise, it is, from all angles      borderRadiusGeometry = (
          topLeft: (leftTopRadius != null ? leftTopRadius! : 0),
          topRight:
              (rightTopRadius != null ? rightTopRadius! : 0),
          bottomLeft:
              (leftBottomRadius != null ? leftBottomRadius! : 0),
          bottomRight: (
              rightBottomRadius != null ? rightBottomRadius! : 0));
    }
    Gradient? tGradient;
    if (gradient != null) {
      tGradient = LinearGradient(
        colors: gradientColorList != null ? gradientColorList! : [],
        // What are the gradient colors?        begin: gradientBegin != null ? gradientBegin! : ,
        // The location where the gradient color begins, default centerLeft        end: gradientEnd != null ? gradientEnd! : ,
        // The position where the gradient color ends, default centerRight        stops: gradientColorStops, // Color value gradient, value range [0,1], the length should be the same as the length of colors      );
    }
    Decoration? widgetDecoration = BoxDecoration(
      gradient: tGradient,
      //Background color      color: solidColor != null ? solidColor! : ,
      //Radical corner radius      borderRadius: isCircle == true ? null : borderRadiusGeometry,
      //Is it a circle shape      shape: isCircle == true ?  : ,
      //Border line width and color      border: (
          width: strokeWidth != null ? strokeWidth! : 0,
          color: strokeColor != null ? strokeColor! : ),
    );
    return widgetDecoration;
  }
  /*
   * Get the control
   * */
  Widget? getWidget(BuildContext context) {
    return null;
  }
}

Specific use

There are two ways to use it. One is to use it directly. Just use BaseWidget to wrap your components, and the relevant attributes and methods can be called directly.

return BaseWidget(
      childWidget: const Text("Test text"),
      margin: 10,
      onClick: () {
        //Click event      },
    );

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

class SelfText extends BaseWidget {
  final String? text;
  const SelfText(,
      {,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      ,
      });
  @override
  Widget? getWidget(BuildContext context) {
    return Text(text!);
  }
}

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

return SelfText(
      "Test text",
      margin: 10,
      onClick: () {
        //Click event      },
    );

4. Related summary

In actual development, it is still necessary to exist for the base class of Widget, otherwise there will be a lot of redundant nested code. How to encapsulate it depends on the relevant needs and business. OK, Iron Man, this article ends here. I hope it can help you no matter whether the packaging is good or bad.

The above is the detailed content of the implementation of the Widget base class for Flutter control. For more information about Flutter Widget, please follow my other related articles!