flutter星球实现

添加链接描述

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:vector_math/vector_math_64.dart' show Vector3;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
import 'package:vector_math/vector_math_64.dart' show Vector3;

class PlanetWidget extends StatefulWidget {
  const PlanetWidget({Key? key, required this.children, this.minRadius = 50})
      : super(key: key);

  @override
  _PlanetWidgetState createState() => _PlanetWidgetState();

  final List<Widget> children;
  final double minRadius;
}

class _PlanetWidgetState extends State<PlanetWidget>
    with TickerProviderStateMixin {
  late AnimationController animationController;

  /// 启动加载或者重新加载的时候用的Controller
  late AnimationController reloadAnimationController;

  double preAngle = 0.0;
  double _radius = -1.0;

  List<PlanetTagInfo>? childTagList = [];

  /// 当前操作的向量信息
  Vector3 currentOperateVector = Vector3(1.0, 0.0, 0.0);

  @override
  void initState() {
    super.initState();
    animationController =
        AnimationController(lowerBound: 0, upperBound: pi * 2, vsync: this);
    reloadAnimationController = AnimationController(
        lowerBound: 0,
        upperBound: 1,
        duration: Duration(milliseconds: 300),
        vsync: this);

    animationController.addListener(() {
      setState(() {
        calTagInfo(animationController.value - preAngle);
      });
    });
    reloadAnimationController.addListener(() {
      setState(() {});
    });

    // initData();
  }

  void initData() {
    childTagList = widget.children
        .map((e) => PlanetTagInfo(child: e, planetTagPos: Vector3.zero()))
        .toList();

    currentOperateVector = updateOperateVector(Offset(-1.0, 1.0));

    initTagInfo();

    WidgetsBinding.instance!.addPostFrameCallback((_) {
      reloadAnimationController.forward().then((value) => _reStartAnimation());
    });
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    if (widget.children.isNotEmpty) {
      initData();
    }
  }

  @override
  void didUpdateWidget(covariant PlanetWidget oldWidget) {
    if (oldWidget.children != this.widget.children) {
      if (widget.children.isNotEmpty) {
        animationController.reset();
        reloadAnimationController.reset();
        initData();
      }
    }
    super.didUpdateWidget(oldWidget);
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (BuildContext context, BoxConstraints constraints) {
        var radius = min(constraints.maxWidth, constraints.maxHeight) / 2.0;

        /// 太小就不显示了
        if (radius < widget.minRadius) {
          return SizedBox.shrink();
        }

        if (_radius != radius) {
          if (_radius == -1.0) {
            _radius = radius;
            initTagInfo();
          } else {
            _radius = radius;
            resizeTagInfo();
          }
        }

        final Map<Type, GestureRecognizerFactory> gestures =
        <Type, GestureRecognizerFactory>{};
        gestures[PanGestureRecognizer] =
            GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
                  () => PanGestureRecognizer(debugOwner: this),
                  (PanGestureRecognizer instance) {
                instance
                  ..onDown = (detail) {
                    if (animationController.isAnimating) {
                      _stopAnimation();
                    }
                  }
                  ..onStart = (detail) {
                    if (animationController.isAnimating) {
                      _stopAnimation();
                    }
                  }
                  ..onUpdate = (detail) {
                    if (detail.delta.dx == 0 && detail.delta.dy == 0) {
                      return;
                    }
                    double distance = sqrt(detail.delta.dx * detail.delta.dx +
                        detail.delta.dy * detail.delta.dy);
                    setState(() {
                      currentOperateVector = updateOperateVector(detail.delta);
                      calTagInfo(distance / _radius);
                    });
                  }
                  ..onEnd = (detail) {
                    startFlingAnimation(detail);
                  }
                  ..onCancel = () {
                    _reStartAnimation();
                  }
                  ..dragStartBehavior = DragStartBehavior.start
                  ..gestureSettings =

                  /// 为了能竞争过 HorizontalDragGestureRecognizer ,不得不使用一些下作手段;
                  /// 比如说卷起来,判断阈值比 HorizontalDragGestureRecognizer 的阈值小;
                  /// PS :默认的PanGestureRecognizer 的判断阈值是 touchSlop * 2;
                  const DeviceGestureSettings(touchSlop: kTouchSlop / 4);
              },
            );

        gestures[TapGestureRecognizer] =
            GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
                  () => TapGestureRecognizer(debugOwner: this),
                  (TapGestureRecognizer instance) {
                instance
                  ..onTapDown = (detail) {
                    _stopAnimation();
                  }
                  ..onTapUp = (detail) {
                    _reStartAnimation();
                  };
              },
            );

        return RawGestureDetector(
          gestures: gestures,
          behavior: HitTestBehavior.translucent,
          excludeFromSemantics: false,
          child: Container(
            width: _radius * 2,
            height: _radius * 2,
            child: LayoutBuilder(
              builder: (BuildContext context, BoxConstraints constraints) {
                /// 要根据Z轴高度更新Stack中的叠放顺序;
                /// 要不然点击重叠部分的时候,可能点击事件并非最上面的处理;
                /// PS :实在不行搞个获取Z轴的Stack,修改hitTest让它遍历顺序根据Z轴来制定?
                childTagList?.sort((item1, item2) =>
                    item1.planetTagPos.z.compareTo(item2.planetTagPos.z));

                var itemOpacity =
                ((_radius - widget.minRadius) / widget.minRadius);

                if (itemOpacity <= 0.1) {
                  return SizedBox.shrink();
                }

                return Opacity(
                  opacity: _radius >= widget.minRadius * 2 ? 1.0 : itemOpacity,
                  child: Stack(
                    alignment: Alignment.center,
                    children: childTagList
                        ?.map((e) => Transform(
                      transform: calTransformByTagInfo(
                          e, animationController.value),

                      /// 聊胜于无的优化,如果基本看不到了,那没必要显示
                      child: e.opacity >= 0.15
                          ? Opacity(
                        opacity: e.opacity,
                        child: RepaintBoundary(
                          child: e.child,
                        ),
                      )
                          : SizedBox.shrink(),
                    ))
                        .toList() ??
                        [],
                  ),
                );
              },
            ),
          ),
        );
      },
    );
  }

  void _stopAnimation() {
    animationController.stop();
  }

  void _reStartAnimation() {
    animationController.value = preAngle;
    animationController.repeat(
        min: 0, max: pi * 2, period: Duration(seconds: 20));
  }

  void startFlingAnimation(DragEndDetails detail) {
    /// 计算手势要滑动多少距离
    var velocityPerDis = sqrt(pow(detail.velocity.pixelsPerSecond.dx, 2) +
        pow(detail.velocity.pixelsPerSecond.dy, 2));

    if (velocityPerDis < 5) {
      _reStartAnimation();
      return;
    }

    /// 距离处以周长就是变化的角度,最大一周
    var angle = min(
        2 * pi,
        animationController.value +
            velocityPerDis / (2 * pi * _radius) * (2 * pi));

    animationController
        .animateWith(SpringSimulation(
        SpringDescription.withDampingRatio(
          mass: 1.0,
          stiffness: 500.0,
        ),
        animationController.value,
        angle,
        1)
      ..tolerance = Tolerance(
        velocity: double.infinity,
        distance: 0.01,
      ))
        .then((value) => _reStartAnimation());
  }

  @override
  void dispose() {
    animationController.dispose();
    reloadAnimationController.dispose();
    super.dispose();
  }

  /// 设置Tag们的初始位置
  void initTagInfo() {
    final itemCount = childTagList?.length ?? 0;

    for (var index = 1; index < itemCount + 1; index++) {
      final phi = (acos(-1.0 + (2.0 * index - 1.0) / itemCount));
      final theta = sqrt(itemCount * pi) * phi;

      final x = _radius * cos(theta) * sin(phi);
      final y = _radius * sin(theta) * sin(phi);
      final z = _radius * cos(phi);

      var childItem = childTagList?[index - 1];
      childItem?.planetTagPos = Vector3(x, y, z);
      childItem?.currentAngle = phi;
      childItem?.radius = _radius;
    }
  }

  /// 重新根据当前的半径,修改大小
  void resizeTagInfo() {
    final itemCount = childTagList?.length ?? 0;

    for (var index = 0; index < itemCount; index++) {
      var childItem = childTagList![index];
      var pos = childItem.planetTagPos;
      pos.x = (_radius / childItem.radius) * pos.x;
      pos.y = (_radius / childItem.radius) * pos.y;
      pos.z = (_radius / childItem.radius) * pos.z;

      childItem.radius = _radius;
    }
  }

  /// 根据变化的角度计算最新位置
  void calTagInfo(double dAngle) {
    var currentAngle = preAngle + dAngle;

    final itemCount = childTagList?.length ?? 0;

    for (var index = 1; index < itemCount + 1; index++) {
      var childItem = childTagList![index - 1];

      var point = childItem.planetTagPos;

      double x = cos(dAngle) * point.x +
          (1 - cos(dAngle)) *
              (currentOperateVector.x * point.x +
                  currentOperateVector.y * point.y) *
              currentOperateVector.x +
          sin(dAngle) * (currentOperateVector.y * point.z);

      double y = cos(dAngle) * point.y +
          (1 - cos(dAngle)) *
              (currentOperateVector.x * point.x +
                  currentOperateVector.y * point.y) *
              currentOperateVector.y -
          sin(dAngle) * (currentOperateVector.x * point.z);

      double z = cos(dAngle) * point.z +
          sin(dAngle) *
              (currentOperateVector.x * point.y -
                  currentOperateVector.y * point.x);
      if (x.isNaN || y.isNaN || z.isNaN) {
        continue;
      }

      childItem.planetTagPos = Vector3(x, y, z);
      childItem.currentAngle = currentAngle;
    }

    if (animationController.isAnimating) {
      preAngle = currentAngle;
    }
  }

  Vector3 updateOperateVector(Offset operateOffset) {
    double x = -operateOffset.dy;
    double y = operateOffset.dx;
    double module = sqrt(x * x + y * y);
    return Vector3(x / module, y / module, 0.0);
  }

  Matrix4 calTransformByTagInfo(PlanetTagInfo tagInfo, double currentAngle) {
    var result = Matrix4.identity();
    result.translate(
        tagInfo.planetTagPos.x * reloadAnimationController.value,
        tagInfo.planetTagPos.y * reloadAnimationController.value,
        tagInfo.planetTagPos.z * reloadAnimationController.value);
    result.scale(tagInfo.scale);
    return result;
  }
}
class PlanetTagInfo {
  Vector3 planetTagPos = Vector3(0, 0, 0);
  Widget child;
  double currentAngle = 0;
  double radius = 0;

