How to create a setState function for AnimatedContainer - dart

I have created my custom stateful widget and I am taking a boolean that I use to check the state of the AnimatedContainer. Also, there's a function in createState to check the state of the AnimatedContainer and change the width of the container. My problem is that I am trying to use the function _handleTap() in AnimatedContainer as I child but it gives me an error saying that the expression has a type of void therefore, can't be used.
class SectionTaps extends StatefulWidget {
SectionTaps(this.isActive);
bool isActive = false;
_SectionTapsState createState() => _SectionTapsState();
}
class _SectionTapsState extends State<SectionTaps> {
bool _isActive = false;
double _width = 255.0;
void _handleTap(){
setState(() {
_isActive = widget.isActive;
_isActive == true ? _width = 55.0 : _width = 255.0;
//change container width
});
}
final leftButton = new AnimatedContainer(
duration: Duration(seconds: 1),
height: 88.0,
width: 255.0,
decoration: new BoxDecoration(
color: new Color(0xFF376480),
shape: BoxShape.rectangle,
borderRadius: new BorderRadius.only(
topRight: Radius.circular(80.0),
bottomRight: Radius.circular(80.0),
),
boxShadow: <BoxShadow>[
new BoxShadow(
color: Colors.black12,
blurRadius: 10.0,
offset: new Offset(0.0, .0),
),
],
),
child: _handleTap(),
);

You need to Wrap your - AnimatedContainer with GestureDetector & use onTap: to call setSate().
Code:
class SectionTaps extends StatefulWidget {
SectionTaps(this.isActive);
bool isActive = false;
_SectionTapsState createState() => _SectionTapsState();
}
class _SectionTapsState extends State<SectionTaps> {
bool _isActive = false;
double _width = 255.0;
void _handleTap() {
setState(() {
print ('Set State Called.');
_isActive = widget.isActive;
_isActive == true ? _width = 55.0 : _width = 255.0;
//change container width
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: GestureDetector(
onTap: () {
_handleTap();
},
child: new AnimatedContainer(
duration: Duration(seconds: 1),
height: 88.0,
width: 255.0,
decoration: new BoxDecoration(
color: new Color(0xFF376480),
shape: BoxShape.rectangle,
borderRadius: new BorderRadius.only(
topRight: Radius.circular(80.0),
bottomRight: Radius.circular(80.0),
),
boxShadow: <BoxShadow>[
new BoxShadow(
color: Colors.black12,
blurRadius: 10.0,
offset: new Offset(0.0, .0),
),
],
),
),
),
),
);
}
}

Related

Paint on Image after Image loaded from the network

I'm loading the images from api call and then showing the image and some data in listview in flutter and drawing bounding box on image, but since my image is getting loaded from url my UI gets disturbed,How I ensure that the Canvas will paint after the image is loaded from the network url in flutter layout.Is there any callbacks which I can use.
class ListPage extends StatefulWidget {
ListPage({Key key, this.title}) : super(key: key);
final String title;
#override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
Future<List<ProcessedInference>> processInference;
#override
void initState() {
processInference = localInference();
super.initState();
}
#override
Widget build(BuildContext context) {
final toAppBar = AppBar(
elevation: 0.1,
backgroundColor: Color.fromRGBO(58, 66, 86, 1.0),
title: Text(widget.title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.list),
onPressed: () {},
)
],
);
Card makeCard(ProcessedInference pro) => Card(
elevation: 2.0,
margin: new EdgeInsets.symmetric(horizontal: 5.0, vertical: 5.0),
child: Container(
child: Column(
children: <Widget>[
CustomPaint(
size: Size(640.0, 480.0),
foregroundPainter: RectPainter(pro.boundingBox),
child: Image.network(pro.frameUrl),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(
height: 8.0,
),
Text(
"Spotted: ${pro.peopleCount}",
style: TextStyle(fontSize: 16.0),
textAlign: TextAlign.start,
),
SizedBox(
height: 8.0,
),
Text(
"Spotted on : ${pro.timeStamp}",
style: TextStyle(fontSize: 16.0),
textAlign: TextAlign.start,
),
SizedBox(
height: 8.0,
),
],
),
],
),
),
);
final makeBody = Container(
child: FutureBuilder<List<ProcessedInference>>(
future: processInference,
builder: (context, snapshot) {
if (snapshot.hasData) {
print("Has data");
if (snapshot.data == null || snapshot.data.length <= 0) {
return Center(
child: Text("No results", textAlign: TextAlign.center));
} else {
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return makeCard(snapshot.data[index]);
});
}
} else if (snapshot.hasError) {
print("has error ");
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
),
);
return Scaffold(
appBar: toAppBar,
body: makeBody,
);
}
}
class RectPainter extends CustomPainter {
final List<List<double>> boundingBox;
RectPainter(this.boundingBox);
#override
void paint(Canvas canvas, Size size) {
final paint = Paint();
paint.color = Colors.deepOrange;
paint.style = PaintingStyle.stroke;
paint.strokeWidth = 2.0;
final boxPaint = Paint();
boxPaint.color = Colors.amberAccent;
boxPaint.style = PaintingStyle.fill;
boxPaint.strokeWidth = 2.0;
for (var i = 0; i < boundingBox.length; i++) {
var confidence = boundingBox[i][0];
var left = boundingBox[i][1] * size.width;
var top = boundingBox[i][2] * size.height;
var right = boundingBox[i][3] * size.width;
var bottom = boundingBox[i][4] * size.height;
var rect = Rect.fromLTRB(left, top - 15, right, bottom);
canvas.drawRect(rect, paint);
TextSpan span = new TextSpan(
style: new TextStyle(color: Colors.red[600], fontSize: 10.0),
text: confidence.toStringAsFixed(2));
TextPainter tp = new TextPainter(
text: span,
textAlign: TextAlign.center,
textDirection: TextDirection.ltr);
tp.layout();
canvas.drawRect(
Rect.fromLTRB(
rect.left, rect.top, rect.left + tp.width, rect.top + tp.height),
boxPaint);
tp.paint(canvas, new Offset(rect.left, rect.top));
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}

Flutter - Expand bottomNavigationBar by swiping or pressing the floatingActionButton

I have a main widget called DashboardWidget. Inside it, I have a Scaffold with BottomNavigationBar and a FloatingActionButton:
Now, I want to make a widget that would be dragged from the bottom by:
Swiping up with the finger.
Pressing on FloatingActionButton.
In other words, I want to expand the BottomNavigationBar.
Here's a design concept in case I was unclear.
The problem is, I'm not sure where to start to implement that. I've thought about removing the BottomNavigationBar and create a custom widget that can be expanded, but I'm not sure if it's possible either.
Output:
I used a different approach and did it without AnimationController, GlobalKey etc, the logic code is very short (_handleClick).
I only used 4 variables, simple and short!
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static double _minHeight = 80, _maxHeight = 600;
Offset _offset = Offset(0, _minHeight);
bool _isOpen = false;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFF6F6F6),
appBar: AppBar(backgroundColor: Color(0xFFF6F6F6), elevation: 0),
body: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
Align(
alignment: Alignment.topLeft,
child: FlatButton(
onPressed: _handleClick,
splashColor: Colors.transparent,
textColor: Colors.grey,
child: Text(_isOpen ? "Back" : ""),
),
),
Align(child: FlutterLogo(size: 300)),
GestureDetector(
onPanUpdate: (details) {
_offset = Offset(0, _offset.dy - details.delta.dy);
if (_offset.dy < _HomePageState._minHeight) {
_offset = Offset(0, _HomePageState._minHeight);
_isOpen = false;
} else if (_offset.dy > _HomePageState._maxHeight) {
_offset = Offset(0, _HomePageState._maxHeight);
_isOpen = true;
}
setState(() {});
},
child: AnimatedContainer(
duration: Duration.zero,
curve: Curves.easeOut,
height: _offset.dy,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.5), spreadRadius: 5, blurRadius: 10)]),
child: Text("This is my Bottom sheet"),
),
),
Positioned(
bottom: 2 * _HomePageState._minHeight - _offset.dy - 28, // 56 is the height of FAB so we use here half of it.
child: FloatingActionButton(
child: Icon(_isOpen ? Icons.keyboard_arrow_down : Icons.add),
onPressed: _handleClick,
),
),
],
),
);
}
// first it opens the sheet and when called again it closes.
void _handleClick() {
_isOpen = !_isOpen;
Timer.periodic(Duration(milliseconds: 5), (timer) {
if (_isOpen) {
double value = _offset.dy + 10; // we increment the height of the Container by 10 every 5ms
_offset = Offset(0, value);
if (_offset.dy > _maxHeight) {
_offset = Offset(0, _maxHeight); // makes sure it does't go above maxHeight
timer.cancel();
}
} else {
double value = _offset.dy - 10; // we decrement the height by 10 here
_offset = Offset(0, value);
if (_offset.dy < _minHeight) {
_offset = Offset(0, _minHeight); // makes sure it doesn't go beyond minHeight
timer.cancel();
}
}
setState(() {});
});
}
}
You can use the BottomSheet class.
Here is a Medium-tutorial for using that, here is a youtube-tutorial using it and here is the documentation for the class.
The only difference from the tutorials is that you have to add an extra call method for showBottomSheet from your FloatingActionButton when it is touched.
Bonus: here is the Material Design page on how to use it.
You can check this code, it is a complete example of how to start implementing this kind of UI, take it with a grain of salt.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:rxdart/rxdart.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Orination Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
bool _isOpen;
double _dragStart;
double _hieght;
double _maxHight;
double _currentPosition;
GlobalKey _cardKey;
AnimationController _controller;
Animation<double> _cardAnimation;
#override
void initState() {
_isOpen = false;
_hieght = 50.0;
_cardKey = GlobalKey();
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 700));
_cardAnimation = Tween(begin: _hieght, end: _maxHight).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_controller.addListener(() {
setState(() {
_hieght = _cardAnimation.value;
});
});
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
backgroundColor: Colors.transparent,
titleSpacing: 0.0,
title: _isOpen
? MaterialButton(
child: Text(
"Back",
style: TextStyle(color: Colors.red),
),
onPressed: () {
_isOpen = false;
_cardAnimation = Tween(begin: _hieght, end: 50.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_controller.forward(from: 0.0);
},
)
: Text(""),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.keyboard_arrow_up),
onPressed: () {
final RenderBox renderBoxCard = _cardKey.currentContext
.findRenderObject();
_maxHight = renderBoxCard.size.height;
_cardAnimation = Tween(begin: _hieght, end: _maxHight).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_controller.forward(from: 0.0);
_isOpen = true;
}),
body: Stack(
key: _cardKey,
alignment: Alignment.bottomCenter,
children: <Widget>[
Container(
width: double.infinity,
height: double.infinity,
color: Colors.black12,
),
GestureDetector(
onPanStart: _onPanStart,
onPanUpdate: _onPanUpdate,
onPanEnd: _onPanEnd,
child:Material(
borderRadius: BorderRadius.only(
topRight: Radius.circular(16.0),
topLeft: Radius.circular(16.0),
),
elevation: 60.0,
color: Colors.white,
// shadowColor: Colors.,
child: Container(
height: _hieght,
child: Center(
child: Text("Hello, You can drag up"),
),
),
),
),
],
),
);
}
void _onPanStart(DragStartDetails details) {
_dragStart = details.globalPosition.dy;
_currentPosition = _hieght;
}
void _onPanUpdate(DragUpdateDetails details) {
final RenderBox renderBoxCard = _cardKey.currentContext.findRenderObject();
_maxHight = renderBoxCard.size.height;
final hieght = _currentPosition - details.globalPosition.dy + _dragStart;
print(
"_currentPosition = $_currentPosition _hieght = $_hieght hieght = $hieght");
if (hieght <= _maxHight && hieght >= 50.0) {
setState(() {
_hieght = _currentPosition - details.globalPosition.dy + _dragStart;
});
}
}
void _onPanEnd(DragEndDetails details) {
_currentPosition = _hieght;
if (_hieght <= 60.0) {
setState(() {
_isOpen = false;
});
} else {
setState(() {
_isOpen = true;
});
}
}
}
Edit: I modified the code by using Material Widget instead of A container with shadow for better performance,If you have any issue, please let me know .

