请如何创建像图片一样颤动的侧面径向菜单,并在用户点击它时滚动
任何帮助,将不胜感激。
这可以通过使用GestureDetector、Transform、三角函数和使用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,
),
),
),
),
),
),
),
);
}
}
使用此代码时,请勿修改自定义小部件的内部,除非您确切知道该部分代码的作用。对齐小部件时,请改为从外部包裹自定义小部件。
您可以尝试使用此包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,
),
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),),) ,
],
),