  PlanetTagInfo({required this.planetTagPos, required this.child});

  double get opacity {
    var result = 0.9 * ((radius + planetTagPos.z) / (radius * 2)) + 0.1;
    return result.isNaN || result.isNegative ? 0.0 : result;
  }

  double get scale {
    var result = ((radius + planetTagPos.z) / (radius * 2)) * 6 / 8 + 2 / 8;
    return result.isNaN || result.isNegative ? 0.0 : result;
  }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flutter是一个用于构建跨平台移动应用的开源框架,而Fluttersound是Flutter的一个音频处理库。要实现微信的功能,可以借助FlutterFluttersound进行开发。 首先,我们可以利用Flutter的UI组件和布局系统,构建用户界面,包括聊天界面、联系人列表、朋友圈等。Flutter提供了丰富的组件和布局选项,可以根据微信的设计规范进行界面开发。 其次,我们可以利用Fluttersound来实现音频的录制和播放功能。Fluttersound提供了丰富的音频处理功能,包括录制、播放、暂停、停止等操作。我们可以利用这些功能来实现微信中的语音消息功能,用户可以通过点击按钮进行录音,然后将录制的语音发送给好友进行播放。 同时,我们还可以利用Fluttersound来实现聊天界面中的声音通话功能。Fluttersound提供了音频编码和解码的功能,可以将用户的声音编码为音频数据,发送给对方进行播放。 除了音频功能,我们还可以利用Flutter的网络请求库来实现微信中的消息发送和接收功能。Flutter提供了http库,可以发送HTTP请求和接收响应,我们可以利用这个库来实现聊天消息的发送和接收。 总结来说,利用FlutterFluttersound可以实现微信的大部分功能,包括界面开发、音频消息的录制与播放、声音通话和消息的发送与接收等。通过充分发挥FlutterFluttersound的优势,我们可以快速构建出一个功能齐全的微信应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值