flutter新手蒙版引导功能插件
题记 —— 执剑天涯,从你的点滴积累开始,所及之处,必精益求精,即是折腾每一天。
重要消息
- flutter中网络请求dio使用分析 视频教程在这里
- Flutter 从入门实践到开发一个APP之UI基础篇 视频
- Flutter 从入门实践到开发一个APP之开发实战基础篇
- flutter跨平台开发一点一滴分析系列文章系列文章 在这里了
本文将描述 在 flutter 项目中实现新手功能引导框功能 1、flutter_guidance_plugin 插件使用 2、组件 CustomPaint 与 CustomPainter 的使用分析 3、组件 WillPopScope 的使用分析 4、canvas 中手势识别 GestureDetector 使用分析 5、Container 实现蒙版效果 6、Canvas 绘制文本分析
在 flutter 项目中的配置文件 pubspec.yaml 中添加依赖 插件源码在这里
#新手蒙版引导插件
flutter_guidance_plugin:
#git 方式依赖
git:
#仓库地址
url: https://github.com/zhaolongs/flutter_guidance_plugin.git
# 分支
ref: master
导包
import 'package:flutter_guidance_plugin/flutter_guidance_plugin.dart';
创建指引数据
///创建新手指引数据
randomTestData(){
List<CurvePoint> curvePointList = [];
for (int i = 0; i < 5; i++) {
///创建指引
CurvePoint curvePoint = CurvePoint(0, 0);
if(i==0){
///x,y 指定指引位置 从0-1 ,手机屏幕左上角开始为(0,0)位置,右下角为(1,1)
curvePoint.x = double.parse("0.5");
curvePoint.y = double.parse("0.17");
///为引导框内显示的文字
curvePoint.tipsMessage = "点击这里可 显示可滑动的引导蒙版";
}else if(i==1){
curvePoint.x = double.parse("0.5");
curvePoint.y = double.parse("0.1");
curvePoint.tipsMessage = "点击这里可 再次显示 引导蒙版";
}else{
curvePoint.x = double.parse("0.${_randomBit(3)}");
curvePoint.y = double.parse("0.${_randomBit(3)}");
curvePoint.tipsMessage = "这是随机的引导消息内容$i";
}
curvePointList.add(curvePoint);
}
return curvePointList;
}
在这里我们使用到了 CurvePoint ,这是一个自定义的类,用来保存页面数据行为
class CurvePoint {
///x,y 指定指引位置 从0-1 ,手机屏幕左上角开始为(0,0)位置,右下角为(1,1)
double x;
double y;
///为引导框内显示的文字
String tipsMessage;
String nextString;
CurvePoint(this.x, this.y,
{this.tipsMessage = "--", this.nextString = "下一步"});
}
然后触发蒙版指引-> 在刚刚进入页面时触发或者点击按钮时触发
void show1() {
///获取模拟数据
List<CurvePoint> curvePointList = randomTestData();
///参数一 上下文对象
///参数二 [curvePointList]用户指引数据集合
///参数三 [pointX][pointY] 当 curvePointList 为null 时起作用 可用作只有一个引导指引功能页面
///参数五 [isSlide] 为true 时,提示框可以移动
///参数六 [logs] 为true 时输出Log日志
showBeginnerGuidance(context, curvePointList: curvePointList,pointX: 0,pointY: 0,isSlide:false ,logs: true);
}
void show2() {
///获取模拟数据
List<CurvePoint> curvePointList = randomTestData();
showBeginnerGuidance(context, curvePointList: curvePointList,logs: true,isSlide: true);
}
最终实现的效果就如上图中所示 源码在这里
在这里实现的蒙版效果从两方面来讲:
我们这里的蒙版引导层实际上是通过 Navigator push 了一个 Widget ,那么我们需要实现 push 出来的新在页面层要保持透明,那么在这里是这样实现的:
///背景透明的跳转
Navigator.of(context).push(PageRouteBuilder(
opaque: false,
pageBuilder: (context, animation, secondaryAnimation) {
///GuideSplashPage 是引导页面具体实现
return GuideSplashPage(curvePointList:curvePointList,pointX: pointX,pointY: pointY,textColor: tipsTextColor,isSlide:isSlide,clickCallback:clickCallback);
}));
}
PageRouteBuilder 在 flutter 中用来自定义路由切换功能,在这里通过 pageBuilder 函数来构建将到 跳转的页面
对于参数 opaque ,我们可以理解为用来配置开启的页面背景是否透明,这里配置的为 false ,如果配置为 true,那么页面背景将不会透明。
在这个 PageRouteBuilder 中,我们还可以添加一个渐变动画,使用页面切换效果体验更佳
///背景透明的跳转
Navigator.of(context).push(PageRouteBuilder(
opaque: false,
pageBuilder: (context, animation, secondaryAnimation) {
///GuideSplashPage 是引导页面具体实现
return GuideSplashPage(
curvePointList: curvePointList,
pointX: pointX,
pointY: pointY,
textColor: tipsTextColor,
isSlide: isSlide,
clickCallback: clickCallback);
},
transitionsBuilder: (BuildContext context, Animation<double> animation1,
Animation<double> animation2, Widget child) {
/// 渐变过渡
return FadeTransition(
///渐变过渡 0.0-1.0
opacity: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
///动画样式
parent: animation1,
///动画曲线
curve: Curves.fastOutSlowIn,
),),
child: child,
);
},),);
这里我们添加了一个 transitionsBuilder ,它是用来控制页面效果效果的,例如这里添加了一个 FadeTransition 渐变过渡效果
从上述图片效果中我们可以看到 引导蒙版层出现与消失的时候是有 透明度渐变的效果的。
当新景透明后,我们需要设置一个如上述图片中的黑色蒙版效果,在这里使用的是 Container 填充整个页面,然后设置一个透明度的背景
Container(
color: Color(0x90000000),
... ...
)
在我们的蒙版实现页面 GuideSplashPage 中,我们使用到了 组件 WillPopScope,在Flutter中通过WillPopScope来实现返回按钮拦截
@override
Widget build(BuildContext context) {
double padding = (MediaQuery.of(context).size.width / 9);
double height = MediaQuery.of(context).size.height;
double width = MediaQuery.of(context).size.width;
///在Flutter中通过WillPopScope来实现返回按钮拦截
return WillPopScope(
child: Container(
height: height,
width: width,
color: Color(0x90000000),
child: ...
///在Android手机中,当点击后退按钮的时候,会回调此事件
///在这里返回 false 表示拦截事件
onWillPop: () async {
return Future.value(false);
});
}
其实关于气泡提示实现我们也可以使用一张切图来实现,不过无小编喜欢搞事件,所以在这里使用了 三阶贝塞尔曲线 cubicTo 来绘制这个气泡,三阶贝塞尔曲线核心就是做好 控制点与目标点的计算,如下图小编绘制了坐标分析图
这的坐标计算代码比较多,所以小编就不放代码了,可以到源码中查看哈,三阶贝塞尔曲线 cubicTo 使用分析在这里。
Canvas 中并没有直接像 Android ios 中提供绘制文字的方法,在这里 我们是通过段落构建器来综合实现的绘制文本功能
///[textWidth] 文本的宽度
///[textOffset] 文本绘制的开始位置 左上角
///[text] 绘制的文字内容
void drawTextFunction(
double textWidth, Offset textOffset, Canvas canvas, String text,
{Color textColor = Colors.blue}) {
///创建画笔
var textPaint = Paint();
///设置画笔颜色
textPaint.color = textColor;
/// 新建一个段落建造器,然后将文字基本信息填入;
ui.ParagraphBuilder pb = ui.ParagraphBuilder(ui.ParagraphStyle(
textAlign: TextAlign.center,
fontWeight: FontWeight.w400,
fontStyle: FontStyle.normal,
fontSize: 15.0,
));
///设置文字的样式
pb.pushStyle(ui.TextStyle(color: textColor));
if (text == null || text.length == 0) {
text = "--";
} else if (text.length > 20) {
text = text.substring(0, 20);
text += "...";
}
pb.addText(text);
// 设置文本的宽度约束
ui.ParagraphConstraints pc = ui.ParagraphConstraints(width: textWidth);
// 这里需要先layout,将宽度约束填入,否则无法绘制
ui.Paragraph paragraph = pb.build()..layout(pc);
///偏移量在这里指的是文字左上角的 位置
canvas.drawParagraph(paragraph, textOffset);
}
}
需要注意的是这里的ParagraphBuilder TextStyle 我们通过 ui. 别名来调用的,这是因为
import 'dart:ui' as ui; //这里用as取个别名,有库名冲突
import 'package:flutter/material.dart';
在这两个包内,存在相同名字的 ParagraphBuilder 与 TextStyle ,而我们需要使用 ui 包中的,所以在这里通过 别名调用。
在这里,我们不能使用 Button 或者 InkWell 等,也不好使用,因为这里的下一步,我们是通过 Canvas 绘制出来的,Canvas 是不能绘制事件的,而且在这里,我们绘制的 下一步的按钮的位置也是不固定的
所以在这里,小编通过
GestureDetector(
onTapUp: (TapUpDetails detail) {
GuideLogs.e('onTapUp');
onTap(context, detail);
},
/*横向拖动的开始状态*/
onHorizontalDragStart: (startDetails) {
GuideLogs.e('拖动的开始');
if (widget.isSlide) {
setState(() {
slideOffset = startDetails.globalPosition;
});
}
},
onHorizontalDragUpdate: (startDetails) {
GuideLogs.e('位置变化 dx:${startDetails.globalPosition.dx} dy:${startDetails.globalPosition.dy}');
if (widget.isSlide) {
setState(() {
slideOffset = startDetails.globalPosition;
});
}
},
/*横向拖动的结束状态*/
onHorizontalDragEnd: (endDetails) {
GuideLogs.e('拖动的结束');
if (widget.isSlide) {
setState(() {
slideOffset = Offset.zero;
});
}
},
child: Stack(
children: <Widget>[
在这里放我们的 CustomPaint 画布绘制的内容
],
),
)
对于 onTap 点击事件来讲,我们这里进行了计算
///用户计算下一步的点击事件
void onTap(BuildContext context, TapUpDetails detail) {
Offset globalPosition = detail.globalPosition;
GuideLogs.e("onTapUp 点击了 ${globalPosition.dx} ${globalPosition.dy}");
if (nextRect != null) {
///获取当前 下一步的区域
double left = nextRect.left - 60;
double right = nextRect.right + 60;
double bottom = nextRect.bottom + 60;
double top = nextRect.top - 60;
///获取当前屏幕上手指点击的位置
double dx = globalPosition.dx;
double dy = globalPosition.dy;
if (dx > left && dx < right && dy > top && dy < bottom) {
if (currentPointIndex < widget.curvePointList.length - 1) {
///如果当前不是最后一页面,那么取出下一页的内容信息
setState(() {
currentPointIndex++;
});
///蒙版中点击下一步的回调事件
if(widget.clickCallback!=null){
widget.clickCallback(false);
}
} else {
///如果是最后一页退出蒙版引导
if(widget.clickCallback!=null){
widget.clickCallback(true);
}
Navigator.of(context).pop();
}
}
}
}
///记录下一步按钮位置的回调
void liserClickCallback(Rect rect) {
this.nextRect = rect;
}
上述 使用到的 nextRect,是的个Rect,是用来记录引导层中下一步按钮的绘制位置信息的。
完毕,如有疑问请回复,也可以关注一下,小编最近正在搞事情 Java 、mybatis、springboot 、vue、react、Sql、android、ios 会持续记录