Flutter - 手写体widgets之wired_elements

2023-11-20 17:40

本文主要是介绍Flutter - 手写体widgets之wired_elements,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

介绍

今天带大家一起看看wired_elements,Wired Elements 是一系列具有手绘外观的基本 UI 元素。

其实这种外观的UI元素在web端已经有非常成熟的组件库,请看这里。他是基于rough.js实现的一系列组件,可用于快速建立交互型产品设计稿,已经有基于此设计的可拖拽的网页端项目软件,大家可以搜一搜看看,我之前搜到过,不过当时没有收藏。。。也可用于自己blog的UI,也可以just for fun。总之web端是有了,但是Flutter我是没有看到,只有一个flutter_rough项目,他是rough.js的一个Flutter实现。

所以今天我就站在居然的肩膀上,写了一个Wired Elements的Flutter实现,先上图,后面会挑选1,2个widget说一下是如何实现的。

 

 

 

 

 

巨人flutter_rough

上面说到了是站在巨人的肩膀上,就是说的flutter_rough,但是此组件库的作者没看到在维护了,似乎对这种样式的需求不太高?但是我们不管,just for fun!因为最新的flutter插件都需要null safety,但是flutter_rough并不是null safety的,已经有人建了一个issue提了这个问题,作者迟迟没有回复,所以这里我们没有办法,不能通过dependency的方式引入,只得把代码拷贝到自己的项目中自己做了null safety(这里已经在wired_elements的readme中说明标注了引入flutter_rough)。好了,基本工作已经完成,下面我们就可以开始切入我们wired_elements的组件库了。

初始化wired_elements组件库

首先,我们需要开发的是flutter&dart的package,所以我们按照官方的步骤一步步创建项目,做好初始化工作。

1. 创建项目:

flutter create --template=package wired_elements

2. 实现package wired_elements:如下图,lib文件夹下面有一个导出文件wired_elements.dart,2个文件夹roughsrc,其中rough是拷贝过来并做了null safety的flutter_rough组件库,src文件夹下面是我们需要实现的手写体widgets,可以看到目前为止一些基本的widgets都有了,但是还有很多没有比如日历、进度条等等,后面会继续迭代。src文件夹下面还有个canvas文件夹,里面包含的主要是一些通用canvas操作,方便开发我们的widgets,具体就不细说了,大家可以看源码,我们的主要任务是介绍一下具体的手写体widgets。

手写体按钮 - wired_button

大家可以参考web端的按钮,主要是边框和文字,文字手写体我们直接引入google的hand writing字体即可,比较简单,但是这种手写体的边框如何实现?

其实很简单,我们隐藏Flutter按钮的边框,然后外部包一层Container,实现自定义的decoration即可,这里的自定义decoration已经被flutter_rough实现好了,所以我们拿来主义即可。

@override
Widget buildWiredElement() {
return Container(padding: EdgeInsets.zero,height: 42.0,decoration: RoughBoxDecoration(shape: RoughBoxShape.rectangle,borderStyle: RoughDrawingStyle(width: 1,color: borderColor,),),child: SizedBox(height: double.infinity,child: TextButton(style: TextButton.styleFrom(primary: textColor,),child: child,onPressed: onPressed,),),
);
}

上面的代码片段我们使用了RoughBoxDecotation,它提供了具体的边框形状和样式供我们选择,我们这里使用了长方形并且指定了粗细和颜色,这样一个简单的wired_button就实现了,剩下的就是添加一些Flutter button本身的参数暴露出来即可。

如果大家看了wired_button的源码就知道,我们没有直接继承StatefulWidget或者StatelessWidget,我们自己写了WiredBaseWidget继承了StatelessWidget,然后wired_button继承WiredBaseWidget,并实现WiredBaseWidget的方法buildWiredElement()。

为什么要这么做呢?可以看到在WiredBaseWidget类中,我们包裹了一层RepaintBoundary,它是用来隔离屏幕canvas的重绘,因为我们使用了自定义的decoration,继承了BoxPainter,使用的是同一个canvas,这样屏幕上只要是用了这个自定义dcoration的就是使用了同一个canvas实例来绘制屏幕,如果屏幕上面有多个wired_button,那么当我们点击某一个按钮,他会触发重绘,如果我们不隔离重绘,其它的按钮也会跟着重绘,这并不是我们期望的,所以我们使用了RepaintBoundary来避免这个问题。

手写体滑件 - wired_slider

滑件在调节屏幕亮度,调节视频播放进度、调节音量等等地方都可以用到。可以参考Flutter material的Slider。

那么手写体滑件该怎么实现呢,在这里我们的思路是:隐藏Flutter Slider自己的进度条直线和当前位置的实心圆,通过设置activeColorinactiveColor的颜色为透明色即可,这样我们就可以用手写体直线和圆来覆盖当前位置,达到了UI的手写体样式。如何覆盖?用Stack布局。

