fCreate
一个简单的眼力测试游戏,由 Flutter CustomPainter 提供支持。
一个简单的Android应用程序,使用Flutter构建。该应用程序正在参加Flutter Create竞赛。因此,它功能有限,仅使用5112字节的Dart代码构建。而且它是一个纯Dart应用程序,目标是Android SDK版本28。
它是如何工作的?
-
执行从App()类开始,该类扩展StatelessWidget。
-
由于此应用程序仅针对Android,因此它是MaterialApp。
-
由于我将为我们的应用程序添加交互性,因此我需要一个StatefulWidget。Home()满足了这一需求。
-
由于代码限制,我将应用程序限制为仅在纵向模式下运行。
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); -
在build方法中,我返回了一个Scaffold Widget,它提供了一个健全的Material Design骨架。
-
Scaffold的body是一个Stack widget,它非常擅长根据特定条件或用户事件处理重叠小部件的隐藏,通过使用Opacity Widget作为其父级。
-
我还在使用一个FloatingActionButton(),用于在帮助小部件和游戏小部件之间切换。
floatingActionButton: FloatingActionButton( onPressed: _e ? () { swap(); // _tm holds a periodic Timer, which helps us to generate a new Paint widget, every 1 seconds. _tm = Timer.periodic(Duration(seconds: 1), (t) { if (t.isActive) setState(() { _b = Random().nextInt(12) + 8; // _b, holds number of columns and rows, to be drawn in Paint widget // which also gets updated in random fashion }); }); } : () { swap(); _tm.cancel(); // timer, _tm is cancelled, to avoid unnecessary periodic computational tasks. }, // here `e` is a boolean variable, which decides behaviour of this button child: Icon(_e ? Icons.play_arrow : Icons.help), // icon also gets updated backgroundColor: Colors.cyanAccent, elevation: 16, tooltip: _e ? 'Init' : 'Help', // tooltip text is also updated as value of `e` gets updated. ), -
所以,你遇到一个新函数swap(),它只是改变两个小部件的不透明度值,并使它们可见或不可见。
swap() { var tmp = _h; // simple swapping of `_h` and `_g` is done here. setState(() { _h = _g; _g = tmp; _e = !_e; // putting negation of `_e` into `_e`, helps us to change behaviour of floatingActionButton. }); } -
你可能已经找到了一个名为setW()的方法,由于在_HomeState中实现了White类而存在。
@override setW(int c) { if (!_tm.isActive) setState(() { _wh = c; }); else _wh = c; // here I'm not interested in updating the UI too, that's why assignment is put outside of setState((){}). // otherwise that might collide with scheduled UI updated operation, which runs periodically using Timer, and updates CustomPaint widget. } -
GestureDetector widget用作CustomPaint的父级,以处理用户输入事件。单击一次表示用户选择了当前显示的金漆小部件,并想知道他/她是否选择了至少有50%白色球的小部件。作为响应,会弹出一个Dialog来指示当前选择的状态。
onTap: () { if (_tm.isActive) { _tm.cancel(); showDialog( context: context, builder: (cx) => Dialog( elevation: 9, child: Padding( padding: EdgeInsets.all(16), child: Text( '$_wh/${_b * _b} White Balls ${_wh >= (_b * _b) / 2 ? '\u{2714}' : '\u{2716}'}', style: TextStyle( fontSize: 25, letterSpacing: 2))))); } }, -
现在,如果你双击屏幕,取消的计时器将重新开始运行。
onDoubleTap: () { if (!_tm.isActive) _tm = Timer.periodic(Duration(seconds: 1), (t) { if (t.isActive) setState(() { _b = Random().nextInt(12) + 8; }); }); } -
一个居中对齐的小部件用作Opacity的子级,用于显示帮助页面。更改不透明度值可以显示帮助页面或游戏页面。当然,这些Opacity小部件的父级是Stack小部件。
Center( child: Padding( padding: EdgeInsets.all(12), child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ Text('An EYE Test Game', style: TextStyle( fontWeight: FontWeight.bold, letterSpacing: 3, fontSize: 30)), Divider(color: Colors.white, height: 24), Text( 'Click to reveal whether it has atleast 50% WHITE Balls. Double clicking restarts loop.', maxLines: 3) ]))) -
让我们来谈谈Painter类,它继承了CustomPainter,主要负责在CustomPaint小部件中执行的绘图操作。Painter的构造函数接受两个参数:沿行和列放置的球的数量(它是正方形)以及一个White类的实例,该实例充当回调机制,用于更新在绘图过程中随机生成的白色球的值,这些值存储在_HomeState类中的一个变量中。
-
根据CustomPainter的定义,需要在Painter中重写paint()和shouldRepaint()方法。
-
所以,我将绘制一些圆圈,为此我需要一个Offset。X轴和Y轴上的位置确定如下。
double y = size.height / (b * 2); // b is # of balls along x and y axis. double x = size.width / (b * 2); -
白色球的数量确定如下。
w += (p.color == Colors.white) ? 1 : 0; // p variable holds instance of Paint(), which is instantiated just in previous line. -
这就是如何以迭代方式绘制圆圈,使用while循环直到我们到达X轴上的size.width或Y轴上的size.height。
canvas.drawCircle( Offset(x, y), min(size.height / (b * 2), size.width / (b * 2)), p); // radius of circle is decreased, so that no two circle gets overlapped. -
在paint()的内部while循环中,x的坐标值在每次迭代中增加size.width / b。
x += size.width / b; -
y也是如此,在外while循环中。
y += size.height / b; -
绘制完成后,屏幕上绘制的白色球的数量会更新,其中White抽象类起着作用。
wh.setW(w); // wh is an instance of WHite, which is passed into constructor of Painter. -
就是这样。


