本节目标

[1]. 了解手势在画布中的使用方式
[2]. 练习绘制,并根据手指滑动完成控制杆的绘制
[3]. 练习绘制,并根据手指滑动完成【刻度尺】的绘制
[4]. 了解如何限制绘制区域

一、控制柄组件

下面的控制器很能够体现出手势操作在画布中的使用。其中小球的圆心只能在大圆中移动。可以回调出移动的方向和距离。是一个非常好的控制案例。其中包含一些角度运算、边界控制的技巧也很值得去体会。

1. 基本思路

如下所示,是控制柄的4个瞬间。灰色区域是组件的占位空间。小圆的圆心只能在大圆区域内移动。所以组件尺寸size和校园半径handleRadius可以确定大圆的半径bgR=size/2-handleRadius 在移动的过程中。获取到触点位置。计算小圆偏移量即可。

2. 静态效果

先完成下面的静态效果

class HandleWidget extends StatefulWidget {final double size;final double handleRadius;HandleWidget({Key? key, this.size = 160.0, this.handleRadius = 20.0}): super(key: key);@override_HandleWidgetState createState() => _HandleWidgetState();
}class _HandleWidgetState extends State<HandleWidget> {@overrideWidget build(BuildContext context) {return CustomPaint(size: Size(widget.size, widget.size),painter: _HandlePainter(handleR: widget.handleRadius));}
}class _HandlePainter extends CustomPainter {var _paint = Paint();var handleR;_HandlePainter({this.handleR}) {_paint..color = Colors.blue..style = PaintingStyle.fill..isAntiAlias = true;}@overridevoid paint(Canvas canvas, Size size) {canvas.clipRect(Offset.zero & size);final bgR = size.width / 2 - handleR;canvas.translate(size.width / 2, size.height / 2);_paint.color = _paint.color.withAlpha(100);canvas.drawCircle(Offset(0, 0), bgR, _paint);_paint.color = _paint.color.withAlpha(150);canvas.drawCircle(Offset(0, 0), handleR, _paint);}@overridebool shouldRepaint(_HandlePainter oldDelegate) =>oldDelegate.handleR != handleR;
}

3. 添加手势控制

通过GestureDetector可以监听到手指在组件上的触碰信息。
在onPanUpdate时可以获取到触点信息。onPanEnd是手指离开的时候。可以重置偏移。
在ValueNotifier 对象中传入画板用来触发画布更新

