Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter custom range slider

I'm trying to create a range slider on top of a Row of Containers which should create an audio waveform, but I have no idea where to even start...

The main issue is that the range slider sits right on top of the row of containers and it should change their colors on the "selected" section.

enter image description here

Here's what I currently have:

enter image description here

The code to create the image and details.

class BeatLyricsPage extends StatefulWidget {
  final Beat beat;
  BeatLyricsPage(this.beat);

  @override
  _BeatLyricsPageState createState() => _BeatLyricsPageState(beat);
}

class _BeatLyricsPageState extends State<BeatLyricsPage> {
  final Beat beat;


  final _kPicHeight = 190.0;
  // used in _buildPageHeading to add the beat key and beat bpm
  Widget _buildBeatInfoItem(String text) => DecoratedBox(
        decoration: BoxDecoration(
          border: Border.all(color: MyColor.white, width: 1.0),
          borderRadius: BorderRadius.circular(4.0),
        ),
        child: Padding(
          padding: EdgeInsets.symmetric(vertical: 3.0, horizontal: 12.0),
          child: Text(text, style: TextStyle(color: MyColor.white, fontSize: 10.0, fontWeight: FontWeight.w600)),
        ),
      );

  final _kAudioControlsWidth = 180.0;
  final _kAudioControlsHeight = 36.0;
  final _kAudioControlsMainButtonSize = 56.0;

  Widget _buildAudioControls(BuildContext context) => Positioned(
        left: (MediaQuery.of(context).size.width / 2) - (_kAudioControlsWidth / 2),
        top: _kPicHeight - (_kAudioControlsHeight / 2),
        child: Stack(
          overflow: Overflow.visible,
          children: [
            Container(
              width: _kAudioControlsWidth,
              height: _kAudioControlsHeight,
              decoration: BoxDecoration(color: MyColor.darkGrey, borderRadius: BorderRadius.circular(100.0)),
              padding: EdgeInsets.symmetric(horizontal: LayoutSpacing.sm),
              child: Row(
                children: [
                  CButtonLike(beatId: beat.id),
                  Spacer(),
                  GestureDetector(
                    behavior: HitTestBehavior.opaque,
                    child: Icon(BeatPulseIcons.cart),
                    onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => LicenseOptionsPage(beat))),
                  ),
                ],
              ),
            ),
            // ****** MAIN BUTTON (Play/Pause) ******
            Positioned(
              left: (_kAudioControlsWidth / 2) - (_kAudioControlsMainButtonSize / 2),
              top: (_kAudioControlsHeight - _kAudioControlsMainButtonSize) / 2,
              child: Container(
                height: _kAudioControlsMainButtonSize,
                width: _kAudioControlsMainButtonSize,
                decoration: BoxDecoration(
                    gradient: LinearGradient(begin: Alignment.topLeft, colors: [MyColor.primary, Color(0xFFf80d0a)]),
                    borderRadius: BorderRadius.circular(100.0)),
                child: CButtonPlay(),
              ),
            )
          ],
        ),
      );

  Widget _buildWaveForm() {
    // creates a random list of doubles, "fake data"
    var rng = Random();
    final List waveFormData = [];
    for (var i = 0; i < 90; i++) {
      waveFormData.add(rng.nextInt(45).toDouble());
    }
    // player bloc
    final playerBloc = BlocProvider.getPlayerBloc(context);
    // renders
    return Container(
      height: _kPicHeight,
      padding: EdgeInsets.symmetric(vertical: LayoutSpacing.xxxl),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.end,
        children: [
          // current playing second
          StreamBuilder<double>(
            stream: playerBloc.playingSecond,
            initialData: 0.0,
            builder: (_, playingSecondSnapshot) {
              // current beat playing
              return StreamBuilder<Beat>(
                stream: playerBloc.playingBeat,
                builder: (_, playingBeatSnapshot) {
                  final playingBeat = playingBeatSnapshot.data;
                  // if the beat playing is the same as the beat selected for the lyrics, show playing seconds
                  if (playingBeat?.id == beat.id)
                    return Text(secondsToTime(playingSecondSnapshot.data), style: MyFontStyle.sizeXxs);
                  // otherwise show 0:00
                  else
                    return Text(secondsToTime(0), style: MyFontStyle.sizeXxs);
                },
              );
            },
          ),
          SizedBox(width: LayoutSpacing.xs),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.end,
            children: waveFormData
                .map((waveFormDataIndex) => Container(
                      height: waveFormDataIndex > 5.0 ? waveFormDataIndex : 5.0,
                      width: 2,
                      color: MyColor.white,
                      margin: EdgeInsets.only(right: 1),
                    ))
                .toList(),
          ),
          SizedBox(width: LayoutSpacing.xs),
          Text(secondsToTime(beat.length), style: MyFontStyle.sizeXxs),
        ],
      ),
    );
  }

  Widget _buildPageHeading(BuildContext context, {@required String imageUrl}) => Stack(
        children: [
          Column(
            children: [
              Hero(
                tag: MyKeys.makePlayerCoverKey(beat.id),
                child: Opacity(
                  opacity: 0.3,
                  child: Container(
                    height: _kPicHeight,
                    decoration: BoxDecoration(
                      image: DecorationImage(image: CachedNetworkImageProvider(imageUrl), fit: BoxFit.cover),
                    ),
                  ),
                ),
              ),
              Container(color: MyColor.background, height: LayoutSpacing.xl)
            ],
          ),
          Padding(
            padding: EdgeInsets.all(LayoutSpacing.xs),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: [
                _buildBeatInfoItem(beat.key),
                SizedBox(width: 4.0),
                _buildBeatInfoItem('${beat.bpm} BPM'),
              ],
            ),
          ),
          _buildAudioControls(context),
          _buildWaveForm(),
        ],
      );
}
like image 406
Giacomo Avatar asked Oct 25 '18 10:10