@override
Widget build(BuildContext context) {
return Stack(alignment: Alignment.center,children: [// 覆盖Slider的直线SizedBox(height: 1,width: double.infinity,child: WiredCanvas(painter: WiredLineBase(x1: 0,y1: 0,x2: double.infinity,y2: 0,strokeWidth: 2,),fillerType: RoughFilter.HatchFiller,),),// 覆盖Slider的的位置圆圈Positioned(left: _getSliderWidth() * _currentSliderValue / widget.max - 12,child: SizedBox(height: 24.0,width: 24.0,child: WiredCanvas(painter: WiredCircleBase(diameterRatio: .7,fillColor: textColor,),fillerType: RoughFilter.HachureFiller,fillerConfig: FillerConfig.build(hachureGap: 1.0),),),),SliderTheme(data: SliderThemeData(trackShape: CustomTrackShape(),),child: Slider(value: _currentSliderValue,min: widget.min,max: widget.max,activeColor: Colors.transparent,inactiveColor: Colors.transparent,divisions: widget.divisions,label: widget.label,onChanged: (value) {bool result = false;if (widget.onChanged != null) {result = widget.onChanged!(value);}if (result) {setState(() {_currentSliderValue = value;});}},),),],
);
}

以上代码,我们使用了Stack布局并指定alignmentcenter,达到覆盖原有的Slider的效果。

因为Flutter Slider有一个默认的margins,但是我们并不想这样,我们覆盖的直线需要从头覆盖到尾部,如果有了margins就会导致覆盖的直线长于Flutter Slider,所以使用了SliderTheme包裹了Slider,然后自定义实现data,为什么这么做可以去掉margins?请参考此处。

我们绘制了手写体UI,但是Slider是可以改变他的value的,换句话说,Slider的那个实心圆是可以改变位置的,那么我们绘制的圆圈也要在拖动的时候跟着改变到正确的位置。从源码中看到可以按照比例来改变,我们知道当前Slider的值_currentSliderValue,我们也知道Slider的滑动最大值widget.max,这个时候如果知道Slider的物理像素值sliderWidth,我们就可以按照比例来计算出Slider的实心圆的位置x = sliderWidth * _currentSliderValue / widget.max,幸运的是我们可以通过下面的方法拿到sliderWidth

double _getSliderWidth() {
double width = 0;
try {var box = context.findRenderObject() as RenderBox;width = box.size.width;
} catch (e) {}return width;
}

有的同学会问了,看源码为什么还要减去12?因为实心球的直径是24!

等等!源码里面在initState方法为甚还有这么一段代码?如注释描述,第一次进入build方法拿不到sliderWidth,所以我们需要在第一次build完毕在调用setState方法强制让widgetbuild一次,从而拿到sliderWidth让初始化时实心球的位置也能正确展示。

// Delay for calculate the slider's width `_getSliderWidth()` during the next frame
Future.delayed(Duration(milliseconds: 0), () {setState(() {});
});

结尾

我们在这里主要挑选了具有代表性的2个widgets - wired_button和wired_slider介绍了一下,其他的目前已实现的widgets基本差不多,主要思路是去除已有的边框或者线条,用手写体来覆盖已有的UI,从而代码UI样式的改变。源码在此,pub.dev在此,欢迎大家提PR或者issues,如果对你有帮助希望能够给个star,谢谢!!!

这篇关于Flutter - 手写体widgets之wired_elements的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/396338

相关文章

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。

Flutter Button使用

Material 组件库中有多种按钮组件如ElevatedButton、TextButton、OutlineButton等,它们的父类是于ButtonStyleButton。         基本的按钮特点:         1.按下时都会有“水波文动画”。         2.onPressed属性设置点击回调,如果不提供该回调则按钮会处于禁用状态,禁用状态不响应用户点击。

flutter开发实战-flutter build web微信无法识别二维码及小程序码问题

flutter开发实战-flutter build web微信无法识别二维码及小程序码问题 GitHub Pages是一个直接从GitHub存储库托管的静态站点服务,‌它允许用户通过简单的配置,‌将个人的代码项目转化为一个可以在线访问的网站。‌这里使用flutter build web来构建web发布到GitHub Pages。 最近通过flutter build web,通过发布到GitHu

Flutter 中的低功耗蓝牙概述

随着智能设备数量的增加,控制这些设备的需求也在增加。对于多种使用情况,期望设备在需要进行控制的同时连接到互联网会受到很大限制,因此是不可行的。在这些情况下,使用低功耗蓝牙(也称为 Bluetooth LE 或 BLE)似乎是最佳选择,因为它功耗低,在我们的手机中无处不在,而且无需连接到更广泛的网络。因此,蓝牙应用程序的需求也在不断增长。 通过阅读本文,您将了解如何开始在 Flutter 中开

27. Remove Elements

题目: 解答: 类似题26,注意下删除后的元素的移动方式即可 代码: class Solution {public:int removeElement(vector<int>& nums, int val) {if(nums.empty()) return 0;int len = nums.size();int lenafter = 0, head = 0;for(int i

flutter开发多端平台应用的探索 下 (跨模块、跨语言通信之平台通道)

前文 Flutter 是一个跨平台的开发框架,它允许开发者使用相同的代码库来构建 iOS、Android、Web 和桌面应用程序。 上文flutter开发多端平台应用的探索 上(基本操作)-CSDN博客列举了一些特定平台的case(桌面端菜单,鼠标快捷键)的使用方法,有些是flutter提供了对应能力,只需要学习如何调API,有些事三方库支持,本文要探讨的平台通道是更为强大的工具,很多三方插件

Flutter-使用dio插件请求网络(get ,post,下载文件)

引入库:dio: ^2.1.13可直接运行的代码:包含了post,get 下载文件import 'package:flutter/material.dart';import 'package:dio/dio.dart';void main() {runApp(new MaterialApp(title: 'Container demo',home: new visitNetPage(),)

Flutter-选择附件,图片,视频。file_picker

仅供参考: 引入插件: file_picker: ^1.3.8 按照返回值,分了三组: // Single file path String filePath;第一组:返回文件地址 //选择任何文件 filePath = await FilePicker.getFilePath(type: FileType.ANY); // will let you pick one file path, fr

Flutter-图表显示charts_flutter

引入插件: charts_flutter: ^0.4.0 ChartFlutterBean import 'package:charts_flutter/flutter.dart';import 'package:myself_project/OrdinalSales%20.dart';class ChartFlutterBean {static List<Series<TimeSer