11

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

在此处输入图像描述

在此处输入图像描述

在此处输入图像描述

任何帮助,将不胜感激。

4

2 回答 2

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,
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

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

于 2020-10-25T20:33:08.440 回答
1

您可以尝试使用此包circle_wheel_scroll,在 Stack 内围绕此小部件移动,如有必要,将 Positioned 放置在负左位置。

CircleListScrollView(
              physics: CircleFixedExtentScrollPhysics(),
              axis: Axis.horizontal,
              itemExtent: 80,
              children: List.generate(20, _buildItem),
              radius: MediaQuery.of(context).size.width * 0.6,
            ),

或此列表wheelscrollview

ListWheelScrollView( 
        itemExtent: 100, 
          
        // diameterRatio: 1.6, 
        // offAxisFraction: -0.4, 
        // squeeze: 0.8, 
        clipToSize: true, 
       children: <Widget>[ 
         RaisedButton(onPressed:null , 
       child: Text("Item 1",textAlign:TextAlign.start, 
            style:TextStyle(color:Colors.black,fontWeight: FontWeight.bold,fontSize: 25),),) , 
       RaisedButton(onPressed:null , 
       child: Text("Item 2",textAlign:TextAlign.center, 
            style:TextStyle(color:Colors.black,fontWeight: FontWeight.bold,fontSize: 25),),) , 
       RaisedButton(onPressed:null , 
       child: Text("Item 3",textAlign:TextAlign.center, 
            style:TextStyle(color:Colors.black,fontWeight: FontWeight.bold,fontSize: 25),),) , 
       RaisedButton(onPressed:null , 
       child: Text("Item 4",textAlign:TextAlign.center, 
            style:TextStyle(color:Colors.black,fontWeight: FontWeight.bold,fontSize: 25),),) , 
       RaisedButton(onPressed:null , 
       child: Text("Item 5",textAlign:TextAlign.center, 
            style:TextStyle(color:Colors.black,fontWeight: FontWeight.bold,fontSize: 25),),) , 
       RaisedButton(onPressed:null , 
       child: Text("Item 6",textAlign:TextAlign.center, 
            style:TextStyle(color:Colors.black,fontWeight: FontWeight.bold,fontSize: 25),),) , 
       RaisedButton(onPressed:null , 
       child: Text("Item 7",textAlign:TextAlign.center, 
            style:TextStyle(color:Colors.black,fontWeight: FontWeight.bold,fontSize: 25),),) , 
       RaisedButton(onPressed:null , 
       child: Text("Item 8",textAlign:TextAlign.center, 
            style:TextStyle(color:Colors.black,fontWeight: FontWeight.bold,fontSize: 25),),) , 
       ], 
      ), 
于 2020-10-27T10:27:02.403 回答