final class VolumeControlWidget extends StatefulWidget {
const VolumeControlWidget({super.key});
@override
State<VolumeControlWidget> createState() => _VolumeControlWidgetState();
}
class _VolumeControlWidgetState extends State<VolumeControlWidget> {
final double maxVolume = kMinInteractiveDimension * 5;
final double minVolume = kMinInteractiveDimension * .5;
double currentVolume = 0;
final double borderThickness = 1.5;
void setVolume(double volume) {
setState(() {
currentVolume = volume.clamp(minVolume, maxVolume).roundToDouble();
});
}
@override
void initState() {
currentVolume = minVolume;
super.initState();
}
bool get isMax => currentVolume == maxVolume;
bool get isMin => currentVolume <= minVolume;
int get volumePercentage => ((currentVolume / maxVolume) * 100).round().clamp(0, 100);
@override
Widget build(BuildContext context) {
return Container(
height: maxVolume,
width: kMinInteractiveDimension * 1.2,
constraints: BoxConstraints(minHeight: minVolume, maxHeight: maxVolume),
decoration: ShapeDecoration(
shape: StadiumBorder(
side: BorderSide(
width: borderThickness,
color: Colors.deepPurpleAccent.shade700,
),
),
),
clipBehavior: Clip.antiAlias,
child: RawGestureDetector(
gestures: {
VerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
VerticalDragGestureRecognizer.new,
(VerticalDragGestureRecognizer instance) {
instance.onUpdate = (DragUpdateDetails details) {
final double newVolume = currentVolume - details.delta.dy;
setVolume(newVolume);
};
},
),
},
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 50),
constraints: BoxConstraints(minHeight: minVolume, maxHeight: maxVolume),
///
/// (borderThickness * 2) because , we have border on top and bottom
///
///
height: currentVolume - (borderThickness * 2),
width: isMin ? kMinInteractiveDimension : kMinInteractiveDimension * 1.2,
decoration: BoxDecoration(
borderRadius: const BorderRadius.vertical(
top: Radius.circular(200),
),
gradient: LinearGradient(
colors: [
Colors.deepPurpleAccent.shade200,
Colors.deepPurpleAccent.shade400,
Colors.deepPurpleAccent.shade700,
],
begin: AlignmentDirectional.bottomCenter,
end: AlignmentDirectional.topCenter,
),
),
child: isMin
? Align(
alignment: isMin
? const AlignmentDirectional(
0,
0.2,
)
: Alignment.center,
child: Container(
width: kMinInteractiveDimension * .5,
height: isMin ? 1 : borderThickness,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(2),
),
),
)
:
///
/// Optional : You can use only Sizedbox() instead of this
///
SizedBox(
child: Center(
child: Text(
'% ${volumePercentage.toStringAsFixed(1)}',
style: const TextStyle(color: Colors.white),
),
),
),
),
],
),
),
);
}
}