How to create a range slider with thumb as png image in flutter How to create a range slider with thumb as png image in flutter dart dart

How to create a range slider with thumb as png image in flutter


You need to create custom slider.

Let’s split tasks:

Gestures:

  • Tap, when user taps on the one of the labels.
  • Drag, when user drags the indicator.

Animations:

  • Background animations

    • moving text
    • scaling head
  • Switcher animations:

    • color
    • face

Extra: - Need to think what to do when user stop drugging between two labels.

After 15 hours of thinking and coding, I have:

enter image description here

import 'package:flutter/material.dart';import 'package:vector_math/vector_math.dart' as v_math;void main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Flutter Demo',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: Scaffold(        body: SafeArea(          child: Column(            mainAxisAlignment: MainAxisAlignment.center,            children: <Widget>[              Text('How was the help you recived?', style: TextStyle(color: Color(0xFF6f7478), fontSize: 18),),              SizedBox(height: 20),              ReviewSlider()            ],          ),        ),      ),    );  }}class ReviewSlider extends StatefulWidget {  @override  _ReviewSliderState createState() => _ReviewSliderState();}class _ReviewSliderState extends State<ReviewSlider> with SingleTickerProviderStateMixin {  double intitalReviewValue = 2;  final List<String> reviews = ['Terrible', 'Bad', 'Okay', 'Good', 'Great'];  Animation<double> _animation;  AnimationController _controller;  Tween<double> _tween;  double _innerWidth;  double _animationValue;  @override  void initState() {    super.initState();    _controller = AnimationController(      value: intitalReviewValue,      vsync: this,      duration: Duration(milliseconds: 400),    );    _tween = Tween(end: intitalReviewValue);    _animation = _tween.animate(      CurvedAnimation(        curve: Curves.easeIn,        parent: _controller,      ),    )..addListener(() {        setState(() {          _animationValue = _animation.value;        });      });    _animationValue = intitalReviewValue;    WidgetsBinding.instance.addPostFrameCallback(_afterLayout);  }  _afterLayout(_) {    setState(() {      _innerWidth = MediaQuery.of(context).size.width - 2 * paddingSize;    });  }  void handleTap(int state) {    _controller.duration = Duration(milliseconds: 400);    _tween.begin = _tween.end;    _tween.end = state.toDouble();    _controller.reset();    _controller.forward();  }  _onDrag(details) {    var newAnimatedValue = _calcAnimatedValueFormDragX(      details.globalPosition.dx,    );    if (newAnimatedValue > 0 && newAnimatedValue < reviews.length - 1) {      setState(        () {          _animationValue = newAnimatedValue;        },      );    }  }  _calcAnimatedValueFormDragX(x) {    return (x - circleDiameter / 2 - paddingSize * 2) / _innerWidth * reviews.length;  }  _onDragEnd(_) {    _controller.duration = Duration(milliseconds: 100);    _tween.begin = _animationValue;    _tween.end = _animationValue.round().toDouble();    _controller.reset();    _controller.forward();  }  @override  void dispose() {    super.dispose();    _controller.dispose();  }  @override  Widget build(BuildContext context) {    return Center(      child: _innerWidth == null          ? Container()          : Container(              padding: EdgeInsets.symmetric(horizontal: paddingSize),              height: 200,              child: Stack(children: <Widget>[                MeasureLine(                  states: reviews,                  handleTap: handleTap,                  animationValue: _animationValue,                  width: _innerWidth,                ),                MyIndicator(                  animationValue: _animationValue,                  width: _innerWidth,                  onDrag: _onDrag,                  onDragEnd: _onDragEnd,                ),                Text(_animationValue.round().toString()),              ]),            ),    );  }}const double circleDiameter = 60;const double paddingSize = 10;class MeasureLine extends StatelessWidget {  MeasureLine({this.handleTap, this.animationValue, this.states, this.width});  final double animationValue;  final Function handleTap;  final List<String> states;  final double width;  List<Widget> _buildUnits() {    var res = <Widget>[];    var animatingUnitIndex = animationValue.round();    var unitAnimatingValue = (animationValue * 10 % 10 / 10 - 0.5).abs() * 2;    states.asMap().forEach((index, text) {      var paddingTop = 0.0;      var scale = 0.7;      var opacity = .3;      if (animatingUnitIndex == index) {        paddingTop = unitAnimatingValue * 5;        scale = (1 - unitAnimatingValue) * 0.7;        opacity = 0.3 + unitAnimatingValue * 0.7;      }      res.add(LimitedBox(        key: ValueKey(text),        maxWidth: circleDiameter,        child: GestureDetector(          onTap: () {            handleTap(index);          },          child: Column(            mainAxisAlignment: MainAxisAlignment.start,            children: <Widget>[              Transform.scale(                  scale: scale,                  child: Stack(                    children: [                      Head(),                      Face(                        color: Colors.white,                        animationValue: index.toDouble(),                      )                    ],                  )),              Padding(                padding: EdgeInsets.only(top: paddingTop),                child: Opacity(                  opacity: opacity,                  child: Text(                    text,                    style: TextStyle(color: Colors.black),                  ),                ),              )            ],          ),        ),      ));    });    return res;  }  @override  Widget build(BuildContext context) {    return Stack(      children: <Widget>[        Positioned(          top: circleDiameter / 2,          left: 20,          width: width - 40,          child: Container(            width: width,            color: Color(0xFFeceeef),            height: 3,          ),        ),        Row(          mainAxisAlignment: MainAxisAlignment.spaceBetween,          children: _buildUnits(),        ),      ],    );  }}class Face extends StatelessWidget {  Face({    this.color = const Color(0xFF616154),    this.animationValue,  });  final Color color;  final double animationValue;  @override  Widget build(BuildContext context) {    return Container(      height: circleDiameter,      width: circleDiameter,      child: CustomPaint(        size: Size(300, 300),        painter: MyPainter(animationValue, color: color),      ),    );  }}class MyPainter extends CustomPainter {  MyPainter(    animationValue, {    this.color = const Color(0xFF615f56),  })  : activeIndex = animationValue.floor(),        unitAnimatingValue = (animationValue * 10 % 10 / 10);  Color color;  final int activeIndex;  final double unitAnimatingValue;  @override  void paint(Canvas canvas, Size size) {    _drawEye(canvas, size);    _drawMouth(canvas, size);  }  _drawEye(canvas, size) {    var angle = 0.0;    var wide = 0.0;    switch (activeIndex) {      case 0:        angle = 55 - unitAnimatingValue * 50;        wide = 80.0;        break;      case 1:        wide = 80 - unitAnimatingValue * 80;        angle = 5;        break;    }    var degree1 = 90 * 3 + angle;    var degree2 = 90 * 3 - angle + wide;    var x1 = size.width / 2 * 0.65;    var x2 = size.width - x1;    var y = size.height * 0.41;    var eyeRadius = 5.0;    var paint = Paint()..color = color;    canvas.drawArc(      Rect.fromCircle(        center: Offset(x1, y),        radius: eyeRadius,      ),      v_math.radians(degree1),      v_math.radians(360 - wide),      false,      paint,    );    canvas.drawArc(      Rect.fromCircle(        center: Offset(x2, y),        radius: eyeRadius,      ),      v_math.radians(degree2),      v_math.radians(360 - wide),      false,      paint,    );  }  _drawMouth(Canvas canvas, size) {    var upperY = size.height * 0.70;    var lowerY = size.height * 0.77;    var middleY = (lowerY - upperY) / 2 + upperY;    var leftX = size.width / 2 * 0.65;    var rightX = size.width - leftX;    var middleX = size.width / 2;    double y1, y3, x2, y2;    Path path2;    switch (activeIndex) {      case 0:        y1 = lowerY;        x2 = middleX;        y2 = upperY;        y3 = lowerY;        break;      case 1:        y1 = lowerY;        x2 = middleX;        y2 = unitAnimatingValue * (middleY - upperY) + upperY;        y3 = lowerY - unitAnimatingValue * (lowerY - upperY);        break;      case 2:        y1 = unitAnimatingValue * (upperY - lowerY) + lowerY;        x2 = middleX;        y2 = unitAnimatingValue * (lowerY + 3 - middleY) + middleY;        y3 = upperY;        break;      case 3:        y1 = upperY;        x2 = middleX;        y2 = lowerY + 3;        y3 = upperY;        path2 = Path()          ..moveTo(leftX, y1)          ..quadraticBezierTo(            x2,            y2,            upperY - 2.5,            y3 - 2.5,          )          ..quadraticBezierTo(            x2,            y2 - unitAnimatingValue * (y2 - upperY + 2.5),            leftX,            upperY - 2.5,          )          ..close();        break;      case 4:        y1 = upperY;        x2 = middleX;        y2 = lowerY + 3;        y3 = upperY;        path2 = Path()          ..moveTo(leftX, y1)          ..quadraticBezierTo(            x2,            y2,            upperY - 2.5,            y3 - 2.5,          )          ..quadraticBezierTo(            x2,            upperY - 2.5,            leftX,            upperY - 2.5,          )          ..close();        break;    }    var path = Path()      ..moveTo(leftX, y1)      ..quadraticBezierTo(        x2,        y2,        rightX,        y3,      );    canvas.drawPath(        path,        Paint()          ..color = color          ..style = PaintingStyle.stroke          ..strokeCap = StrokeCap.round          ..strokeWidth = 5);    if (path2 != null) {      canvas.drawPath(        path2,        Paint()          ..color = color          ..style = PaintingStyle.fill          ..strokeCap = StrokeCap.round,      );    }  }  @override  bool shouldRepaint(MyPainter oldDelegate) {    return unitAnimatingValue != oldDelegate.unitAnimatingValue ||        activeIndex != oldDelegate.activeIndex;  }}class MyIndicator extends StatelessWidget {  MyIndicator({    this.animationValue,    width,    this.onDrag,    this.onDragStart,    this.onDragEnd,  })  : width = width - circleDiameter,        possition = animationValue == 0 ? 0 : animationValue / 4;  final double possition;  final Function onDrag;  final Function onDragStart;  final Function onDragEnd;  final double width;  final double animationValue;  @override  Widget build(BuildContext context) {    return Container(      child: Positioned(        top: 0,        left: width * possition,        child: _buildIndicator(),      ),    );  }  _buildIndicator() {    var opacityOfYellow = possition > 0.5 ? 1.0 : possition * 2;    return GestureDetector(      onPanDown: onDragStart,      onPanUpdate: onDrag,      onPanStart: onDrag,      onPanEnd: onDragEnd,      child: Container(        width: circleDiameter,        height: circleDiameter,        child: Stack(          children: <Widget>[            Head(              color: Color(0xFFf4b897),              hasShadow: true,            ),            Opacity(              opacity: opacityOfYellow,              child: Head(                color: Color(0xFFfee385),              ),            ),            Face(              animationValue: animationValue,            )          ],        ),      ),    );  }}class Head extends StatelessWidget {  Head({this.color = const Color(0xFFc9ced2), this.hasShadow = false});  final Color color;  final bool hasShadow;  @override  Widget build(BuildContext context) {    return Container(      height: circleDiameter,      width: circleDiameter,      decoration: BoxDecoration(        boxShadow: hasShadow            ? [BoxShadow(color: Colors.black26, offset: Offset(0, 2), blurRadius: 5.0)]            : null,        color: color,        shape: BoxShape.circle,      ),    );  }}

https://github.com/kherel/review_slider