Flutter - Animate change on height when child of container renders

I'm trying to recreate something like ExpansionTile but in a Card. When I click the card, its child renders and the card changes its height, so I want to animate that change.
I tried using AnimatedContainer and GlobalKey to know the final size of the card with its child rendered and then set the new height to AnimatedContainer but that didn't work.
In the end I just had to use AnimatedSize. It replicates exactly the animation that I want.
AnimatedSize(
vsync: this,
duration: Duration(milliseconds: 150),
curve: Curves.fastOutSlowIn,
child: Container(
child: Container(
child: !_isExpanded
? null
: FadeTransition(opacity: animationFade, child: widget.child),
),
),
);
You can use the AnimatedContainer for animations
class Animate extends StatefulWidget {
#override
_AnimateState createState() => _AnimateState();
}
class _AnimateState extends State<Animate> {
var height = 200.0;
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
return Scaffold(
body: Center(
child: AnimatedContainer(
color: Colors.amber,
duration: new Duration(milliseconds: 500),
height: height,
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
if (height == 200.0) {
height = 400.0;
} else {
height = 200.0;
}
});
},
child: Icon(Icons.settings),
),
);
}
}
I tweaked the ExpansionTile, this has proper animation. Hope this helps
class _FixedExpansionTileState extends State<FixedExpansionTile> with SingleTickerProviderStateMixin {
AnimationController _controller;
CurvedAnimation _easeOutAnimation;
CurvedAnimation _easeInAnimation;
ColorTween _borderColor;
ColorTween _headerColor;
ColorTween _iconColor;
ColorTween _backgroundColor;
Animation<double> _iconTurns;
bool _isExpanded = false;
#override
void initState() {
super.initState();
_controller = new AnimationController(duration: _kExpand, vsync: this);
_easeOutAnimation = new CurvedAnimation(parent: _controller, curve: Curves.easeOut);
_easeInAnimation = new CurvedAnimation(parent: _controller, curve: Curves.easeIn);
_borderColor = new ColorTween();
_headerColor = new ColorTween();
_iconColor = new ColorTween();
_iconTurns = new Tween<double>(begin: 0.0, end: 0.5).animate(_easeInAnimation);
_backgroundColor = new ColorTween();
_isExpanded = PageStorage.of(context)?.readState(context) ?? widget.initiallyExpanded;
if (_isExpanded)
_controller.value = 1.0;
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
void _handleTap() {
setState(() {
_isExpanded = !_isExpanded;
if (_isExpanded)
_controller.forward();
else
_controller.reverse().then<void>((value) {
setState(() {
// Rebuild without widget.children.
});
});
PageStorage.of(context)?.writeState(context, _isExpanded);
});
if (widget.onExpansionChanged != null)
widget.onExpansionChanged(_isExpanded);
}
Widget _buildChildren(BuildContext context, Widget child) {
final Color borderSideColor = Colors.transparent;
// final Color titleColor = _headerColor.evaluate(_easeInAnimation);
return new Container(
decoration: new BoxDecoration(
color: _backgroundColor.evaluate(_easeOutAnimation) ?? Colors.transparent,
border: new Border(
top: new BorderSide(color: borderSideColor),
bottom: new BorderSide(color: borderSideColor),
)
),
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconTheme.merge(
data: new IconThemeData(color: _iconColor.evaluate(_easeInAnimation)),
child: new ListTile(
onTap: _handleTap,
leading: widget.leading,
title: new DefaultTextStyle(
style: Theme.of(context).textTheme.subhead.copyWith(color: Colors.transparent),
child: widget.title,
),
trailing: widget.trailing ?? new RotationTransition(
turns: _iconTurns,
child: const Icon(Icons.expand_more),
),
),
),
new ClipRect(
child: new Align(
heightFactor: _easeInAnimation.value,
child: child,
),
),
],
),
);
}
#override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
_borderColor.end = theme.dividerColor;
_headerColor
..begin = theme.textTheme.subhead.color
..end = theme.accentColor;
_iconColor
..begin = theme.unselectedWidgetColor
..end = theme.accentColor;
_backgroundColor.end = widget.backgroundColor;
final bool closed = !_isExpanded && _controller.isDismissed;
return new AnimatedBuilder(
animation: _controller.view,
builder: _buildChildren,
child: closed ? null : new Column(children: widget.children),
);
}
}
It works better for me
AnimatedCrossFade(
duration: _controller.duration!,
firstCurve: Curves.easeInOut,
secondCurve: Curves.easeInOut,
firstChild: Container(),
secondChild: widget.content,
crossFadeState: isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst,
)

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.
Here's what I currently have:
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(),
],
);
}
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.
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,
),
),
);
}
}
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,
)

