Last active
May 30, 2022 21:00
-
-
Save jogboms/9a347da5ae1b6ae41d5d2075dfafa9b7 to your computer and use it in GitHub Desktop.
Flutter guild presentation - CustomPainter demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
void main() => runApp(const MaterialApp(home: App())); | |
class App extends StatelessWidget { | |
const App({super.key}); | |
@override | |
Widget build(BuildContext context) => const Scaffold(body: Center(child: SliderWidget())); | |
} | |
class SliderWidget extends StatefulWidget { | |
const SliderWidget({super.key}); | |
@override | |
State<SliderWidget> createState() => _SliderWidgetState(); | |
} | |
class _SliderWidgetState extends State<SliderWidget> { | |
@override | |
Widget build(BuildContext context) { | |
return SizedBox.fromSize( | |
size: Constants.size, | |
child: GestureDetector( | |
onHorizontalDragStart: (details) => _onDragStart(details.localPosition), | |
onHorizontalDragUpdate: (details) => _onDragUpdate(details.localPosition), | |
child: CustomPaint( | |
painter: SliderPainter( | |
dragPosition: _dragPosition, | |
), | |
), | |
), | |
); | |
} | |
double _dragPosition = 0.0; | |
bool _canDragKnob = false; | |
void _onDragStart(Offset position) { | |
final knobRect = Offset(_dragPosition, 0) & Constants.knobSize; | |
final knobLeftPosition = position.dx + Constants.knobRadius; | |
_canDragKnob = knobLeftPosition >= knobRect.left && knobLeftPosition < knobRect.right; | |
} | |
void _onDragUpdate(Offset position) { | |
if (!_canDragKnob) { | |
return; | |
} | |
setState(() { | |
_dragPosition = position.dx.clamp(0.0, Constants.size.width); | |
}); | |
} | |
} | |
class SliderPainter extends CustomPainter { | |
SliderPainter({required this.dragPosition}); | |
final double dragPosition; | |
@override | |
void paint(Canvas canvas, Size size) { | |
final knobTrackRect = Offset.zero.shift(Constants.knobToTrackPadding) & Constants.knobTrackSize; | |
canvas.drawRRect( | |
Constants.borderRadius.toRRect(knobTrackRect), | |
Paint()..shader = Constants.gradient.createShader(knobTrackRect), | |
); | |
final lowerLimit = knobTrackRect.left; | |
final upperLimit = knobTrackRect.right; | |
final knobLeftPosition = dragPosition.clamp(lowerLimit, upperLimit); | |
final knobColor = Constants.gradientColors.mix(lowerLimit, upperLimit, knobLeftPosition); | |
final knobCenter = Offset(knobLeftPosition, size.height / 2); | |
canvas | |
..drawShadow( | |
Path()..addOval(Rect.fromCircle(center: knobCenter, radius: Constants.knobRadius)), | |
Constants.knobShadowColor, | |
Constants.knobElevation, | |
true, | |
) | |
..drawCircle( | |
knobCenter, | |
Constants.knobRadius, | |
Paint()..color = Constants.knobBackgroundColor, | |
) | |
..drawCircle( | |
knobCenter, | |
Constants.knobRadius - Constants.knobToTrackPadding, | |
Paint()..color = knobColor, | |
); | |
} | |
@override | |
bool shouldRepaint(SliderPainter oldDelegate) => dragPosition != oldDelegate.dragPosition; | |
} | |
class Constants { | |
Constants._(); | |
static const size = Size(720, 120); | |
static final knobDimension = size.height; | |
static final knobSize = Size.square(knobDimension); | |
static final knobRadius = knobDimension / 2; | |
static final knobToTrackPadding = knobDimension * .15; | |
static final knobTrackSize = Size(size.width - knobToTrackPadding * 2, size.height - knobToTrackPadding * 2); | |
static const knobBackgroundColor = Colors.white; | |
static const knobElevation = 8.0; | |
static const knobShadowColor = Colors.black87; | |
static final borderRadius = BorderRadius.circular(size.height / 2); | |
static const gradientColors = [Colors.blueAccent, Colors.purpleAccent]; | |
static const gradient = LinearGradient(colors: gradientColors); | |
} | |
// https://stackoverflow.com/a/55088673/8236404 | |
double Function(double input) interpolate({ | |
double inputMin = 0, | |
double inputMax = 1, | |
double outputMin = 0, | |
double outputMax = 1, | |
}) { | |
assert(inputMin != inputMax || outputMin != outputMax); | |
final diff = (outputMax - outputMin) / (inputMax - inputMin); | |
return (input) => ((input - inputMin) * diff) + outputMin; | |
} | |
extension ShiftExtension on Offset { | |
Offset shift(double delta) => translate(delta, delta); | |
} | |
extension MixGradientColor on List<Color> { | |
Color mix(double min, double max, double value) => | |
Color.lerp(first, last, interpolate(inputMin: min, inputMax: max)(value))!; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment