Skip to content

Instantly share code, notes, and snippets.

@BarryDaBee
Created February 24, 2025 06:38
Show Gist options
  • Select an option

  • Save BarryDaBee/0c7be68480848e0737f6139c5b594e66 to your computer and use it in GitHub Desktop.

Select an option

Save BarryDaBee/0c7be68480848e0737f6139c5b594e66 to your computer and use it in GitHub Desktop.
Flutter Hook implementation of a Slidable widget that snaps to parentWidth / 2 and automatically closes on swipe. This was created because flutter_slidable's action buttons always fill the parent height, which is not always the intended behavior.
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
class AppSlidable extends HookWidget {
const AppSlidable({
required this.child,
required this.actions,
super.key,
});
final Widget child;
final List<Widget> actions;
@override
Widget build(BuildContext context) {
final offset = useState(Offset.zero);
final globalKey = useMemoized(GlobalKey.new);
final size = useState(Size.zero);
final targetOffset = useState(Offset.zero);
useEffect(
() {
/// Get max size of widget
WidgetsBinding.instance.addPostFrameCallback((_) {
final renderBox =
globalKey.currentContext?.findRenderObject() as RenderBox?;
if (renderBox != null) {
size.value = renderBox.size;
}
});
return null;
},
[],
);
void animateTo(Offset newOffset) {
targetOffset.value = newOffset;
}
return Stack(
children: [
AnimatedPositioned(
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
right: -offset.value.dx - size.value.width,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
animateTo(Offset.zero);
},
child: SizedBox(
width: size.value.width,
height: size.value.height,
child: Row(
spacing: 4,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(width: 8),
...actions,
],
),
),
),
),
TweenAnimationBuilder<Offset>(
tween: Tween(begin: offset.value, end: targetOffset.value),
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
onEnd: () => offset.value = targetOffset.value,
builder: (context, animatedOffset, child) {
return Transform.translate(
offset: animatedOffset,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
key: globalKey,
onPanUpdate: (details) {
final updatedOffset =
offset.value + Offset(details.delta.dx, 0);
final hasNotExtendedPastLeft =
updatedOffset.dx >= -size.value.width / 2;
final hasNotExtendedPastRight = updatedOffset.dx <= 0;
if (hasNotExtendedPastLeft && hasNotExtendedPastRight) {
offset.value = updatedOffset;
targetOffset.value = updatedOffset;
}
},
onPanEnd: (details) {
final halfOpen = -size.value.width / 2;
if (offset.value.dx <= halfOpen / 2) {
animateTo(Offset(halfOpen, 0));
} else {
animateTo(Offset.zero);
}
},
child: child,
),
);
},
child: child,
),
],
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment