forked from lxm_flutter/FlutterUnit
绘制集录-井字棋绘制
This commit is contained in:
145
lib/painter_system/fun/stemp/stamp_data.dart
Normal file
145
lib/painter_system/fun/stemp/stamp_data.dart
Normal file
@@ -0,0 +1,145 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class StampData extends ChangeNotifier {
|
||||
final List<Stamp> stamps = [];
|
||||
|
||||
void push(Stamp stamp) {
|
||||
stamps.add(stamp);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void removeLast() {
|
||||
stamps.removeLast();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void activeLast({Color color = Colors.blue}) {
|
||||
stamps.last.color = color;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
stamps.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void animateAt(int index, double radius) {
|
||||
stamps[index].radius = radius;
|
||||
stamps[index].rePath();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
GameState checkWin(double length){
|
||||
bool redWin = _checkWinByColor(length,Colors.red);
|
||||
if(redWin) return GameState.redWin;
|
||||
|
||||
bool blueWin = _checkWinByColor(length,Colors.blue);
|
||||
if(blueWin) return GameState.blueWin;
|
||||
|
||||
return GameState.doing;
|
||||
}
|
||||
|
||||
bool _checkWinByColor(double length,Color color) {
|
||||
List<Offset> red = stamps
|
||||
.where((element) => element.color == color)
|
||||
.map((e) => e.center)
|
||||
.toList();
|
||||
List<Point<int>> redPoints = red
|
||||
.map<Point<int>>((e) => Point<int>(e.dx ~/ length, e.dy ~/ length))
|
||||
.toList();
|
||||
|
||||
return _checkWinInline(redPoints, 3);
|
||||
}
|
||||
|
||||
bool _checkWinInline(List<Point<int>> points, int max) {
|
||||
if (points.length < max) return false;
|
||||
for (int i = 0; i < points.length; i++) {
|
||||
int x = points[i].x;
|
||||
int y = points[i].y;
|
||||
if (_check(x, y, points, CheckModel.horizontal,max)) {
|
||||
return true;
|
||||
} else if (_check(x, y, points, CheckModel.vertical,max)) {
|
||||
return true;
|
||||
} else if (_check(x, y, points, CheckModel.leftDiagonal,max)) {
|
||||
return true;
|
||||
} else if (_check(x, y, points, CheckModel.rightDiagonal,max)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _check(int x, int y, List<Point> points, CheckModel checkModel,int max) {
|
||||
int count = 1;
|
||||
Point checkPoint;
|
||||
for (int i = 1; i < max; i++) {
|
||||
switch (checkModel) {
|
||||
case CheckModel.horizontal: checkPoint = Point(x - i, y); break;
|
||||
case CheckModel.vertical: checkPoint = Point(x, y - i); break;
|
||||
case CheckModel.leftDiagonal: checkPoint = Point(x - i, y + i);break;
|
||||
case CheckModel.rightDiagonal: checkPoint = Point(x + i, y + i); break;
|
||||
}
|
||||
if (points.contains(checkPoint)) {count++;} else {break;}
|
||||
}
|
||||
if (count == max) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
enum CheckModel {
|
||||
horizontal, // 横向判断
|
||||
vertical, // 竖向判断
|
||||
leftDiagonal, // 左斜判断
|
||||
rightDiagonal // 右斜判断
|
||||
}
|
||||
|
||||
enum GameState{
|
||||
doing, // 进行中
|
||||
redWin, // 红胜
|
||||
blueWin // 蓝胜
|
||||
}
|
||||
|
||||
class Stamp {
|
||||
Color color;
|
||||
Offset center;
|
||||
double radius;
|
||||
|
||||
Stamp({this.color = Colors.blue, this.center, this.radius = 20});
|
||||
|
||||
Path _path;
|
||||
|
||||
Path get path {
|
||||
if (_path == null) {
|
||||
_path = Path();
|
||||
double r = radius;
|
||||
double rad = 30 / 180 * pi;
|
||||
|
||||
_path..moveTo(center.dx, center.dy);
|
||||
_path.relativeMoveTo(r * cos(rad), -r * sin(rad));
|
||||
_path.relativeLineTo(-2 * r * cos(rad), 0);
|
||||
_path.relativeLineTo(r * cos(rad), r + r * sin(rad));
|
||||
_path.relativeLineTo(r * cos(rad), -(r + r * sin(rad)));
|
||||
|
||||
_path..moveTo(center.dx, center.dy);
|
||||
_path.relativeMoveTo(0, -r);
|
||||
_path.relativeLineTo(-r * cos(rad), r + r * sin(rad));
|
||||
_path.relativeLineTo(2 * r * cos(rad), 0);
|
||||
_path.relativeLineTo(-r * cos(rad), -(r + r * sin(rad)));
|
||||
|
||||
return _path;
|
||||
} else {
|
||||
return _path;
|
||||
}
|
||||
}
|
||||
|
||||
set path(Path path) {
|
||||
_path = path;
|
||||
}
|
||||
|
||||
void rePath() {
|
||||
_path = null;
|
||||
_path = path;
|
||||
}
|
||||
}
|
||||
227
lib/painter_system/fun/stemp/stamp_paper.dart
Normal file
227
lib/painter_system/fun/stemp/stamp_paper.dart
Normal file
@@ -0,0 +1,227 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import 'base/draw_picture.dart';
|
||||
import 'art/hypnotic_squares.dart';
|
||||
import 'art/joy_division.dart';
|
||||
import 'art/piet_mondrian.dart';
|
||||
import 'base/random_portrait.dart';
|
||||
import 'fun/random_portrait.dart';
|
||||
import 'art/tiled_lines.dart';
|
||||
import 'art/triangular_mesh.dart';
|
||||
import 'art/un_deux_trois.dart';
|
||||
|
||||
@@ -14,7 +14,8 @@ import 'base/clock_widget.dart';
|
||||
import 'base/draw_path_fun.dart';
|
||||
import 'base/draw_grid_axis.dart';
|
||||
import 'base/draw_picture.dart';
|
||||
import 'base/random_portrait.dart';
|
||||
import 'fun/random_portrait.dart';
|
||||
import 'fun/stemp/stamp_paper.dart';
|
||||
import 'gallery.dart';
|
||||
|
||||
/// create by 张风捷特烈 on 2020/12/5
|
||||
@@ -82,6 +83,12 @@ class GalleryFactory {
|
||||
info:
|
||||
" 本样例介绍绘制矩形及随机数处理。通过点位集合确定矩形位置信息,将其绘制出来。其中对点的随机生成和对称处理能让你练习对数据的控制力。",
|
||||
content: RandomPortrait()),
|
||||
FrameShower(
|
||||
title: "井字棋",
|
||||
author: "张风捷特烈",
|
||||
info:
|
||||
" 本例通过井字棋的绘制与逻辑校验,集合了手势、绘制、动画、校验等重要的技能,是一个非常好的联系案例。",
|
||||
content: StampPaper()),
|
||||
];
|
||||
case GalleryType.art:
|
||||
return [
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:flutter_unit/painter_system/base/draw_picture.dart';
|
||||
import 'package:flutter_unit/painter_system/art/hypnotic_squares.dart';
|
||||
import 'package:flutter_unit/painter_system/art/joy_division.dart';
|
||||
import 'package:flutter_unit/painter_system/art/piet_mondrian.dart';
|
||||
import 'package:flutter_unit/painter_system/base/random_portrait.dart';
|
||||
import 'package:flutter_unit/painter_system/fun/random_portrait.dart';
|
||||
import 'package:flutter_unit/painter_system/art/tiled_lines.dart';
|
||||
import 'package:flutter_unit/painter_system/art/triangular_mesh.dart';
|
||||
import 'package:flutter_unit/painter_system/art/un_deux_trois.dart';
|
||||
|
||||
Reference in New Issue
Block a user