flutter 自定义简单好用的小组件(二)

266 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情

AlertDialog 背景支持渐变色

背景

设计图上的弹框背景是个渐变色,系统自带的AlertDialog 可以更改背景颜色,却不支持设置渐变,不准备自己全新搞一个,查看AlertDialog的源码

image.png 我们只要对dialogChild变量,进行扩展,支持渐变背景就可以了。

思路

  • 支持背景色渐变我们可以使用Container
  • decoration 作为参数传递
  • decoration 和 color 有冲突,需要填一个判断
  • 增加宽度,支持对弹框的宽度进行自定义 主要更改部分如下 image.png

代码

代码太多,更改部分如下:

import 'package:flutter/material.dart';
import 'dart:ui';

const EdgeInsets _defaultInsetPadding =
    EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0);

class CustomDialog extends StatelessWidget {
  const CustomDialog({
    Key? key,
    this.title,
    this.decoration,
    this.width,
    .....
  })  : assert(contentPadding != null),
        assert(clipBehavior != null),
        super(key: key);
  final Widget? title;
  // 添加一个container 支持dialog的样式自定义
  final Decoration? decoration;
  // 添加一个container 支持dialog的样式自定义
  final double? width;
   ...
    Widget dialogChild = Container(
      decoration: decoration,
      color: decoration!=null?null:Colors.white,
      width: width,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: columnChildren,
      ),
    );
    ....
    return Dialog(
      backgroundColor: Colors.transparent,
      elevation: elevation,
      insetPadding: insetPadding,
      clipBehavior: clipBehavior,
      shape: shape,
      child: dialogChild,
    );
  }
}
double _paddingScaleFactor(double textScaleFactor) {
  final double clampedTextScaleFactor =
      textScaleFactor.clamp(1.0, 2.0).toDouble();
  return lerpDouble(1.0, 1.0 / 3.0, clampedTextScaleFactor - 1.0)!;
}

加减框

image.png

思路

经常会遇到不是特别大的功能点,项目用的地方也挺多,找个框架不一定合适,还有点大,自己写吧~

  • 一个"+"号IconButton
  • 一个"-"号IconButton
  • 一个输入框TextField
  • 上面的放一行Row
  • 每部分有边框,有颜色,各加一个Container
  • 参数分析:按钮宽高可自定义、整体宽度可定义,可以支持输入的最大值、点击加、减、编辑输入框的事件,disabled 属性

代码

import 'package:flutter/material.dart';
class NumberControllerWidget extends StatefulWidget {
  //高度
  final double height;
  //输入框的宽度 总体宽度为自适应
  final double width;
  //按钮的宽度
  final double iconWidth;
  //默认输入框显示的数量
  final String numText;
  //点击加号回调 数量
  final ValueChanged? addValueChanged;
  //点击减号回调 数量
  final ValueChanged? subValueChanged;
  //输入框更改数量回调 
  final ValueChanged? updateValueChanged;
  //最大可以加到的数字
  final int maxCount;

  final bool disabled;

  NumberControllerWidget(
      {this.height = 30,
      this.width = 30,
      this.iconWidth = 25,
      this.numText = '0',
      this.addValueChanged,
      this.subValueChanged,
      this.updateValueChanged,
      this.disabled = false,
      this.maxCount = 99});
  @override
  _NumberControllerWidgetState createState() => _NumberControllerWidgetState();
}