Giacomo


People also ask

How do you change the color of the range slider in flutter?

Thumb overlay color You can change the color of the thumb overlay in the range slider using the overlayColor property. You must import the theme.

How do you change the slider width in flutter?

You can use SliderTheme to customize the slider appearance and wrap Slider with Container to give custom width for the slider. It will take Container width as Slider width. Save this answer.

What is material rangeslider in flutter?

Material RangeSlider in Flutter. The range slider, a highly customizable component for selecting a range of values, has been released in Flutter 1.7. This article explains what a range slider is, why you might use it, and how you can use Material Theming to customize the behavior and appearance of the Flutter RangeSlider.

How to draw a slider in flutter?

Every single UI component library has a prebuilt component to draw a slider ( aka range). Flutter, of course, also has its own version. It looks quite basic, but it’s very easy to customize by wrapping it with a SliderTheme. Leaving aside the logic behind, there are three main elements in the widget:

What is the range slider widget?

[A range slider widget, consisting of 5 divisions and showing the default // divisions, from 0 to 100. This means are values are split between 0, 20, 40, // 60, 80, and 100. The range values are initialized with 40 and 80 in this demo. /// This is the main application widget. /// This is the stateful widget that the main application instantiates.

How do I change the state of a range slider?

The range slider widget itself does not maintain any state. Instead, when the state of the slider changes, the widget calls the onChanged callback. Most widgets that use a range slider will listen for the onChanged callback and rebuild the slider with new values to update the visual appearance of the slider.


Video Answer


2 Answers

To create a custom range slider, you can use the GestureRecognizer and save the position of each slider in variable inside a StatefulWidget. To decide wether a bar with the index i is inside the range, you can divide the pixel position of the limiter(bar1&bar2 in the source below) by the width of bars and compare it to i.

Sadly I couldn't work with your code example. Instead I created a bare minimum example as you can see below. If you take a minute to read into, I'm sure you can transfer it to your application.

Custom Range Slider

import 'dart:math';

import 'package:flutter/material.dart';

List<int> bars = [];

void main() {
  // generate random bars
  Random r = Random();
  for (var i = 0; i < 50; i++) {
    bars.add(r.nextInt(200));
  }

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Home(),
    );
  }
}

class Home extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => HomeState();
}

class HomeState extends State<Home> {
  static const barWidth = 5.0;
  double bar1Position = 60.0;
  double bar2Position = 180.0;

