import 'dart:async';
import 'dart:math';
import 'dart:ui' as ui;
import 'dart:ui';
import 'package:demo/widget/luck/luck_entity.dart';
import 'package:demo/widget/luck/luck_util.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class LuckDrawWidget extends StatefulWidget {
///抽奖相关数据
@required
LuckEntity chartEntity;
double startTurns = .0;
double radius = 130;
LuckDrawWidget(this.chartEntity, {this.radius, this.startTurns});
@override
_LuckDrawWidgetState createState() => _LuckDrawWidgetState();
}
class _LuckDrawWidgetState extends State<LuckDrawWidget>
with TickerProviderStateMixin {
///这个是 自动
AnimationController autoAnimationController;
Animation<double> tween;
double turns = .0;
GlobalKey _key = GlobalKey();
///角加速度,类似摩擦力 的作用 ,让惯性滚动 减慢,这个意思是每一秒 ,角速度 减慢vA个pi。
double vA = 40.0;
Offset offset;
double pBy;
double pBx;
double pAx;
double pAy;
double mCenterX;
double mCenterY;
Animation<double> _valueTween;
double animalValue;
@override
void initState() {
super.initState();
//获取中心图片资源
getPoint();
//获取每条数据item
getResours();
}
getPoint() => getAssetImage(
widget?.chartEntity?.luckPic,
width: widget?.chartEntity?.centerWidth,
height: widget?.chartEntity?.centerHeight,
)
.then((value) => widget?.chartEntity?.image = value)
.whenComplete(() => setState(() {}));
getResours() => widget?.chartEntity?.entitys?.forEach((e) async => ((e.pic
.contains("http") ||
e.pic.contains("https"))
? await getNetImage(e?.pic?.trim(), width: e?.width, height: e?.height)
.then((value) => e.image = value)
.whenComplete(() => setState(() {}))
: await getAssetImage(e?.pic?.trim(), width: e?.width, height: e?.height)
.then((value) => e.image = value)
.whenComplete(() => setState(() {}))));
//获取网络图片 返回ui.Image
Future<ui.Image> getNetImage(String url, {width, height}) async {
try {
ByteData data = await NetworkAssetBundle(Uri.parse(url)).load(url);
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(),
targetWidth: width, targetHeight: height);
ui.FrameInfo fi = await codec.getNextFrame();
return fi.image;
} catch (e) {
return null;
}
}
//获取本地图片 返回ui.Image
Future<ui.Image> getAssetImage(String asset, {width, height}) async {
ByteData data = await rootBundle.load(asset);
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(),
targetWidth: width, targetHeight: height);
ui.FrameInfo fi = await codec.getNextFrame();
return fi.image;
}
// 获取图片 本地为false 网络为true
Future<ui.Image> loadImage(var path, bool isUrl) async {
ImageStream stream;
if (isUrl) {
stream = NetworkImage(path).resolve(ImageConfiguration.empty);
} else {
stream = AssetImage(path, bundle: rootBundle)
.resolve(ImageConfiguration.empty);
}
Completer<ui.Image> completer = Completer<ui.Image>();
listener(ImageInfo frame, bool synchronousCall) {
final ui.Image image = frame.image;
completer.complete(image);
stream.removeListener(ImageStreamListener(listener));
}
stream.addListener(ImageStreamListener(listener));
return completer.future;
}
@override
Widget build(BuildContext context) {
return Container(
width: 2 * widget.radius,
height: 2 * widget.radius,
child: GestureDetector(
child: CustomPaint(
painter: PieChartPainter(
_key,
turns,
widget.startTurns,
widget.chartEntity.entitys,
widget?.chartEntity?.image,
widget.chartEntity.centerHeight,
widget.chartEntity.centerWidth,
),
key: _key,
),
onPanEnd: _onPanEnd,
onPanDown: _onPanDown,
onPanUpdate: _onPanUpdate,
),
);
}
void _onPanUpdate(DragUpdateDetails details) {
pBx = details.globalPosition.dx;
//后面的 点的 x坐标
pBy = details.globalPosition.dy;
//后面的点的 y坐标
double dTurns = getTurns();
setState(() {
turns += dTurns;
});
pAx = pBx;
pAy = pBy;
}
void _onPanDown(DragDownDetails details) {
if (offset == null) {
//获取position
RenderBox box = _key.currentContext.findRenderObject();
offset = box.localToGlobal(Offset.zero);
mCenterX = offset.dx + 130;
mCenterY = offset.dy + 130;
}
pAx = details.globalPosition.dx; //初始的点的 x坐标
pAy = details.globalPosition.dy; //初始的点的 y坐标
}
double getTurns() {
///计算 之前的点相对于水平的 角度
///
///
/// o点(offset.dx+130,offset.dy+130).
/// C点 (offset.dx+260,offset.dy+130).
/// oc距离 130
///
/// A点 (pAx,pAy),
/// B点 (pBx,pBy).
/// AC距离
double acDistance = LuckUtil.distanceForTwoPoint(
offset.dx + 2 * widget.radius, offset.dy + widget.radius, pAx, pAy);
/// AO距离
double aoDistance = LuckUtil.distanceForTwoPoint(
offset.dx + widget.radius, offset.dy + widget.radius, pAx, pAy);
///计算 cos aoc 的值 ,然后拿到 角 aoc
///
double ocdistance = widget.radius;
int c = 1;
if (pAy < (offset.dy + widget.radius)) {
c = -1;
}
double cosAOC = (aoDistance * aoDistance +
ocdistance * ocdistance -
acDistance * acDistance) /
(2 * aoDistance * ocdistance);
double AOC = c * acos(cosAOC);
/// BC距离
double bcDistance = LuckUtil.distanceForTwoPoint(
offset.dx + 2 * widget.radius, offset.dy + widget.radius, pBx, pBy);
/// BO距离
double boDistance = LuckUtil.distanceForTwoPoint(
offset.dx + widget.radius, offset.dy + widget.radius, pBx, pBy);
c = 1;
if (pBy < (offset.dy + widget.radius)) {
c = -1;
}
///计算 cos boc 的值,然后拿到角 boc;
double cosBOC = (boDistance * boDistance +
ocdistance * ocdistance -
bcDistance * bcDistance) /
(2 * boDistance * ocdistance);
double BOC = c * acos(cosBOC);
return BOC - AOC;
}
///抬手的时候 , 惯性滑动
void _onPanEnd(DragEndDetails details) {
double vx = details.velocity.pixelsPerSecond.dx;
double vy = details.velocity.pixelsPerSecond.dy;
if (vx != 0 || vy != 0) {
onFling(vx, vy);
}
}
void onFling(double velocityX, double velocityY) {
//获取触点到中心点的线与水平线正方向的夹角
double levelAngle = LuckUtil.getPointAngle(mCenterX, mCenterY, pBx, pBy);
//获取象限
int quadrant = LuckUtil.getQuadrant(pBx - mCenterX, pBy - mCenterY);
//到中心点距离
double distance =
LuckUtil.distanceForTwoPoint(mCenterX, mCenterY, pBx, pBy);
//获取惯性绘制的初始角度
double inertiaInitAngle = LuckUtil.calculateAngleFromVelocity(
velocityX, velocityY, levelAngle, quadrant, distance);
if (inertiaInitAngle != null && inertiaInitAngle != 0) {
//如果角速度不为0; 则进行滚动
/// 按照 va的加速度 拿到 滚动的时间 。 也就是 结束后 惯性动画的 执行 时间, 高中物理
double t = LuckUtil.abs(inertiaInitAngle) / vA;
double s = t * inertiaInitAngle / 2;
animalValue = turns;
var time = new DateTime.now();
int direction = 1;
///方向控制参数
if (inertiaInitAngle < 0) {
direction = -1;
}
autoAnimationController = AnimationController(
duration: Duration(milliseconds: (t * 1000).toInt()), vsync: this)
..addListener(() {
var animalTime = new DateTime.now();
int t1 =
animalTime.millisecondsSinceEpoch - time.millisecondsSinceEpoch;
setState(() {
double s1 = (2 * inertiaInitAngle - direction * vA * (t1 / 1000)) *
t1 /
(2 * 1000);
turns = animalValue + s1;
});
});
autoAnimationController.forward();
}
}
@override
void dispose() {
super.dispose();
if (autoAnimationController != null) {
autoAnimationController.dispose();
}
}
}
class PieChartPainter extends CustomPainter {
GlobalKey _key = GlobalKey();
double turns = .0;
double startTurns = .0;
@required
int centerHeight;
@required
int centerWidth;
@required
List<LuckItem> entitys;
@required
ui.Image _image;
PieChartPainter(
this._key,
this.turns,
this.startTurns,
this.entitys,
this._image,
this.centerHeight,
this.centerWidth,
);
double startAngles = 0;
@override
void paint(Canvas canvas, Size size) {
drawAcr(canvas, size);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
void drawAcr(Canvas canvas, Size size) {
startAngles = 0;
Rect rect = Rect.fromCircle(
center: Offset(size.width / 2, size.height / 2),
radius: size.width / 2);
Paint paint = Paint()
..color = Colors.red
..strokeWidth = 1.0
..isAntiAlias = true
..style = PaintingStyle.fill;
//画扇形
drawCircule(canvas, rect, size, paint);
//画中心图片
drawCenterPic(canvas, size, paint);
//绘制内容
drawContent(canvas, size, paint);
}
drawCircule(Canvas canvas, Rect rect, Size size, Paint paint) {
for (int i = 0; i < entitys.length; i++) {
paint..color = entitys[i].bgColor ?? Colors.green;
canvas.drawArc(rect, 2 * pi * startAngles + turns + startTurns,
2 * pi * entitys[i].percent, true, paint);
startAngles += entitys[i].percent;
}
startAngles = 0;
}
drawCenterPic(Canvas canvas, Size size, Paint paint) {
if (_image != null) {
canvas.save();
//画中心按钮图片
canvas.drawImage(
_image,
Offset(
(size.width - centerWidth) / 2, (size.height - centerHeight) / 2),
paint,
);
canvas.restore();
}
}
drawContent(Canvas canvas, Size size, Paint paint) {
for (int i = 0; i < entitys.length; i++) {
canvas.save();
// 新建一个段落建造器,然后将文字基本信息填入;
ParagraphBuilder pb = ParagraphBuilder(ParagraphStyle(
textDirection: TextDirection.ltr,
// 字体对齐方式
textAlign: TextAlign.right,
fontWeight: FontWeight.w500,
fontStyle: FontStyle.normal,
fontSize: 12.0,
maxLines: 5,
ellipsis: "...",
));
pb.pushStyle(ui.TextStyle(
color: entitys[i].nameColor ?? Colors.white,
background: paint..color = Colors.white,
height: 1,
));
double roaAngle =
2 * pi * (startAngles + entitys[i].percent / 2) + turns + startTurns;
pb.addText(entitys[i].name);
//计算扇形文字宽度
// 设置文本的宽度约束
ParagraphConstraints pc = ParagraphConstraints(width: 20);
// 这里需要先layout,将宽度约束填入,否则无法绘制
Paragraph paragraph = pb.build()..layout(pc);
// 文字左上角起始点
var startX = (centerHeight / 2 + entitys[i].height);
var offsetAngles =
(sin((startAngles + (entitys[i].percent / 2)) * (pi / 180)));
Offset offset =
Offset(startX, startX * offsetAngles - entitys[i].height / 2);
canvas.translate(size.width / 2, size.height / 2);
canvas.rotate((1) * roaAngle);
if (entitys[i].image != null) {
Offset offsetPic = Offset(centerHeight / 2 + 3.0,
(centerHeight / 2) * offsetAngles - entitys[i].height / 2);
canvas.drawImageRect(
entitys[i].image,
Offset(0.0, 0.0) &
Size(entitys[i].width.toDouble(), entitys[i].height.toDouble()),
offsetPic &
Size(entitys[i].width.toDouble(), entitys[i].height.toDouble()),
paint);
}
canvas.drawParagraph(paragraph, offset);
canvas.restore();
startAngles += entitys[i].percent;
}
}
}
class LuckEntity {
final String luckPic; //抽奖按钮 现在考虑是否支持网图动态配置
final int centerHeight;
final int centerWidth;
ui.Image image;
final List<LuckItem> entitys;
LuckEntity({
this.luckPic,
this.centerHeight = 90,
this.centerWidth = 90,
this.image,
this.entitys,
});
@override
String toString() {
return 'ChartEntity{luckPic: $luckPic, entitys: $entitys}';
}
}
class LuckItem {
final String pic; //扇形图片链接
@required
ui.Image image;
int height;
int width;
@required
final String name; //扇形名字
final Color nameColor; //扇形名字颜色
@required
final Color bgColor; //扇形背景颜色
@required
final double percent; //百分比
LuckItem({
this.pic,
this.image,
this.height = 30,
this.width = 30,
this.name,
this.nameColor,
this.bgColor,
this.percent,
});
@override
String toString() {
return 'ChartEntity{ pic: $pic, name: $name, nameColor: $nameColor, bgColor: $bgColor, percent: $percent}';
}
}