Flutter Stack change depth

I have a Stack with 3 Positioned widgets with a GestureDetector as a child. I'm able to drag them, but I also want to bring the clicked one to the front as soon as you click it.
I've tried lifting up a call to change state in the parent widget, but that does also exchange the afected widgets position.
Here is the full sample code (it's the body in a Scaffold.
Any help will be appreciated
class DragBox extends StatefulWidget {
final Offset startPosition;
final Color color;
final String label;
final Function bringToTop;
DragBox({
this.startPosition,
this.color: const Color.fromRGBO(255, 255, 255, 1.0),
this.label,
this.bringToTop
});
#override
DragBoxState createState() => DragBoxState();
}
class DragBoxState extends State<DragBox> {
Offset position;
Offset _startPos;
#override
void initState() {
position = widget.startPosition;
super.initState();
}
#override
Widget build(BuildContext context) {
return Positioned(
left: position.dx,
top: position.dy,
child: GestureDetector(
onScaleStart: (details) {
//Store start conditions
_startPos = details.focalPoint;
widget.bringToTop(widget);
},
onScaleUpdate: (scaleDetails) {
setState(() {
position += scaleDetails.focalPoint - _startPos;
_startPos = scaleDetails.focalPoint;
});
},
child: _buildPart()
));
}
Widget _buildPart() {
return Container(
width: 100.0,
height: 100.0,
decoration: BoxDecoration(
border: Border.all(color: Color.fromARGB(255, 0, 0, 0), width: 1.0),
color: widget.color),
child: Text(
widget.label,
style: TextStyle(
color: Color.fromRGBO(255, 255, 255, 1.0),
//decoration: TextDecoration.none,
fontSize: 15.0,
),
),
);
}
}
class WorkTable extends StatefulWidget {
#override
WorkTableState createState() => WorkTableState();
}
class WorkTableState extends State<WorkTable> {
List<DragBox> dragParts = [];
#override
void initState() {
dragParts = [];
//dragParts = [];
dragParts.add(DragBox(
startPosition: Offset(0.0, 0.0),
color: Colors.red,
label: "Box1",
bringToTop: this.bringToTop,
));
dragParts.add(DragBox(
startPosition: Offset(50.0, 50.0),
color: Colors.red,
label: "Box2",
bringToTop: this.bringToTop,
));
dragParts.add(DragBox(
startPosition: Offset(100.0, 100.0),
color: Colors.blue,
label: "Box3",
bringToTop: this.bringToTop,
));
super.initState();
}
#override
Widget build(BuildContext context) {
for(DragBox d in dragParts){
print(d.label);
}
return Stack(
children: dragParts,
);
}
void bringToTop(Widget widget) {
setState(() {
dragParts.remove(widget);
dragParts.add(widget);
});
}
}
The strange part happens here
void bringToTop(Widget widget) {
setState(() {
dragParts.remove(widget);
dragParts.add(widget);
});
}
Instead of changing only depth, positions are also exchanged
Adding a GlobalKey (or some other key) to your widgets should fix the problem:
dragParts.add(DragBox(
key: GlobalKey(),
startPosition: Offset(0.0, 0.0),
color: Colors.red,
label: "Box1",
bringToTop: this.bringToTop,
));
dragParts.add(DragBox(
key: GlobalKey(),
startPosition: Offset(50.0, 50.0),
color: Colors.red,
label: "Box2",
bringToTop: this.bringToTop,
));
dragParts.add(DragBox(
key: GlobalKey(),
startPosition: Offset(100.0, 100.0),
color: Colors.blue,
label: "Box3",
bringToTop: this.bringToTop,
));
https://i.stack.imgur.com/TeeQZ.gif
My knowledge column: https://zhuanlan.zhihu.com/p/46982762
Example:
class TestBringToFrontPage extends StatefulWidget {
TestBringToFrontPage({Key key}) : super(key: key);
#override
State<TestBringToFrontPage> createState() {
return TestBringToFrontState();
}
}
class BoxData {
final double width;
final double height;
final Offset position;
final String key;
final Color color;
final TextAlign align;
BoxData(this.key, this.color,this.align, this.width, this.height, this.position);
}
class TestBringToFrontState extends State<TestBringToFrontPage> {
List<Widget> widgetArrays = [];
var boxDataArrays = [
BoxData("Flutter版的bringToFront", Colors.blueGrey,TextAlign.center, 200.0, 30.0, Offset(100.0, 200.0)),
BoxData("AAA", Colors.blue,TextAlign.left, 100.0, 100.0, Offset(100.0, 300.0)),
BoxData("BBB", Colors.orange,TextAlign.right, 100.0, 100.0, Offset(150.0, 250.0)),
];
#override
void initState() {
super.initState();
widgetArrays = [];
for (int i = 0; i < boxDataArrays.length; i++) {
var boxData = boxDataArrays[i];
widgetArrays.add(Positioned(
key: Key(boxData.key),
left: boxData.position.dx,
top: boxData.position.dy,
child: GestureDetector(
child: _buildBox(
boxData.color,
boxData.key,
boxData.align,
boxData.width,
boxData.height
),
onTap: () {
bringToFront(Key(boxData.key));
},
)));
}
}
#override
Widget build(BuildContext context) {
return Stack(
children: widgetArrays,
);
}
void bringToFront(Key key) {
setState(() {
for (var i = 0; i < widgetArrays.length; i++) {
Widget widget = widgetArrays[i];
if (key == widget.key) {
widgetArrays.remove(widget);
widgetArrays.add(widget);
break;
}
}
});
}
Widget _buildBox(
Color color, String label, TextAlign align, double width, double height) {
return Container(
width: width,
height: height,
decoration: BoxDecoration(
border: Border.all(color: Color.fromARGB(255, 0, 0, 0), width: 1.0),
color: color),
child: Text(
label,
textAlign: align,
style: TextStyle(
color: Color.fromRGBO(255, 255, 255, 1.0),
fontWeight: FontWeight.w700,
//decoration: TextDecoration.none,
fontSize: 15.0,
),
),
);
}
}

Resources