void main() {// 确定初始化WidgetsFlutterBinding.ensureInitialized();//横屏SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);//全屏显示SystemChrome.setEnabledSystemUIOverlays([]);runApp(MyApp());
}class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',debugShowCheckedModeBanner: false,theme: ThemeData(primarySwatch: Colors.blue,),home: Scaffold(body: Center(child: HandleWidget(),),));}
}
class HandleWidget extends StatefulWidget {final double size;final double handleRadius;HandleWidget({Key? key, this.size = 160, this.handleRadius = 20.0}): super(key: key);@override_HandleWidgetState createState() => _HandleWidgetState();
}class _HandleWidgetState extends State<HandleWidget> {ValueNotifier<Offset> _offset = ValueNotifier(Offset.zero);@overrideWidget build(BuildContext context) {return GestureDetector(onPanEnd: reset,onPanUpdate: parser,child: CustomPaint(size: Size(widget.size, widget.size),painter: _HandlePainter(color: Colors.green,handleR: widget.handleRadius,offset: _offset)));}reset(DragEndDetails details) {_offset.value = Offset.zero;}parser(DragUpdateDetails details) {final offset = details.localPosition;double dx = 0.0;double dy = 0.0;dx = offset.dx - widget.size / 2;dy = offset.dy - widget.size / 2;var rad = atan2(dx, dy);if (dx < 0) {rad += 2 * pi;}var bgR = widget.size / 2 - widget.handleRadius;var thta = rad - pi / 2; //旋转坐标系90度if (sqrt(dx * dx + dy * dy) > bgR) {dx = bgR * cos(thta);dy = -bgR * sin(thta);}_offset.value = Offset(dx, dy);}
}class _HandlePainter extends CustomPainter {var _paint = Paint();final ValueNotifier<Offset> offset;final Color color;var handleR;_HandlePainter({this.handleR,required this.offset, this.color = Colors.blue}): super(repaint: offset) ;@overridevoid paint(Canvas canvas, Size size) {canvas.clipRect(Offset.zero & size);final bgR = size.width / 2 - handleR;canvas.translate(size.width / 2, size.height / 2);_paint.style = PaintingStyle.fill;_paint.color = color.withAlpha(100);canvas.drawCircle(Offset(0, 0), bgR, _paint);_paint.color = color.withAlpha(150);canvas.drawCircle(Offset(offset.value.dx, offset.value.dy), handleR, _paint);_paint.color = color;_paint.style = PaintingStyle.stroke;canvas.drawLine(Offset.zero, offset.value, _paint);}@overridebool shouldRepaint(_HandlePainter oldDelegate) =>oldDelegate.offset != offset ||oldDelegate.color != color ||oldDelegate.handleR != handleR;
}

4.监听器

像switch和Slider这样的组件都会向外界提供回调方法。我们也可以将距离和角度回调给使用者。
比如下面通过控制器旋转来对蓝色的Container进行操作

void main() {// 确定初始化WidgetsFlutterBinding.ensureInitialized();//横屏SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);//全屏显示SystemChrome.setEnabledSystemUIOverlays([]);runApp(MyApp());
}class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',debugShowCheckedModeBanner: false,theme: ThemeData(primarySwatch: Colors.blue,),home: HomePage());}
}class HomePage extends StatefulWidget {@override_HomePageState createState() => _HomePageState();
}class _HomePageState extends State<HomePage> {double _rotate = 0;@overrideWidget build(BuildContext context) {return Scaffold(body: Center(child: Row(mainAxisSize: MainAxisSize.min,children: [Transform.rotate(angle: _rotate,child:Container(color: Colors.blue,alignment: Alignment.center,width: 100,height: 100,),),HandleWidget(onMove: _onMove,),],),),);}void _onMove(double rotate, double distance) {setState(() {_rotate= rotate;});}
}
class HandleWidget extends StatefulWidget {final double size;final double handleRadius;final void Function(double rotate, double distance) onMove;HandleWidget({Key? key,this.size = 160,this.handleRadius = 20.0,required this.onMove}): super(key: key);@override_HandleWidgetState createState() => _HandleWidgetState();
}class _HandleWidgetState extends State<HandleWidget> {ValueNotifier<Offset> _offset = ValueNotifier(Offset.zero);@overrideWidget build(BuildContext context) {return RepaintBoundary(child: GestureDetector(onPanEnd: reset,onPanUpdate: parser,child: CustomPaint(size: Size(widget.size, widget.size),painter: _HandlePainter(color: Colors.green,handleR: widget.handleRadius,offset: _offset))),);}reset(DragEndDetails details) {_offset.value = Offset.zero;widget.onMove(0, 0);}parser(DragUpdateDetails details) {final offset = details.localPosition;double dx = 0.0;double dy = 0.0;dx = offset.dx - widget.size / 2;dy = offset.dy - widget.size / 2;var rad = atan2(dx, dy);if (dx < 0) {rad += 2 * pi;}var bgR = widget.size / 2 - widget.handleRadius;var thta = rad - pi / 2; //旋转坐标系90度var d = sqrt(dx * dx + dy * dy);if (d > bgR) {dx = bgR * cos(thta);dy = -bgR * sin(thta);}widget.onMove(thta, d);_offset.value = Offset(dx, dy);}
}class _HandlePainter extends CustomPainter {var _paint = Paint();final ValueNotifier<Offset> offset;final Color color;var handleR;_HandlePainter({this.handleR,required this.offset, this.color = Colors.blue}): super(repaint: offset);@overridevoid paint(Canvas canvas, Size size) {canvas.clipRect(Offset.zero & size);final bgR = size.width / 2 - handleR;canvas.translate(size.width / 2, size.height / 2);_paint.style = PaintingStyle.fill;_paint.color = color.withAlpha(100);canvas.drawCircle(Offset(0, 0), bgR, _paint);_paint.color = color.withAlpha(150);canvas.drawCircle(Offset(offset.value.dx, offset.value.dy), handleR, _paint);_paint.color = color;_paint.style = PaintingStyle.stroke;canvas.drawLine(Offset.zero, offset.value, _paint);}@overridebool shouldRepaint(_HandlePainter oldDelegate) =>oldDelegate.offset != offset ||oldDelegate.color != color ||oldDelegate.handleR != handleR;
}

二、绘制刻度尺

通过绘制如下的滑动刻度尺,来练习一下水平滑动的操作以及刻度的绘制。其中包含对边界值的限制。一些数值计算等使用的技巧。

1. 组件介绍

需要指定的属性有,最大值max,最小值min,刻度尺可以在这之间滑动
并且给出一个onChanged的回调方法。将当前值直接传给用户。在画板中最重要的属性就是水平滑动的距离。所以在组件中使用一个ValueNotifier的对象传给画板。用于更新重绘。
通过GestureDetector在水平方向进行监听。和和兴时进行移动过程中的点位计算。逻辑我放在_paese方法中。

import 'dart:math';import 'package:flutter/material.dart';
import 'dart:ui' as ui;class RulerChooser extends StatefulWidget {final Size size;final void Function(double) onChanged;final int min;final int max;RulerChooser({Key? key,required this.onChanged,this.max = 200,this.min = 100,this.size = const Size(240.0, 60)}): super(key: key);@override_RulerChooserState createState() => _RulerChooserState();
}class _RulerChooserState extends State<RulerChooser> {ValueNotifier<double> _dx = ValueNotifier(0.0);@overrideWidget build(BuildContext context) {return GestureDetector(onPanUpdate: _parser,child: CustomPaint(size: widget.size,painter: _HandlePainter(dx: _dx, max: widget.max, min: widget.min)),);}double dx = 0;void _parser(DragUpdateDetails details) {dx += details.delta.dx;if (dx > 0) {dx = 0.0;}var limitMax = -(widget.max - widget.min) * (_kSpacer + _kStrokeWidth);if (dx < limitMax) {dx = limitMax;}_dx.value = dx;if (widget.onChanged != null) {widget.onChanged(details.delta.dx / (_kSpacer + _kStrokeWidth));}}
}const double _kHeightLevel1 = 20; // 短线长
const double _kHeightLevel2 = 25; // 5 线长
const double _kHeightLevel3 = 30; //10 线长
const double _kPrefixOffSet = 5; // 左侧偏移
const double _kVerticalOffSet = 12; // 线顶部偏移
const double _kStrokeWidth = 2; // 刻度宽
const double _kSpacer = 4; // 刻度间隙
const List<Color> _kRulerColors = [// 渐变色Color(0xFF1426FB),Color(0xFF6080FB),Color(0xFFBEE0FB),
];
const List<double> _kRulerColorStops = [0.0, 0.2, 0.8];class _HandlePainter extends CustomPainter {var _paint = Paint();Paint _pointPaint = Paint();final ValueNotifier<double> dx;final int max;final int min;_HandlePainter({required this.dx,required this.max,required this.min}) : super(repaint: dx) {_paint..strokeWidth = _kStrokeWidth..shader = ui.Gradient.radial(Offset(0, 0), 25, _kRulerColors, _kRulerColorStops, TileMode.mirror);_pointPaint..color = Colors.purple..strokeWidth = 4..strokeCap = StrokeCap.round;}@overridevoid paint(Canvas canvas, Size size) {canvas.clipRect(Offset.zero & size);drawArrow(canvas);canvas.translate(dx.value, 0);drawRuler(canvas);}// 绘制刻度void drawRuler(Canvas canvas) {double y = _kHeightLevel1;for (int i = min; i < max + 5; i++) {if (i % 5 == 0 && i % 10 != 0) {y = _kHeightLevel2;} else if (i % 10 == 0) {y = _kHeightLevel3;_simpleDrawText(canvas, i.toString(),offset: Offset(-3, _kHeightLevel3 + 5));} else {y = _kHeightLevel1;}canvas.drawLine(Offset.zero, Offset(0, y), _paint);canvas.translate(_kStrokeWidth + _kSpacer, 0);}}// 绘制三角形尖角void drawArrow(Canvas canvas) {var path = Path()..moveTo(_kStrokeWidth / 2 + _kPrefixOffSet, 3)..relativeLineTo(-3, 0)..relativeLineTo(3, _kPrefixOffSet)..relativeLineTo(3, -_kPrefixOffSet)..close();canvas.drawPath(path, _pointPaint);canvas.translate(_kStrokeWidth / 2 + _kPrefixOffSet, _kVerticalOffSet);}void _simpleDrawText(Canvas canvas, String str,{Offset offset = Offset.zero}) {var builder = ui.ParagraphBuilder(ui.ParagraphStyle())..pushStyle(ui.TextStyle(color: Colors.black, textBaseline: ui.TextBaseline.alphabetic),)..addText(str);canvas.drawParagraph(builder.build()..layout(ui.ParagraphConstraints(width: 11.0 * str.length)),offset);}@overridebool shouldRepaint(_HandlePainter oldDelegate) =>oldDelegate.dx != dx || oldDelegate.min != min || oldDelegate.max != max;
}

Flutter绘制指南10-手势在绘制中的使用相关推荐

  1. python画熊猫论文_Python数据可视化之美:专业图表绘制指南(全彩)

    Python数据可视化之美:专业图表绘制指南(全彩)电子书 系统性地介绍Python 的绘图语法系统,包括matplotlib.Seaborn.plotnine 包,以及用于地理空间数据可视化的Bas ...

  2. 【Flutter】动画学习(一)Canvas绘制

    文章目录 1 CustomPainter介绍 1.1 paint方法 1.2 shouldRepaint方法 1.3 Paint 1.4 CustomPainter 1.5 创建项目 2 api介绍 ...

  3. Flutter学习笔记 —— CustomPainter自定义画布绘制爱心

    Flutter学习笔记 -- CustomPainter自定义画布绘制爱心 前言 效果图 代码示例 温馨提示 结束语 前言 最近在学习Flutter中 Canvas相关内容,今天尝试写了一个爱心Dem ...

  4. html5中Canvas、绘制线条模糊、常见绘制工具、绘制基本图形、绘制图片、面向对象的方式绘制图形图片、绘制文本、帧动画绘制

    Canvas容器: canvas标签用来定义图像的容器,必须配合脚本来绘制图像,canvas也运用于游戏开发.注意:canvas绘制图时会出现线条模糊情况,这是因为显示屏像素和canvas中定义的一个 ...

  5. android 动态绘制布局,Android代码和绘制曲线中按钮和绘图板的动态布局

    时间: 2019年1月11日 本文向您介绍Android代码中的按钮和绘图板的动态布局和绘制曲线,主要包括示例android 动态绘制曲线,应用技巧,基本知识和知识android 动态绘制曲线,包括A ...

  6. activiti创建子流程_OA流程图绘制指南

    OA流程图绘制指南 必填与非必填参数 自下而上查询时是否允许下级查看 当前流程属于哪个部门 可不填默认使用添加人部门 业务流程除了审核分组不用填其余都是必填项 指定流程申请人或部门 点击空白处 → 右 ...

  7. dx绘制2d图像_在DirectX 中进行2D渲染

    http://flcstudio.blog.163.com/blog/static/756035392008115111123672/ 最近,我看到很多关于DirectX8在最新的API中摒弃Dire ...

  8. [-Flutter 自组篇-] 蛛网图+绘制+动画实践

    在Android的时候自定义过蛛网图,花了半天时间.复刻到Flutter只用了不到20分钟 不得不说Flutter中的Canvas对安卓玩家还是非常友好的,越来越觉得Flutter非常有趣. 在视图方 ...

  9. 小白也能学会的模拟屏幕滑动与手势密码绘制

    前言 App自动化测试中有两个很重要的操作,屏幕滑动与绘制手势密码.目前很多App在启动时,都存在启动时的引导动画或者加载上下文内容时需要手动上滑或者下滑加载页面,所以在自动化测试的过程中模拟手的滑动 ...

最新文章

  1. 美国中学生被学校监控,实时位置、和谁接触一览无余
  2. js和php获取页面的url信息
  3. zpf 获取表单等数据的用法
  4. php中abstract和interface的区别
  5. [Python图像处理] 二十.图像量化处理和采样处理及局部马赛克特效
  6. Oracle 时区(TimeZone )-- DST
  7. TP中给select下拉框选中的内容搜索选中seleted
  8. ActiveMQ(三):ActiveMQ的安全机制、api及订阅模式demo
  9. 手把手教你写竞品分析
  10. Linux虚拟文件系统之文件读取(sys_read())
  11. android切图双数,浅谈网页设计切图规范
  12. CodeBlocks下载与安装
  13. java 二维向量_二维向量的叉积是标量还是向量?
  14. # 数学基础task 01 函数极限与连续性
  15. autocad 如何摆正显示_CAD怎么调整坐标系显示?
  16. 计算机桌面文字重影,电脑桌面字有重影怎么办
  17. 设计任务调度依赖配置表
  18. 从零开始做运营 进阶篇
  19. 学生用台灯什么光对眼睛好?开学季精选真正适合孩子的护眼台灯
  20. 天空之城(献给我喜欢的女孩,杨)

热门文章

  1. Java8日期时间API,Java高级多线程面试
  2. 怎样免费在线压缩MKV格式视频
  3. 懂得生活,懂得自律,懂得自由
  4. 电视墙解码服务器不在线,电视墙服务器有多少个功能?
  5. python的pandas计算5天滑动平均气温,并批量计算春季起始日
  6. 双移线驾驶员模型,多项式双移线模拟 采用多项式搭建双移线期望路径,基于郭孔辉单点预瞄理论,搭建双移线simulink驾驶员模型
  7. Unity3D 游戏开发必备软件或服务推荐
  8. java毕业设计后勤管理系统在线报修系统mybatis+源码+调试部署+系统+数据库+lw
  9. 各家大牛免费邮箱的每天发信数量限制
  10. xss实现获取网站源码