当前位置: 首页 > news >正文

手机网站开发基础免费网站生成器

手机网站开发基础,免费网站生成器,什么是网站收录,表白二维码生成器📚 目录 介绍组合多个组件自绘组件 Custompaint绘制边界RepaintBoundaryCustomPainter与Canvas画笔Paint绘制组件例子 本文学习和引用自《Flutter实战第二版》:作者:杜文 1. 介绍 当Flutter提供的现有组件无法满足我们的需求,或…

📚 目录

  1. 介绍
  2. 组合多个组件
  3. 自绘组件
    1. Custompaint
    2. 绘制边界RepaintBoundary
    3. CustomPainter与Canvas
    4. 画笔Paint
    5. 绘制组件例子

本文学习和引用自《Flutter实战·第二版》:作者:杜文

1. 介绍

当Flutter提供的现有组件无法满足我们的需求,或者我们为了共享代码需要封装一些通用组件,这时我们就需要自定义组件。在Flutter中自定义组件有三种方式:通过组合其他组件、自绘和实现RenderObject。

  • 组合多个Widget:通过已有组件来拼装组合成一个新的组件。
  • 通过CustomPaint自绘:通过Flutter中提供的CustomPaint和Canvas来实现UI自绘。
  • 通过RenderObject自绘:RenderObject中最终也是通过Canvas API来绘制的。而CustomPaint只是为了方便开发者封装的一个代理类。

2. 组合多个组件

实现一个渐变色背景,支持圆角,按下有涟漪效果的按钮,并且实现一个带动画的可以旋转的容器,每次点击按钮,容器旋转一点。

