ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

flutter 自定义组件-抽奖大转盘

2021-11-29 14:03:15  阅读:427  来源: 互联网

标签:widget 自定义 double canvas entitys 大转盘 offset height flutter


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}';
  }
}

标签:widget,自定义,double,canvas,entitys,大转盘,offset,height,flutter
来源: https://blog.csdn.net/NotesChapter/article/details/121608234

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有