  @override
  Widget build(BuildContext context) {
    int i = 0;

    return Scaffold(
      body: Center(
        child: Stack(
          alignment: Alignment.centerLeft,
          children: <Widget>[
            Row(
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisAlignment: MainAxisAlignment.start,
              children: bars.map((int height) {
                Color color =
                    i >= bar1Position / barWidth && i <= bar2Position / barWidth
                        ? Colors.deepPurple
                        : Colors.blueGrey;
                i++;

                return Container(
                  color: color,
                  height: height.toDouble(),
                  width: 5.0,
                );
              }).toList(),
            ),
            Bar(
              position: bar2Position,
              callback: (DragUpdateDetails details) {
                setState(() {
                  bar2Position += details.delta.dx;
                });
              },
            ),
            Bar(
              position: bar1Position,
              callback: (DragUpdateDetails details) {
                setState(() {
                  bar1Position += details.delta.dx;
                });
              },
            ),
          ],
        ),
      ),
    );
  }
}

class Bar extends StatelessWidget {
  final double position;
  final GestureDragUpdateCallback callback;

  Bar({this.position, this.callback});

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.only(left: position >= 0.0 ? position : 0.0),
      child: GestureDetector(
        onHorizontalDragUpdate: callback,
        child: Container(
          color: Colors.red,
          height: 200.0,
          width: 5.0,
        ),
      ),
    );
  }
}
like image 126
NiklasPor Avatar answered Nov 15 '22 18:11

NiklasPor


in order to have a wave slider :

class WaveSlider extends StatefulWidget {
  final double initialBarPosition;
  final double barWidth;
  final int maxBarHight;
  final double width;
  WaveSlider({
    this.initialBarPosition = 0.0,
    this.barWidth = 5.0,
    this.maxBarHight = 50,
    this.width = 60.0,
  });
  @override
  State<StatefulWidget> createState() => WaveSliderState();
}

class WaveSliderState extends State<WaveSlider> {
  List<int> bars = [];
  double barPosition;
  double barWidth;
  int maxBarHight;
  double width;

  int numberOfBars;

  void randomNumberGenerator() {
    Random r = Random();
    for (var i = 0; i < numberOfBars; i++) {
      bars.add(r.nextInt(maxBarHight - 10) + 10);
    }
  }

  _onTapDown(TapDownDetails details) {
    var x = details.globalPosition.dx;
    print("tap down " + x.toString());
    setState(() {
      barPosition = x;
    });
  }

  @override
  void initState() {
    super.initState();
    barPosition = widget.initialBarPosition;
    barWidth = widget.barWidth;
    maxBarHight = widget.maxBarHight.toInt();
    width = widget.width;
    if (bars.isNotEmpty) bars = [];
    numberOfBars = width ~/ barWidth;
    randomNumberGenerator();
  }

  @override
  Widget build(BuildContext context) {
    int barItem = 0;
    return Scaffold(
      backgroundColor: Colors.grey[900],
      body: Center(
        child: GestureDetector(
          onTapDown: (TapDownDetails details) => _onTapDown(details),
          onHorizontalDragUpdate: (DragUpdateDetails details) {
            setState(() {
              barPosition = details.globalPosition.dx;
            });
          },
          child: Container(
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.end,
              mainAxisAlignment: MainAxisAlignment.start,
              children: bars.map((int height) {
                Color color = barItem + 1 < barPosition / barWidth
                    ? Colors.white
                    : Colors.grey[600];
                barItem++;
                return Row(
                  children: <Widget>[
                    Container(
                      width: .1,
                      height: height.toDouble(),
                      color: Colors.black,
                    ),
                    Container(
                      decoration: BoxDecoration(
                        color: color,
                        borderRadius: BorderRadius.only(
                          topLeft: const Radius.circular(1.0),
                          topRight: const Radius.circular(1.0),
                        ),
                      ),
                      height: height.toDouble(),
                      width: 4.8,
                    ),
                    Container(
                      width: .1,
                      height: height.toDouble(),
                      color: Colors.black,
                    ),
                  ],
                );
              }).toList(),
            ),
          ),
        ),
      ),
    );
  }
}

and use it like :

WaveSlider(
        initialBarPosition: 180.0,
        barWidth: 5.0,
        maxBarHight: 50,
        width: MediaQuery.of(context).size.width,
      )
like image 23
mohammad Avatar answered Nov 15 '22 20:11

mohammad