Skip to content

Instantly share code, notes, and snippets.

@Zfinix
Last active January 25, 2021 08:40
Show Gist options
  • Select an option

  • Save Zfinix/5345cb16c952a7b109ce59084cb4c14c to your computer and use it in GitHub Desktop.

Select an option

Save Zfinix/5345cb16c952a7b109ce59084cb4c14c to your computer and use it in GitHub Desktop.
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(
MaterialApp(
theme: ThemeData.dark(),
debugShowCheckedModeBanner: false,
home: Playground(),
),
);
class Playground extends StatefulWidget {
@override
_PlaygroundState createState() => _PlaygroundState();
}
class _PlaygroundState extends State<Playground> with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFF070813),
body: Center(
child: SlideButton(
vsync: this,
child: Text("SLIDE TO SEND"),
onSlide: () {
print("onChanged");
},
onTap: () {
print("onTap");
},
),
),
);
}
}
class SlideButton extends SingleChildRenderObjectWidget {
SlideButton({
Key key,
this.child,
this.onTap,
this.onSlide,
@required this.vsync,
}) : super(key: key, child: child);
final Widget child;
final VoidCallback onTap;
final VoidCallback onSlide;
final TickerProvider vsync;
@override
RenderSlideButton createRenderObject(BuildContext context) {
return RenderSlideButton(onTap: onTap, onSlide: onSlide, vsync: vsync);
}
@override
void updateRenderObject(
BuildContext context, covariant RenderSlideButton renderObject) {
renderObject
..onTap = onTap
..onSlide = onSlide
..vsync = vsync;
}
}
class RenderSlideButton extends RenderProxyBox {
RenderSlideButton({
RenderBox child,
VoidCallback onTap,
VoidCallback onSlide,
TickerProvider vsync,
}) : _onTap = onTap,
_onSlide = onSlide,
_vsync = vsync,
super(child) {
final physics = BouncingScrollPhysics();
drag = HorizontalDragGestureRecognizer()
..minFlingVelocity = physics.minFlingVelocity
..maxFlingVelocity = physics.maxFlingVelocity
..minFlingDistance = physics.dragStartDistanceMotionThreshold
..onStart = _onDragStart
..onUpdate = _onDragUpdate
..onCancel = _onDragCancel
..onEnd = _onDragEnd;
}
DragGestureRecognizer drag;
AnimationController slideController;
TickerProvider _vsync;
set vsync(TickerProvider vsync) {
assert(vsync != null);
if (vsync == _vsync) {
return;
}
_vsync = vsync;
slideController.resync(_vsync);
}
VoidCallback _onTap;
set onTap(VoidCallback onTap) {
if (_onTap == onTap) {
return;
}
_onTap = onTap;
}
VoidCallback _onSlide;
set onSlide(VoidCallback onSlide) {
if (_onSlide == onSlide) {
return;
}
_onSlide = onSlide;
}
@override
void attach(PipelineOwner owner) {
super.attach(owner);
slideController = AnimationController.unbounded(
value: 0.0,
vsync: _vsync,
duration: Duration(milliseconds: 350),
)..addListener(markNeedsPaint);
}
@override
void detach() {
slideController.removeListener(markNeedsPaint);
super.detach();
}
void _onDragStart(DragStartDetails details) {
slideController.animateTo(size.width * .05).whenCompleteOrCancel(() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_onTap?.call();
});
});
}
void _onDragUpdate(DragUpdateDetails details) {
slideController.value += details.primaryDelta;
}
void _onDragCancel() {
slideController.value = 0.0;
}
void _onDragEnd(DragEndDetails details) {
final threshold = size.width / 4;
if (slideController.value > threshold) {
slideController.animateTo(size.width).whenCompleteOrCancel(() {
_onDragCancel();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_onSlide?.call();
});
});
return;
}
slideController.animateBack(0.0).whenCompleteOrCancel(_onDragCancel);
}
@override
bool hitTestSelf(ui.Offset position) => true;
@override
bool get isRepaintBoundary => true;
@override
void handleEvent(PointerEvent event, covariant BoxHitTestEntry entry) {
if (event is PointerDownEvent) {
drag.addPointer(event);
}
}
@override
void performLayout() {
final effectiveConstraints = constraints.enforce(BoxConstraints(
minHeight: 40,
maxHeight: 80,
maxWidth: 400,
));
size = effectiveConstraints.biggest;
if (child != null) {
child.layout(constraints, parentUsesSize: true);
} else {}
}
@override
void paint(PaintingContext context, Offset offset) {
final canvas = context.canvas;
final value = math.max(0.0, slideController.value);
final t = interpolate(inputMax: size.width)(value);
final bounds = RRect.fromRectAndRadius(offset & size, Radius.circular(18));
canvas.clipRRect(bounds);
canvas.drawRRect(
bounds,
Paint()
..color = Color.lerp(Color(0xFF141224), Color(0xFF5A01CB),
Curves.decelerate.transform(t)),
);
if (child != null) {
context.paintChild(
child,
size.center(Offset(offset.dx - 80, offset.dy - 10)).translate(value, 0),
);
}
}
}
// https://stackoverflow.com/a/55088673/8236404
double Function(double input) interpolate({
double inputMin = 0,
double inputMax = 1,
double outputMin = 0,
double outputMax = 1,
}) {
//range check
if (inputMin == inputMax) {
print("Warning: Zero input range");
return null;
}
if (outputMin == outputMax) {
print("Warning: Zero output range");
return null;
}
//check reversed input range
var reverseInput = false;
final oldMin = math.min(inputMin, inputMax);
final oldMax = math.max(inputMin, inputMax);
if (oldMin != inputMin) {
reverseInput = true;
}
//check reversed output range
var reverseOutput = false;
final newMin = math.min(outputMin, outputMax);
final newMax = math.max(outputMin, outputMax);
if (newMin != outputMin) {
reverseOutput = true;
}
// Hot-rod the most common case.
if (!reverseInput && !reverseOutput) {
final dNew = newMax - newMin;
final dOld = oldMax - oldMin;
return (double x) {
return ((x - oldMin) * dNew / dOld) + newMin;
};
}
return (double x) {
double portion;
if (reverseInput) {
portion = (oldMax - x) * (newMax - newMin) / (oldMax - oldMin);
} else {
portion = (x - oldMin) * (newMax - newMin) / (oldMax - oldMin);
}
double result;
if (reverseOutput) {
result = newMax - portion;
} else {
result = portion + newMin;
}
return result;
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment