forked from lxm_flutter/FlutterUnit
228 lines
5.5 KiB
Dart
228 lines
5.5 KiB
Dart
import 'dart:math';
|
|
import 'dart:ui' as ui;
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'stamp_data.dart';
|
|
|
|
class StampPaper extends StatefulWidget {
|
|
@override
|
|
_StampPaperState createState() => _StampPaperState();
|
|
}
|
|
|
|
class _StampPaperState extends State<StampPaper>
|
|
with SingleTickerProviderStateMixin {
|
|
final StampData stamps = StampData();
|
|
int gridCount = 3;
|
|
double radius = 0;
|
|
double width = 0;
|
|
GameState gameState = GameState.doing;
|
|
|
|
bool get gameOver => gameState != GameState.doing;
|
|
|
|
// 定义动画器
|
|
AnimationController _controller;
|
|
final Duration animDuration = const Duration(milliseconds: 200);
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller = AnimationController(vsync: this, duration: animDuration)
|
|
..addListener(_listenAnim);
|
|
}
|
|
|
|
void _listenAnim() {
|
|
if (_controller.value == 1.0) {
|
|
_controller.reverse();
|
|
}
|
|
double rate = (0.9 - 1) * _controller.value + 1;
|
|
stamps.animateAt(containsIndex, rate * radius);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
width = MediaQuery.of(context).size.shortestSide * 0.8;
|
|
|
|
return GestureDetector(
|
|
onTapDown: _onTapDown,
|
|
onTapUp: _onTapUp,
|
|
onDoubleTap: _clear,
|
|
onTapCancel: _removeLast,
|
|
child: CustomPaint(
|
|
foregroundPainter: StampPainter(stamps: stamps, count: gridCount),
|
|
painter: BackGroundPainter(count: gridCount),
|
|
size: Size(width, width),
|
|
),
|
|
);
|
|
}
|
|
|
|
bool get contains => containsIndex != -1;
|
|
|
|
void _onTapDown(TapDownDetails details) {
|
|
if (gameOver) return;
|
|
|
|
containsIndex = checkZone(details.localPosition);
|
|
if (contains) {
|
|
_controller.forward();
|
|
return;
|
|
}
|
|
|
|
radius = width / 2 / gridCount * 0.618;
|
|
stamps.push(Stamp(
|
|
radius: radius, center: details.localPosition, color: Colors.grey));
|
|
}
|
|
|
|
int containsIndex = -1;
|
|
|
|
int checkZone(Offset src) {
|
|
for (int i = 0; i < stamps.stamps.length; i++) {
|
|
Rect zone = Rect.fromCircle(
|
|
center: stamps.stamps[i].center, radius: width / gridCount / 2);
|
|
if (zone.contains(src)) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void _onTapUp(TapUpDetails details) {
|
|
if (contains || gameOver) return;
|
|
|
|
stamps.activeLast(
|
|
color: stamps.stamps.length % 2 == 0 ? Colors.red : Colors.blue);
|
|
|
|
gameState = stamps.checkWin(width / gridCount);
|
|
if (gameState == GameState.redWin) {
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
|
content: Text("红棋获胜!"),
|
|
backgroundColor: Colors.red,
|
|
));
|
|
}
|
|
|
|
if (gameState == GameState.blueWin) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text("蓝棋获胜!"), backgroundColor: Colors.blue));
|
|
}
|
|
}
|
|
|
|
void _clear() {
|
|
stamps.clear();
|
|
gameState=GameState.doing;
|
|
}
|
|
|
|
void _removeLast() {
|
|
if (contains || gameOver) return;
|
|
|
|
stamps.removeLast();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
stamps.dispose();
|
|
super.dispose();
|
|
}
|
|
}
|
|
|
|
class BackGroundPainter extends CustomPainter {
|
|
BackGroundPainter({this.count = 3});
|
|
|
|
final int count;
|
|
|
|
final Paint _pathPaint = Paint()
|
|
..style = PaintingStyle.stroke
|
|
..strokeWidth = 1;
|
|
|
|
static const List<Color> colors = [
|
|
Color(0xFFF60C0C),
|
|
Color(0xFFF3B913),
|
|
Color(0xFFE7F716),
|
|
Color(0xFF3DF30B),
|
|
Color(0xFF0DF6EF),
|
|
Color(0xFF0829FB),
|
|
Color(0xFFB709F4),
|
|
];
|
|
|
|
static const List<double> pos = [
|
|
1.0 / 7,
|
|
2.0 / 7,
|
|
3.0 / 7,
|
|
4.0 / 7,
|
|
5.0 / 7,
|
|
6.0 / 7,
|
|
1.0
|
|
];
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
Rect zone = Offset.zero & size;
|
|
canvas.clipRect(zone);
|
|
|
|
_pathPaint.shader = ui.Gradient.sweep(
|
|
Offset(size.width / 2, size.height / 2),
|
|
colors,
|
|
pos,
|
|
TileMode.mirror,
|
|
pi / 2,
|
|
pi);
|
|
|
|
canvas.save();
|
|
for (int i = 0; i < count - 1; i++) {
|
|
canvas.translate(0, size.height / count);
|
|
canvas.drawLine(Offset.zero, Offset(size.width, 0), _pathPaint);
|
|
}
|
|
canvas.restore();
|
|
|
|
canvas.save();
|
|
for (int i = 0; i < count - 1; i++) {
|
|
canvas.translate(size.width / count, 0);
|
|
canvas.drawLine(Offset.zero, Offset(0, size.height), _pathPaint);
|
|
}
|
|
canvas.restore();
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant BackGroundPainter oldDelegate) {
|
|
return count != oldDelegate.count;
|
|
}
|
|
}
|
|
|
|
class StampPainter extends CustomPainter {
|
|
final StampData stamps;
|
|
final int count;
|
|
final Paint _paint = Paint();
|
|
final Paint _pathPaint = Paint()
|
|
..color = Colors.white
|
|
..style = PaintingStyle.stroke;
|
|
|
|
StampPainter({this.stamps, this.count = 3}) : super(repaint: stamps);
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
Rect zone = Offset.zero & size;
|
|
canvas.clipRect(zone);
|
|
|
|
stamps.stamps.forEach((stamp) {
|
|
double length = size.width / count;
|
|
int x = stamp.center.dx ~/ (size.width / count);
|
|
int y = stamp.center.dy ~/ (size.width / count);
|
|
double strokeWidth = stamp.radius * 0.07;
|
|
|
|
Offset center = Offset(length * x + length / 2, length * y + length / 2);
|
|
stamp.center = center;
|
|
canvas.drawCircle(
|
|
stamp.center, stamp.radius, _paint..color = stamp.color);
|
|
canvas.drawPath(
|
|
stamp.path,
|
|
_pathPaint
|
|
..strokeWidth = strokeWidth
|
|
..color = Colors.white);
|
|
canvas.drawCircle(stamp.center, stamp.radius + strokeWidth * 1.5,
|
|
_pathPaint..color = stamp.color);
|
|
});
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant StampPainter oldDelegate) {
|
|
return this.stamps != oldDelegate.stamps || this.count != oldDelegate.count;
|
|
}
|
|
}
|