颤振中的侧向径向菜单

时间:2018-10-17 03:49:31

标签: dart flutter flutter-layout flutter-animation

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

enter image description here

enter image description here

任何帮助将不胜感激。

2 个答案:

答案 0 :(得分: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,
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

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

答案 1 :(得分:1)

您可以尝试使用此软件包circle_wheel_scroll,在Stack内的此小部件中移动,如有必要,将其定位为负向左。

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

或这个listwheelscrollview

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),),) , 
       ], 
      ),