class _NumberControllerWidgetState extends State<NumberControllerWidget> {
  var textController = new TextEditingController();
  late FocusNode focusNode;
  @override
  void initState() {
    super.initState();
    this.textController.text = widget.numText;
    focusNode = new FocusNode();
    // 初始化时,监听输入框失去焦点事件
    focusNode.addListener(() {
      if (!focusNode.hasFocus) {
        if (widget.updateValueChanged != null)
          widget.updateValueChanged?.call(textController.text);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.end,
      children: <Widget>[
        Container(
          height: widget.height,
          decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(10),
              border: Border.all(width: 1, color: Colors.black12)),
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              //减号
              coustomIconButton(icon: Icons.remove, isAdd: false),
              //输入框
              Container(
                width: widget.width,
                decoration: BoxDecoration(
                    border: Border(
                        left: BorderSide(width: 1, color: Colors.black12),
                        right: BorderSide(width: 1, color: Colors.black12))),
                child: TextField(
                  controller: textController,
                  focusNode: focusNode,
                  keyboardType: TextInputType.number,
                  textAlign: TextAlign.center,
                  readOnly: widget.disabled,
                  style: TextStyle(fontSize: 12),
                  enableInteractiveSelection: false,
                  decoration: InputDecoration(
                    contentPadding:
                        EdgeInsets.only(left: 0, top: 2, bottom: 2, right: 0),
                    border: const OutlineInputBorder(
                      gapPadding: 0,
                      borderSide: BorderSide(
                        width: 0,
                        style: BorderStyle.none,
                      ),
                    ),
                  ),
                ),
              ),
              //加号
              coustomIconButton(icon: Icons.add, isAdd: true),
            ],
          ),
        )
      ],
    );
  }

  Widget coustomIconButton({IconData? icon,required bool isAdd}) {
    return Container(
      width: widget.iconWidth,
      child: IconButton(
        padding: EdgeInsets.all(0),
        icon: Icon(icon),
        onPressed: () {
          FocusScope.of(context).requestFocus(new FocusNode());
          int number = int.parse(textController.text);
          if (!isAdd && number == 0) return;
          if (isAdd) {
            if ((number < widget.maxCount)) {
              number++;
              if (widget.addValueChanged != null)
                widget.addValueChanged?.call(number);
            }
          } else {
            if (number == 1) return;
            number--;
            if (widget.subValueChanged != null)
              widget.subValueChanged?.call(number);
          }
          textController.text = '$number';
          if (widget.updateValueChanged != null)
            widget.updateValueChanged?.call(number);
        },
      ),
    );
  }
}

事件进度节点

image.png

思路

  • 第一画圆球和下边线
  • 中间的话圆圈、上边线、下边线
  • 最后一个画上边线和圆球
  • 为了区分,需要传参:是否显示上边线,是否显示下边线、是否点亮

代码

import 'package:flutter/material.dart';
class LeftLineView extends StatelessWidget {
  final bool showTop;
  final bool showBottom;
  final bool isLight;

  const LeftLineView(this.showTop, this.showBottom, this.isLight);

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.symmetric(horizontal: 16),//圆和线的左右外边距
      width: 16,
      child: CustomPaint(
        painter: LeftLinePainter(showTop, showBottom, isLight),
      ),
    );
  }
}

class LeftLinePainter extends CustomPainter {
  static const double _topHeight = 16; //圆上的线高度
  static const Color _lightColor = Color(0xfffe3536);//圆点亮的颜色
  static const Color _normalColor = Color(0xff999999);//圆没点亮的颜色

  final bool showTop; //是否显示圆上面的线
  final bool showBottom;//是否显示圆下面的线
  final bool isLight;//圆形是否点亮

  const LeftLinePainter(this.showTop, this.showBottom, this.isLight);

  @override
  void paint(Canvas canvas, Size size) {
    double lineWidth = 2; // 竖线的宽度
    double centerX = size.width / 2; //容器X轴的中心点
    Paint linePain = Paint();// 创建一个画线的画笔
    linePain.color = showTop ? Colors.red : Colors.transparent;
    linePain.strokeWidth = lineWidth;
    linePain.strokeCap = StrokeCap.square;//画线的头是方形的
    //画圆上面的线
    canvas.drawLine(Offset(centerX, 0), Offset(centerX, _topHeight), linePain);
    //依据下面的线是否显示来设置是否透明
    linePain.color = showBottom ? Colors.red : Colors.transparent;
    // 画圆下面的线
    canvas.drawLine(
        Offset(centerX, _topHeight), Offset(centerX, size.height), linePain);
    // 创建画圆的画笔
    Paint circlePaint = Paint();
    circlePaint.color = isLight ? _lightColor : _normalColor;
    circlePaint.style = PaintingStyle.fill;
    // 画中间的圆
    canvas.drawCircle(Offset(centerX, _topHeight), centerX, circlePaint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    if(oldDelegate is LeftLinePainter){
      LeftLinePainter old = oldDelegate;
      if(old.showBottom!=showBottom){
        return true;
      }
      if(old.showTop!=showTop){
        return true;
      }
      if(old.isLight!=isLight){
        return true;
      }
      return false;
    }
    return true;
  }
}