Skip to content

Instantly share code, notes, and snippets.

@pishguy
Forked from pskink/wheel.dart
Created April 15, 2023 18:03
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save pishguy/f041b50d92ab07d2d3956a3358b34857 to your computer and use it in GitHub Desktop.
import 'dart:math';
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
import 'package:flutter/scheduler.dart';
void main() {
// debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
// timeDilation = 1;
runApp(MyApp());
}
var colors = [Color(0xffff8800), Color(0xff00aa00), Color(0xff0000aa),];
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.grey,
body: Wheel(
itemCount: 32,
itemBuilder: (ctx, index, item) {
var t = 0.5 + sin(item.normalizedPhase) / 2;
return Opacity(
opacity: lerpDouble(0.125, 1.0, item.depth),
child: Container(
width: item.width,
height: item.width * 9 / 16,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
gradient: LinearGradient(
begin: Alignment.lerp(Alignment.topLeft, Alignment.topCenter, t),
end: Alignment.bottomCenter,
colors: [Colors.white, colors[index % colors.length]],
stops: [0, 0.4],
),
border: Border.all(color: Colors.black54, width: 4),
),
child: Text('item #$index', textScaleFactor: 1.5,),
),
);
},
),
),
);
}
}
// --------------------------------------------------------------
// --------------------------------------------------------------
// --------------------------------------------------------------
typedef WheelItemBuilder = Widget Function(BuildContext context, int index, WheelItem item);
class WheelItem implements Comparable<WheelItem> {
double x, y, z, phase, normalizedPhase, angle, width, depth;
int index;
WheelItem(this.phase) : x = 0, y = 0, z = 0;
@override
int compareTo(WheelItem other) => z.compareTo(other.z);
}
class Wheel extends StatefulWidget {
final WheelItemBuilder itemBuilder;
final int itemCount;
const Wheel({
Key key,
@required this.itemBuilder,
@required this.itemCount,
}) : super(key: key);
@override
_WheelState createState() => _WheelState();
}
class _WheelState extends State<Wheel> with SingleTickerProviderStateMixin {
AnimationController controller;
List<WheelItem> items = [];
int numItems = 9;
double step;
@override
void initState() {
step = (pi * 2) / numItems;
items = List.generate(numItems, (index) => WheelItem(index * step));
controller = AnimationController.unbounded(vsync: this);
super.initState();
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return AnimatedBuilder(
animation: controller,
builder: (context, child) {
double w = constraints.maxWidth * 0.4;
// process positions.
items.forEach((item) {
final angle = item.phase + controller.value + pi / 2;
final sinus = sin(angle);
final a = controller.value + pi;
final normalizedPhase = (a + item.phase) % (2 * pi);
item
..width = step * w * 0.75
..angle = angle + pi / 2
..x = cos(angle) * w
..z = sinus * w
..depth = 0.5 + sinus / 2
..normalizedPhase = normalizedPhase
..index = ((a - normalizedPhase) / step).round();
});
// sort in Z axis.
items.sort();
return GestureDetector(
onPanStart: (d) => controller.stop(),
onPanUpdate: (d) => controller.value -= d.delta.dx / 100,
onPanEnd: (d) => controller.animateWith(wheelSimulation(step, controller.value, -d.velocity.pixelsPerSecond.dx / 100)),
behavior: HitTestBehavior.opaque,
child: Container(
alignment: Alignment.center,
child: Stack(
children: items.map(mapItem).toList(),
),
),
);
}
);
}
);
}
static final SpringDescription spring = SpringDescription.withDampingRatio(
mass: 0.5,
stiffness: 100.0,
ratio: 1.1,
// ratio: 1.0 - 0.0001,
);
Simulation wheelSimulation(double step, double start, double velocity) {
// hard to tell which one is beter:
var end = start + velocity * 0.15;
// var end = start + velocity.sign * velocity * velocity / 200;
end = (end / step).round() * step;
return SpringSimulation(spring, start, end, velocity);
// return FrictionSimulation.through(start, end, velocity, 0);
}
Widget mapItem(WheelItem item) {
var matrix = Matrix4.identity()
..setEntry(3, 2, 0.001)
..translate(item.x, item.y, -item.z)
..rotateY(item.angle + pi);
return Transform(
alignment: Alignment.center,
transform: matrix,
child: widget.itemBuilder(context, item.index % widget.itemCount, item),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment