颤动中的侧面径向菜单

amr*_*gab 11 dart flutter flutter-layout flutter-animation

请如何创建像图片一样颤动的侧面径向菜单,并在用户点击时滚动

在此处输入图片说明

在此处输入图片说明

在此处输入图片说明

任何帮助,将不胜感激。

Har*_*rry 6

这可以通过使用GestureDetectorTransform、三角函数和一些ClipRect剪辑来实现。

使用GestureDetector,可以查看用户输入的拖动距离。这可用于确定旋转小部件的程度。

使用Transform,可以将小部件移动到特定位置。

三角函数用于确定小部件到圆心的位置。

使用ClipRect,可以剪掉小部件的左侧。

可以通过将拖动距离变为负值来反转滚动方向。

这是制作旋转菜单的代码,该菜单使用我最近创建的用于回答此问题的自定义小部件(如果需要,可以将更多小部件添加到小部件列表中):

import 'dart:math' as math;

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        body:CircularScrollView(//wrap this with align if you want it to be aligned to the right of the screen
          [//add more widgets or remove as you'd like
            GestureDetector(
              onTap: (){},//insert function when icon is tapped
              child: Container(
                child: Center(child: Text('a')),
                height: 20,
                width: 20,
                decoration: BoxDecoration(
                  color: Colors.blue,
                  shape: BoxShape.circle,
                ),
              ),
            ),
            GestureDetector(
              onTap: (){},//insert function when icon is tapped
              child: Container(
                child: Center(child: Text('b')),
                height: 20,
                width: 20,
                decoration: BoxDecoration(
                  color: Colors.blue,
                  shape: BoxShape.circle,
                ),
              ),
            ),
            GestureDetector(
              onTap: (){},//insert function when icon is tapped
              child: Container(
                child: Center(child: Text('c')),
                height: 20,
                width: 20,
                decoration: BoxDecoration(
                  color: Colors.blue,
                  shape: BoxShape.circle,
                ),
              ),
            ),
            GestureDetector(
              onTap: (){},//insert function when icon is tapped
              child: Container(
                child: Center(child: Text('d')),
                height: 20,
                width: 20,
                decoration: BoxDecoration(
                  color: Colors.blue,
                  shape: BoxShape.circle,
                ),
              ),
            ),
            GestureDetector(
              onTap: (){},//insert function when icon is tapped
              child: Container(
                child: Center(child: Text('e')),
                height: 20,
                width: 20,
                decoration: BoxDecoration(
                  color: Colors.blue,
                  shape: BoxShape.circle,
                ),
              ),
            ),
          ],
          radius: 100,
          padding: 0,//add double the radius entered to clip out the right side
          itemMaxHeight: 20,//effects clipping border height
          itemMaxWidth: 20,//effects clipping border width
        ),
      ),
    );
  }
}
class CircularScrollView extends StatefulWidget {
  final List<Widget> items;
  final double radius;
  final double itemMaxHeight;
  final double itemMaxWidth;
  final double padding;
  final bool reverse;
  CircularScrollView(this.items, {Key key, this.radius=10, this.itemMaxHeight=0, this.itemMaxWidth=0, this.padding=0, this.reverse=false}) : super(key: key);
  @override
  _CircularScrollViewState createState() => _CircularScrollViewState();
}

class _CircularScrollViewState extends State<CircularScrollView> {
  double lastPosition;
  List<Widget> transformItems= [];
  double degreesRotated = 0;

  @override
  void initState() {
    setState(() {
      _calculateTransformItems();
    });
    super.initState();
  }
  void _calculateTransformItems(){
    transformItems= [];
    for(int i = 0; i<widget.items.length; i++){
      double startAngle = (i/widget.items.length)*2*math.pi;
      double currentAngle = degreesRotated+startAngle;
      transformItems.add(
        Transform(
          transform: Matrix4.identity()..translate(
            (widget.radius)*math.cos(currentAngle),
            (widget.radius)*math.sin(currentAngle),
          ),
          child: widget.items[i],
        ),
      );
    }
  }
  void _calculateScroll(DragUpdateDetails details){
    if (lastPosition == null){
      lastPosition = details.localPosition.dy;
      return;
    }
    double distance = details.localPosition.dy - lastPosition;
    double distanceWithReversal = widget.reverse?-distance:distance;
    lastPosition =details.localPosition.dy;
    degreesRotated += distanceWithReversal/(widget.radius);
    _calculateTransformItems();
  }

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.centerLeft,
      child: Container(
        height: widget.radius*2+widget.itemMaxHeight,
        width: widget.radius*2 + widget.itemMaxWidth,
        child: GestureDetector(
          onVerticalDragUpdate: (details)=>setState((){_calculateScroll(details);}),
          onVerticalDragEnd: (details){lastPosition=null;},
          child: Container(
            height: double.infinity,
            width: double.infinity,
            color: Colors.transparent,
            child: ClipRect(
              child: Align(
                alignment: Alignment.centerLeft,
                child: Padding(
                  padding: EdgeInsets.only(left: widget.padding),
                  child: Stack(
                    children: transformItems,
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

使用此代码时,请勿修改自定义小部件的内部,除非您确切知道该部分代码的作用。对齐小部件时,请改为从外部包裹自定义小部件。