用Flutter做桌上弹球(绘图 Canvas&CustomPaintAPI)

用Flutter做桌上弹球(绘图(Canvas&CustomPaint)API)

本文主要介绍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的信息,请搜索我们以前的文章或继续浏览下面的相关文章。希望大家以后能多多支持我们!

用Flutter做桌上弹球(绘图 Canvas&CustomPaintAPI)