import 'package:flutter/material.dart';/// 定义
class HomePage extends StatefulWidget {const HomePage({super.key});State<HomePage> createState() => HomePageState();
}/// 实现
class HomePageState extends State<HomePage> {double myTurns = 0.0;Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Flutter Home'),),body: Container(alignment: Alignment.center,child: Column(children: [TurnBox(turns: myTurns,speed: 500,child: const Icon(Icons.refresh,size: 50,)),MyButton(colors: const [Colors.green, Colors.orangeAccent],height: 60.0,width: 300.0,borderRadius: const BorderRadius.all(Radius.circular(8)),onPressed: () {setState(() {myTurns += 0.2;});},child: const Text('放手一搏'),)],),));}
}
/// 自定义按钮
class MyButton extends StatelessWidget {const MyButton({Key? key,this.colors,this.width,this.height,this.onPressed,this.borderRadius,required this.child}): super(key: key);// 渐变色数组final List<Color>? colors;// 按钮属性final double? width;final double? height;final BorderRadius? borderRadius;// 点击回调事件final GestureTapCallback? onPressed;final Widget child;Widget build(BuildContext context) {ThemeData theme = Theme.of(context);List<Color> myColors =colors ?? [theme.primaryColor, theme.primaryColorDark];return DecoratedBox(decoration: BoxDecoration(gradient: LinearGradient(colors: myColors),borderRadius: borderRadius),child: Material(type: MaterialType.transparency,child: InkWell(splashColor: Colors.white70,highlightColor: Colors.transparent,borderRadius: borderRadius ?? BorderRadius.circular(8),onTap: onPressed,child: ConstrainedBox(constraints: BoxConstraints.tightFor(height: height, width: width),child: Center(child: Padding(padding: const EdgeInsets.all(8.0),child: DefaultTextStyle(style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18.0),child: child,),),),),),),);}
}/// 自定义旋转
class TurnBox extends StatefulWidget {const TurnBox({Key? key,this.turns = .0, // 旋转的“圈”数,一圈为360度,如0.25圈即90度this.speed = 200, // 过渡动画执行的总时长required this.child}) :super(key: key);final double turns;final int speed;final Widget child;TurnBoxState createState() => TurnBoxState();
}class TurnBoxState extends State<TurnBox> with SingleTickerProviderStateMixin {AnimationController? myController;void initState() {super.initState();myController = AnimationController(vsync: this,lowerBound: -double.infinity,upperBound: double.infinity);myController!.value = widget.turns;}void dispose() {myController!.dispose();super.dispose();}Widget build(BuildContext context) {return RotationTransition(turns: myController!,child: widget.child,);}void didUpdateWidget(TurnBox oldWidget) {super.didUpdateWidget(oldWidget);// 旋转角度发生变化时执行过渡动画if (oldWidget.turns != widget.turns) {myController!.animateTo(widget.turns,duration: Duration(milliseconds: widget.speed??200),curve: Curves.easeOut,);}}
}

3. 自绘组件

对于一些复杂或不规则的UI,我们可能无法通过组合其他组件的方式来实现。比如一个正六边形、一个渐变的圆形进度条、一个棋盘等。在Flutter中,提供了一个CustomPaint 组件,它可以结合画笔CustomPainter来实现自定义图形绘制。

3-1. CustomPaint

画笔CustomPainter绘制时我们需要提供前景或背景画笔,两者也可以同时提供。我们的画笔需要继承CustomPainter类,我们在画笔类中实现真正的绘制逻辑。

属性描述
painter背景画笔,会显示在子节点后面
foregroundPainter前景画笔,会显示在子节点前面
size当child为null时,代表默认绘制区域大小,如果有child则忽略此参数,画布尺寸则为child尺寸。如果有child但是想指定画布为特定大小,可以使用SizeBox包裹CustomPaint实现。
isComplex是否复杂的绘制,如果是,Flutter会应用一些缓存策略来减少重复渲染的开销。
willChange和isComplex配合使用,当启用缓存时,该属性代表在下一帧中绘制是否会改变。

3-2. 绘制边界RepaintBoundary

如果CustomPaint有子节点,为了避免子节点不必要的重绘并提高性能,通常情况下都会将子节点包裹在RepaintBoundary组件中,这样会在绘制时就会创建一个新的绘制层(Layer),其子组件将在新的Layer上绘制,而父组件将在原来Layer上绘制,也就是说RepaintBoundary 子组件的绘制将独立于父组件的绘制,RepaintBoundary会隔离其子节点和CustomPaint本身的绘制边界。

CustomPaint(// 指定画布大小size: Size(300, 300),painter: MyPainter(),child: RepaintBoundary(child:...), 
)

3-3. CustomPainter与Canvas

CustomPainter中提定义了一个虚函数paint。它有两个参数,Canvas和Size。Canvas是Flutter中绘制UI的底层组件,它是一个画布,包括各种绘制方法。Size是当前绘制区域大小。Canvas常用API如下:

API描述
drawLine画线
drawPoint画点
drawPath画路径
drawImage画图像
drawRect画矩形
drawCircle画圆
drawOval画椭圆
drawArc画圆弧

3-4. 画笔Paint

Flutter提供了Paint类来实现画笔。在Paint中,我们可以配置画笔的各种属性如粗细、颜色、样式等。如下例子:

// 创建一个画笔并配置其属性
var paint = Paint()..isAntiAlias = true // 是否抗锯齿..style = PaintingStyle.fill // 画笔样式:填充..color = Color(0x77cdb175); // 画笔颜色

3-5. 绘制组件例子

如下,是一个自定义绘制的饼状图。

  • 饼图完整代码
import 'dart:math';
import 'package:flutter/material.dart';typedef PieChartViewTap = Function(int index);
typedef OutsideText = Text Function(PieChartModel model, String scale);class PieChartView extends ImplicitlyAnimatedWidget {final List<PieChartModel> models;/// 是否显示内部圆final bool isShowHole;/// 内部圆的半径final double holeRadius;/// 内部圆的颜色final Color holeColor;/// 扇形分割线宽度final double spaceWidth;/// 溢出上方文字final OutsideText? outsideTopText;/// 溢出下方文字final OutsideText? outsideBottomText;/// 扇形点击事件final PieChartViewTap? onTap;const PieChartView(this.models, {Key? key,this.holeRadius = 55.0,this.isShowHole = true,this.holeColor = Colors.white,this.spaceWidth = 2.0,this.outsideTopText,this.outsideBottomText,this.onTap,Curve curve = Curves.linear,Duration duration = const Duration(milliseconds: 150),}) : super(key: key,curve: curve,duration: duration,);CustomPieViewState createState() => CustomPieViewState();
}class CustomPieViewState extends AnimatedWidgetBaseState<PieChartView> {CustomPieTween? customPieTween;List<PieChartModel> get end => widget.models.map((e) => PieChartModel(value: e.value, color: e.color, name: e.name, radius: e.radius)).toList();Widget build(BuildContext context) {return CustomPaint(size: Size.infinite,painter: PieChartPainter(context,customPieTween!.evaluate(animation),holeRadius: widget.holeRadius,isShowHole: widget.isShowHole,holeColor: widget.holeColor,spaceWidth: widget.spaceWidth,outsideTopText: widget.outsideTopText,outsideBottomText: widget.outsideBottomText,onTap: widget.onTap,),);}void forEachTween(TweenVisitor<dynamic> visitor) {customPieTween = visitor(customPieTween, end, (dynamic value) {return CustomPieTween(begin: value, end: end);}) as CustomPieTween;}
}class CustomPieTween extends Tween<List<PieChartModel>> {CustomPieTween({List<PieChartModel>? begin, List<PieChartModel>? end}): super(begin: begin, end: end);List<PieChartModel> lerp(double t) {List<PieChartModel> list = [];begin?.asMap().forEach((index, model) {list.add(model..radius = lerpDouble(model.radius, end?[index].radius ?? 100.0, t));});return list;}double lerpDouble(double radius, double radius2, double t) {if (radius == radius2) {return radius;}var d = (radius2 - radius) * t;var value = radius + d;return value;}
}class PieChartPaint extends CustomPaint {const PieChartPaint({Key? key}) : super(key: key);
}class PieChartPainter extends CustomPainter {final BuildContext context;final List<PieChartModel> models;final bool isShowHole;final double holeRadius;final Color holeColor;final double spaceWidth;final OutsideText? outsideTopText;final OutsideText? outsideBottomText;final PieChartViewTap? onTap;final List<Path> paths = [];final Path holePath = Path();Offset oldTapOffset = Offset.zero;PieChartPainter(this.context,this.models, {this.holeRadius = 60.0,this.isShowHole = true,this.holeColor = Colors.white,this.spaceWidth = 2.0,this.outsideTopText,this.outsideBottomText,this.onTap,});void paint(Canvas canvas, Size size) {//移动到中心点canvas.translate(size.width / 2, size.height / 2);//绘制饼状图_drawPie(canvas, size);//绘制分割线_drawSpaceLine(canvas);// 绘制中心圆_drawHole(canvas, size);// drawLineAndText(canvas);}bool shouldRepaint(CustomPainter oldDelegate) => oldDelegate != this;bool? hitTest(Offset position) {return _interceptTouchEvent(position);}bool _interceptTouchEvent(Offset offset) {if (oldTapOffset.dx == offset.dx && oldTapOffset.dy == offset.dy) {return false;}oldTapOffset = offset;for (int i = 0; i < paths.length; i++) {if (paths[i].contains(offset) && !holePath.contains(offset)) {onTap?.call(i);oldTapOffset = offset;return true;}}onTap?.call(-1);return false;}/// 绘制分割线void _drawSpaceLine(Canvas canvas) {var sumValue = models.fold<double>(0.0, (sum, model) => sum + model.value);var startAngle = 0.0;for (var model in models) {_drawLine(canvas, startAngle, model.radius);startAngle += model.value / sumValue * 360;_drawLine(canvas, startAngle, model.radius);}}void _drawLine(Canvas canvas, double angle, double radius) {var endX = cos(angle * pi / 180) * radius;var endY = sin(angle * pi / 180) * radius;Paint paint = Paint()..style = PaintingStyle.fill..color = Colors.white..strokeWidth = spaceWidth;canvas.drawLine(Offset.zero, Offset(endX, endY), paint);}/// 绘制饼状图void _drawPie(Canvas canvas, Size size) {var startAngle = 0.0;var sumValue = models.fold<double>(0.0, (sum, model) => sum + model.value);for (var model in models) {Paint paint = Paint()..style = PaintingStyle.fill..color = model.color;var sweepAngle = model.value / sumValue * 360;canvas.drawArc(Rect.fromCircle(radius: model.radius, center: Offset.zero),startAngle * pi / 180, sweepAngle * pi / 180, true, paint);Path path = Path();var centerX = size.width / 2;var centerY = size.height / 2;path.addArc(Rect.fromCircle(radius: model.radius, center: Offset(centerX, centerY)),startAngle * pi / 180,sweepAngle * pi / 180);path.moveTo(centerX, centerY);path.lineTo(centerX + cos(startAngle * pi / 180) * model.radius,centerY + sin(startAngle * pi / 180) * model.radius);path.lineTo(centerX + cos((sweepAngle + startAngle) * pi / 180) * model.radius,centerY + sin((sweepAngle + startAngle) * pi / 180) * model.radius);paths.add(path);// 为每一个区域绘制延长线和文字_drawLineAndText(canvas, size, model.radius, startAngle, sweepAngle, model);startAngle += sweepAngle;}}/// 绘制延长线和文字void _drawLineAndText(Canvas canvas, Size size, double radius,double startAngle, double sweepAngle, PieChartModel model) {var ratio = (sweepAngle / 360.0 * 100).toStringAsFixed(2);var top = outsideTopText?.call(model, ratio) ??Text(model.name,style: const TextStyle(color: Colors.black38),);var topTextPainter = getTextPainter(top);var bottom = outsideBottomText?.call(model, ratio) ??Text("$ratio%",style: const TextStyle(color: Colors.black38),);var bottomTextPainter = getTextPainter(bottom);// 绘制横线// 计算开始坐标以及转折点的坐标var startX = radius * (cos((startAngle + (sweepAngle / 2)) * (pi / 180)));var startY = radius * (sin((startAngle + (sweepAngle / 2)) * (pi / 180)));var firstLine = radius / 5;var secondLine =max(bottomTextPainter.width, topTextPainter.width) + radius / 4;var pointX = (radius + firstLine) *(cos((startAngle + (sweepAngle / 2)) * (pi / 180)));var pointY = (radius + firstLine) *(sin((startAngle + (sweepAngle / 2)) * (pi / 180)));// 计算坐标在左边还是在右边// 并计算横线结束坐标// 如果结束坐标超过了绘制区域,则改变结束坐标的值var endX = 0.0;// 距离绘制边界的偏移量var marginOffset = 20.0;if (pointX - startX > 0) {endX = min(pointX + secondLine, size.width / 2 - marginOffset);secondLine = endX - pointX;} else {endX = max(pointX - secondLine, -size.width / 2 + marginOffset);secondLine = pointX - endX;}Paint paint = Paint()..style = PaintingStyle.fill..strokeWidth = 1..color = Colors.grey;// 绘制延长线canvas.drawLine(Offset(startX, startY), Offset(pointX, pointY), paint);canvas.drawLine(Offset(pointX, pointY), Offset(endX, pointY), paint);// 文字距离中间横线上下间距偏移量var offset = 4;var textWidth = bottomTextPainter.width;var textStartX = 0.0;textStartX = _calculateTextStartX(pointX, startX, textWidth, secondLine, textStartX, offset);bottomTextPainter.paint(canvas, Offset(textStartX, pointY + offset));textWidth = topTextPainter.width;var textHeight = topTextPainter.height;textStartX = _calculateTextStartX(pointX, startX, textWidth, secondLine, textStartX, offset);topTextPainter.paint(canvas, Offset(textStartX, pointY - offset - textHeight));// 绘制文字前面的小圆点paint.color = model.color;canvas.drawCircle(Offset(textStartX - 8, pointY - 4 - topTextPainter.height / 2),4,paint);}double _calculateTextStartX(double stopX, double startX, double w,double line2, double textStartX, int offset) {if (stopX - startX > 0) {if (w > line2) {textStartX = (stopX + offset);} else {textStartX = (stopX + (line2 - w));}} else {if (w > line2) {textStartX = (stopX - offset - w);} else {textStartX = (stopX - (line2 - w) - w);}}return textStartX;}TextPainter getTextPainter(Text text) {TextPainter painter = TextPainter(locale: Localizations.localeOf(context),maxLines: text.maxLines,textDirection: TextDirection.ltr,text: TextSpan(text: text.data,style: text.style,),);painter.layout();return painter;}/// 绘制中心圆void _drawHole(Canvas canvas, Size size) {if (isShowHole) {holePath.reset();Paint paint = Paint()..style = PaintingStyle.fill..color = Colors.white;canvas.drawCircle(Offset.zero, holeRadius, paint);var centerX = size.width / 2;var centerY = size.height / 2;holePath.addArc(Rect.fromCircle(radius: holeRadius, center: Offset(centerX, centerY)),0,360 * pi / 180);}}
}/// 数据
class PieChartModel {double value;Color color;String name;double radius;PieChartModel({required this.value,required this.color,required this.name,this.radius = 100,});
}
  • 使用
import 'package:flutter/material.dart';
import 'package:demo1/widget/MyPieChart.dart';/// 定义
class HomePage extends StatefulWidget {const HomePage({super.key});State<HomePage> createState() => HomePageState();
}/// 实现
class HomePageState extends State<HomePage> {double myTurns = 0.0;Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Flutter Home'),),body: Container(alignment: Alignment.center,// 饼图child: PieChartView([PieChartModel(value: 35,name: 'A',color: Colors.blue,radius: 100,),PieChartModel(value: 15,name: 'B',color: Colors.red,radius: 100,),PieChartModel(value: 22,name: 'C',color: Colors.yellow,radius: 100,),PieChartModel(value: 18,name: 'D',color: Colors.orange,radius: 100,),PieChartModel(value: 39,name: 'F',color: Colors.green,radius: 100,),],),));}
}

本次分享就到这儿啦,我是鹏多多,如果您看了觉得有帮助,欢迎评论,关注,点赞,转发,我们下次见~

往期文章

  • 手把手教你搭建规范的团队vue项目,包含commitlint,eslint,prettier,husky,commitizen等等
  • Web Woeker和Shared Worker的使用以及案例
  • Vue2全家桶+Element搭建的PC端在线音乐网站
  • vue3+element-plus配置cdn
  • 助你上手Vue3全家桶之Vue3教程
  • 助你上手Vue3全家桶之VueX4教程
  • 助你上手Vue3全家桶之Vue-Router4教程
  • 超详细!Vue的九种通信方式
  • 超详细!Vuex手把手教程
  • 使用nvm管理node.js版本以及更换npm淘宝镜像源
  • vue中利用.env文件存储全局环境变量,以及配置vue启动和打包命令
  • 超详细!Vue-Router手把手教程

个人主页

  • CSDN
  • GitHub
  • 简书
  • 博客园
  • 掘金
http://www.hrbkazy.com/news/11186.html

相关文章:

  • 网站备案有什么好处高佣金app软件推广平台
  • 网站建设的职责seo就业指导
  • led网站建设方案模板南宁优化推广服务
  • 做网站网页维护 手机App 开发网站推广常用方法
  • 咖啡豆网站模板营销软文800字范文
  • 静态网站开发课程网沧州网络推广外包公司
  • 电信100m光纤做网站最新的即时比分
  • wordpress静态博客主题seo站长之家
  • 高端建站网站的下载百度app到桌面
  • 深圳建设工程招投标网站谷歌推广开户多少费用
  • 我的网站怎么转网页呢网络舆情分析师
  • 智能软件开发方向怎么样网站优化推广软件
  • 网站建设网址深圳百度快速排名提升
  • wordpress单机版安徽网络关键词优化
  • 重庆市城市建设综合开发办网站成都最新疫情
  • 做宽带销售网站域名服务器ip地址查询
  • 做企业网站用drupal7志鸿优化设计电子版
  • 网站首页怎么设计国际新闻界
  • 响应式网站建设必推全网天下网络推广的具体方式
  • 公司网站建设 宁波网络营销优化培训
  • 树莓派可以做网站的服务器吗seo技巧与技术
  • 电子商务网站建设流程重庆关键词优化软件
  • 简述网站建设的主要内容厦门关键词排名优化
  • 做网站优化有什么途径网页分析工具
  • wordpress 3 企业主题淘宝seo搜索引擎原理
  • 做直销网站公司谷歌推广和seo
  • 无极ip求好用的seo软件
  • 上市企业网站建设百度seo如何优化关键词
  • 网站开发包括网站过程seo搜索引擎优化关键词
  • 哪个网站可以做视频外链如何推广seo