本文主要介绍CanvasCustomPaint)API使用Flutter来制作桌上弹球。这篇文章很详细,对你的学习或者工作有一定的参考价值。有需要的朋友可以参考一下。
本文是Flutter中Canvas和CustomPaint API的一个例子。
首先看看我们想要达到的效果:
结合动画演示,最终目标如下:
程序运行后,显示一个小球;
每次启动程序,球的样式都是随机变化的,体现在三点:大小、颜色、位置。
小球跑的规则参考乒乓球或立体弹球游戏;
点击屏幕,球变色;
双击屏幕,球暂停/恢复运动;
按下长屏,球自动开始/停止变色。
应用的主要技术点:Canvas和CustomPaint API。
操作平台:安卓、iOS
源代码地址:
吉图比蒂
功能拆解
先把上一篇文章列出的六个成就目标拆解一下。显然,要实现这些目标,我们需要:
随机颜色生成器;
随机位置发生器;
随机大小生成器;
拉球逻辑;
小球运动的逻辑:
边界判断;
初始运动方向生成器;
方向位置更新器。
用户手势监视器。
功能实现
接下来,我们将逐步实现功能反汇编中列出的六个具体功能。
随机颜色生成器
随机颜色发生器用于程序启动、屏幕点击和自动颜色改变。在Flutter中,我们可以通过颜色类分别定义红色、绿色、蓝色和透明来定义一种独特的颜色。数值范围是0-255。对于透明度,0表示完全透明,255表示完全不透明。
对于随机值,我们使用Random类来生成0到255之间的随机整数。
随机颜色生成器主要通过使用上述两个类来实现。具体代码片段如下:
Color _color=Color.fromARGB(0,0,0,0);
//改变球的颜色
void changeColor() {
_color=Color.fromARGB(255,Random()。nextInt(255),Random()。nextInt(255),Random()。nextInt(255));
}
随机位置生成器
随机位置生成器在程序启动时使用。要生成随机位置,方法仍然是使用Random类,但是要注意随机值的范围。通常我们需要球的位置出现在屏幕上,所以需要产生两次随机数,分别代表球初始位置的X轴和Y轴坐标。坐标值分别小于屏幕的水平和垂直尺寸。当然,它们都必须大于0。
另外,我们还需要分别得到屏幕的宽度和高度。
因此,具体代码实现如下:
[获取屏幕宽度和高度]
双screenX,screenY
@覆盖
小部件构建(BuildContext上下文){
screenX=media query . of(context). size . width;
screenY=media query . of(context). size . height;
.
}
[生成随机位置]
double _x=0,_ y=0;
//生成球的初始位置和大小
空生成球(){
_x=Random()。next double()* screenX;
_y=Random()。next double()* screenY;
}
随机尺寸生成器
随机大小生成器在程序启动时使用。完成前面两个随机值的生成后,我们对这里的大小就很熟悉了。由于随机大小和随机位置是程序启动时调用的,操作对象都是小球,所以我们把它们的实现放在generateBall()方法中。最终代码如下:
double _x=0,_y=0,_ size=0;
//生成球的初始位置和大小
空生成球(){
_size=Random()。nextDouble() * (screenY - screenX)。ABS();
_x=Random()。next double()* screenX;
_y=Random()。next double()* screenY;
}
小球绘制逻辑
要在界面上画一个球,我们需要使用CustomPaint组件。而CustomPaint组件需要一个custompaint实例。小球的绘制工作主要在继承CustomPainter的类中。让我们直接看代码:
导入‘package:flutter/material . dart’;
导入“package:flutter/widgets . dart”;
class Ball扩展CustomPainter {
油漆_油漆;
double _x,_y,_ size
球(双x,双y,双尺寸,颜色颜色){
_ Paint=new Paint();
_ paint.isAntiAlias=true
_paint.color=颜色;
这个。_ x=x
这个。_ y=y
这个。_ size=size
}
@覆盖
空白绘画(画布画布,大小大小){
canvas . draw oval(rect . from center(center:Offset(_ x,_y),width: _size,height: _size),_ paint);
}
@覆盖
bool should repaint(custom painter old delegate){
返回旧代表!=这个;
}
}
通过阅读上面的代码,我们可以发现整个Ball类除了构造方法之外,只有两个override方法,可以说非常简单。
在构造方法中,我们初始化_paint对象,可以看作是一个“画笔”;
在paint()方法中,我们调用canvas对象的drawOval方法画一个圆来代表一个球。画布可以看作是“画板”;
shouldRepaint()方法指示刷新布局时是否需要重绘,重绘只有在返回true时才会发生。在这里,我们可以让程序自己判断。
我们将上面的代码保存为ball.dart以备后用。
注意,这里没有写固定的值,无论是位置、颜色还是大小。因为这个类只负责“画圆”,而画什么样的圆交给这个类的用户来定义,也就是main.dart
在main.dart中,我们将App设置为全屏,并添加一个全屏CustomPaint组件,在其中放置球对象。
@覆盖
小部件构建(BuildContext上下文){
screenX=media query . of(context). size . width;
screenY=media query . of(context). size . height;
返回脚手架(
body:手势检测器(
子:容器(
宽度:double.infinity,
高度:双倍.无穷大,
child:custom paint(painter:Ball(_ x,_y,_size,_color)),
onTap: () {
//改变球的颜色
change color();
},
onDoubleTap: () {
//暂停/恢复移动
_keep_move=!_ keep _ move
},
onLongPress: () {
//自动改变球的颜色
_auto_change_color=!_自动_更改_颜色;
},
));
}
在上面的代码中,GestureDetector组件负责接收用户的click事件,其中_keep_move和_auto_change_color是布尔变量,是球移动和自动变色功能的开关。
接下来,我们调用initState()方法中前面的随机位置生成器、随机大小生成器和随机颜色生成器,给_x、_y、_size和_color赋值。
@覆盖
void initState() {
super . initstate();
widgetsbinding . instance . addpostframcallback((时间戳)async {
generate ball();
change color();
calculateMoveAngle();
start move();
});
}
这里calculateMoveAngle()和startMove()方法分别对应初始运动方向生成器和启动运动并定时更新UI的方法。除了这两种方法之外,如果你现在运行程序,你应该可以看到屏幕上出现一个静态的小球,每次再次运行程序,小球的样式和位置都会发生变化。
接下来,让球动起来!
小球运动逻辑
要让球准确移动,需要遵循以下步骤:第一位老师随机移动方向;然后以60FPS的频率,在运动方向上一次前进5个像素的步长(当然也可以自定义);最后,注意边界判断,当球到达屏幕边缘时要正确转身。
让我们逐一实现它们。
初始运动方向发生器
既然是随机方向,那么平面上360度以内的任何角度都有可能。所以,我们需要老师做出0-360范围内的价值观。然后,根据三角函数和移动方向的速度,计算横坐标和纵坐标的速度。其实很简单。这是勾股定理。
double _step_x,_step_y,_ angle
//计算球的初始移动角度(方向)
void calculateMoveAngle() {
_angle=Random()。next double()* 360;
_ step _ x=sin(_角度)* _速度;
_ step _ y=cos(_ angle)* _ speed;
}
这里我们把运动的速度(_speed)看作三角形的斜边,把横坐标和纵坐标的速度(_step_x,_step_y)看作三角形的直角边。如果我没记错的话,都是初中几何知识。不会很难理解。
定向移动位置更新器
前文说到,我们将以60FPS的刷新率更新界面,这也就意味着,每隔大约16毫秒刷新一次小球位置。因为只有小球的运动,才能让人感到界面在"更新"。这一步骤,我们用到计时器类。并将更新器在initState()方法中调用,以便程序启动后,小球即刻运动,也就是前文代码中见到的开始移动()方法。
//开始移动
void startMove() {
定时器.周期(持续时间(毫秒:16),(定时器){
移动球();
setState((){ });
});
}
//小球移动
void moveBall() {
_ x=_ step _ x
_ y=_ step _ y
}
到此为止,小球已经可以开始沿着某个随机方向移动了。但很快,它将移出屏幕。
边界判定
显然,小球每前进一步,都要做屏幕边界判定,以防小球移出屏幕范围。而边界判定在移动球()方法中实现似乎是最恰当的。
我们可以轻松地总结出小球移动的规律,当小球移动到屏幕边缘时,我们只需让其反向运动即可。比如,小球以3的速度移动并接触屏幕的右边缘,接下来,仍以3的速度移动并朝向屏幕的左边缘。
水平方向如此,垂直方向亦如此。
因此,我们的边界判定逻辑如下:
//带有便捷判定的小球移动
void moveBall() {
if (_x=screenX || _x=0) {
_ step _ x=0-_ step _ x;
}
_ x=_ step _ x
if (_y=screenY || _y=0) {
_ step _ y=0-_ step _ y;
}
_ y=_ step _ y
}
用户手势监听器
最后,配合用户手势及相关的布尔变量,在每次刷新小球位置时实现变色和暂停移动。
继续修改移动球()方法:
//带有便捷判定的小球移动
void moveBall() {
if (_keep_move) {
if (_x=screenX || _x=0) {
_ step _ x=0-_ step _ x;
}
_ x=_ step _ x
if (_y=screenY || _y=0) {
_ step _ y=0-_ step _ y;
}
_ y=_ step _ y
if (_auto_change_color) {
改变颜色();
}
}
}
到此,程序全部实现完成。下面放上完整的主。镖代码:
导入”dart:异步”;
导入“飞镖:数学”;
导入包装:飘动/材质。“飞镖”;
导入“包装:颤振/服务。镖”;
导入“球。镖”;
void main() {
runApp(MyApp());
}
类MyApp扩展了无状态小部件{
@覆盖
小部件构建(构建上下文上下文){
系统铬。setenabledsystemuioverlays([]);
返回材料应用程序(
标题:"颤振演示",
主题:主题数据(
原色手表:颜色。蓝色,
视觉密度:视觉密度。适应平台密度,
),
home: BounceBall(),
);
}
}
类弹力球扩展StatefulWidget {
@覆盖
_ BounceBallState createState()=_ BounceBallState();
}
class _BounceBallState扩展StateBounceBall {
最终double _ speed=5;
double _x=0,_y=0,_ size=0;
双_步_x,_步_y,_角度
Color _color=Color.fromARGB(0,0,0,0);
bool _ auto _ change _ color=false
bool _ keep _ move=true
双screenX,screenY
@覆盖
void initState() {
超级棒。initstate();
widgetsbinding。实例。addpostframcallback((时间戳)异步{
生成球();
改变颜色();
calculateMoveAngle();
开始移动();
});
}
@覆盖
小部件构建(构建上下文上下文){
screenX=媒体查询。(上下文)的。尺寸。宽度;
screenY=媒体查询。(上下文)的。尺寸。身高;
返回脚手架(
正文:手势检测器(
子:容器(
宽度:double.infinity,
高度:双倍。无穷大,
子对象:自定义绘制(绘制者:球(_ x,_y,_size,_color)),
onTap: () {
//改变小球颜色
改变颜色();
},
onDoubleTap: () {
//暂停/恢复移动
_keep_move=!_保持_移动
},
onLongPress: () {
//自动改变小球颜色
_auto_change_color=!_自动_更改_颜色;
},
));
}
//开始移动
void startMove() {
定时器.周期(持续时间(毫秒:16),(定时器){
移动球();
setState((){ });
});
}
//改变小球颜色
void changeColor() {
_color=Color.fromARGB(255,Random().nextInt(255),Random().nextInt(255),
随机()。nextInt(255));
}
//生成小球初始位置和大小
空生成球(){
_size=Random().nextDouble() * (screenY - screenX).ABS();
_x=Random()。next double()* screenX;
_y=Random()。next double()* screenY;
}
//计算球的初始移动角度(方向)
void calculateMoveAngle() {
_angle=Random()。next double()* 360;
_ step _ x=sin(_角度)* _速度;
_ step _ y=cos(_ angle)* _ speed;
}
//球路移动,判断方便
void moveBall() {
if (_keep_move) {
if (_x=screenX || _x=0) {
_ step _ x=0-_ step _ x;
}
_ x=_ step _ x
if (_y=screenY || _y=0) {
_ step _ y=0-_ step _ y;
}
_ y=_ step _ y
if (_auto_change_color) {
change color();
}
}
}
}
让我们一起运行这个程序吧!
关于CanvasCustomPaint)API with Flutter的这篇文章到此为止。更多关于Flutter pinball的信息,请搜索我们以前的文章或继续浏览下面的相关文章。希望大家以后能多多支持我们!