Reputation: 13
I'm a beginner on Flutter development and I have some problems. I will try to explain me.
I have a slider bar (custom widget stateful). I want to integrate this widget on my main view, but I have to change the value of the slider (this value can also change with a tap gesture detector).
I tried several things after research on the net.
For example, with a getter for the VerticalSliderState
and access VerticalSliderState
method to change the value.
This worked only one time after I got a the state is null
error, I think I miss a flutter concept with the setState()
.
Any explanation? Thanks :P
This is my code:
The Custom Widget Stateful:
import 'package:flutter/material.dart';
class VerticalSlider extends StatefulWidget {
....
final double value;
.....
final void Function(double) onValueChanged;
VerticalSlider({
Key key,
@required this.height,
@required this.width,
this.onValueChanged,
this.value,
....
}) : super(key: key);
VerticalSliderState state;
@override
VerticalSliderState createState(){
state = new VerticalSliderState();
return state ;
}
}
class VerticalSliderState extends State<Vertical Slider>{
.....
double _value;
double _currentHeight;
Widget _movingDecoratedBox;
Widget _fixedDecoratedBox;
void setValue(double value){
_setValue(value, false, widget.height);
}
initState() {
super.initState();
_value = widget.value ?? 5.0;
_currentHeight = _convertValueToHeight();
_movingDecoratedBox = widget.movingBox ?? DecoratedBox(
decoration: BoxDecoration(color: Colors.red)
);
_fixedDecoratedBox = widget.fixedBox ?? DecoratedBox(
decoration: BoxDecoration(color: Colors.grey),
);
}
..........
void _onTapUp(TapUpDetails tapDetails) {
RenderBox renderBox = context.findRenderObject();
var newHeight = widget.height - renderBox.globalToLocal(tapDetails.globalPosition).dy;
var newValue = _convertHeightToValue(newHeight);
setState(() {
_currentHeight = (widget.height/10.5) * (newValue);
_setValue(newValue, true, widget.height);
});
}
void _setValue(double newValue, bool userRequest, double height) {
_value = newValue;
if(userRequest){
widget.onValueChanged(_value);
}
else{
setState(() {
_currentHeight = (height/10.5) * (newValue);
});
}
}
Widget _buildMovingBox() {
return Align(
alignment: Alignment.bottomCenter,
child: SizedBox(
width: widget.width,
height: _currentHeight,
child: _movingDecoratedBox,
),
);
}
..........
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapUp: _onTapUp,
child: Stack(
alignment: AlignmentDirectional.bottomCenter,
children: <Widget>[
_buildFixedBox(),
_buildMovingBox(),
],
),
);
}
My main view:
seekBar1 = new VerticalSlider(
height: mediaQueryData.size.height / 2.7,
width: 40.0,
max: 11.0,
min: 0.0,
value: 5.5,
movingBox: new Container(color: Colors.teal),
fixedBox: new Container(color: Colors.grey[200]),
onValueChanged: onValueChanged1,
);
void onValueChanged1(double newValue) {
seekBar2.state.setValue(10-newValue);
print(newValue);
}
Upvotes: 0
Views: 3265
Reputation: 20558
One solution would be the one in the full example below. The slider widget updates the value in the main page and it is used to set the value in the other slider.
I have made up some of the code since your snippet was not complete.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
double _value = 9;
void onValueChanged(double newValue) {
setState(() {
_value = newValue;
});
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Test"),
),
body: new Center(
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
VerticalSlider(
height: MediaQuery.of(context).size.height / 2.7,
width: 40.0,
max: 11.0,
min: 0.0,
value: _value,
movingBox: new Container(color: Colors.teal),
fixedBox: new Container(color: Colors.grey[200]),
onValueChanged: onValueChanged,
),
SizedBox(
width: 50.0,
),
VerticalSlider(
height: MediaQuery.of(context).size.height / 2.7,
width: 40.0,
max: 11.0,
min: 0.0,
value: _value,
movingBox: new Container(color: Colors.teal),
fixedBox: new Container(color: Colors.grey[200]),
onValueChanged: onValueChanged,
),
],
),
),
);
}
}
class VerticalSlider extends StatefulWidget {
final double height;
final double width;
final double max;
final double min;
final double value;
final Widget movingBox;
final Widget fixedBox;
final void Function(double) onValueChanged;
VerticalSlider({
Key key,
@required this.height,
@required this.width,
this.onValueChanged,
this.value,
this.max,
this.min,
this.movingBox,
this.fixedBox,
}) : super(key: key);
@override
VerticalSliderState createState() {
return VerticalSliderState();
}
}
class VerticalSliderState extends State<VerticalSlider> {
double _value;
double _currentHeight;
@override
void didUpdateWidget(VerticalSlider oldWidget) {
super.didUpdateWidget(oldWidget);
_init();
}
initState() {
super.initState();
_init();
}
void _init() {
_value = widget.value ?? widget.max / 2;
_currentHeight = _convertValueToHeight();
}
double _convertValueToHeight() {
return _value * widget.height / widget.max;
}
double _convertHeightToValue(double height) {
return height * widget.max / widget.height;
}
void _onTapUp(TapUpDetails tapDetails) {
RenderBox renderBox = context.findRenderObject();
var newHeight =
widget.height - renderBox.globalToLocal(tapDetails.globalPosition).dy;
var newValue = _convertHeightToValue(newHeight);
widget.onValueChanged(newValue);
setState(() {
_currentHeight = newHeight;
});
}
Widget _buildMovingBox() {
return Align(
alignment: Alignment.bottomCenter,
child: SizedBox(
width: widget.width,
height: _currentHeight,
child: widget.movingBox ??
DecoratedBox(decoration: BoxDecoration(color: Colors.red)),
),
);
}
Widget _buildFixedBox() {
return Align(
alignment: Alignment.bottomCenter,
child: SizedBox(
width: widget.width / 2,
height: double.infinity,
child: widget.fixedBox ??
DecoratedBox(
decoration: BoxDecoration(color: Colors.grey),
),
),
);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapUp: _onTapUp,
child: SizedBox(
width: widget.width,
height: widget.height,
child: Stack(
alignment: AlignmentDirectional.bottomCenter,
children: <Widget>[
_buildFixedBox(),
_buildMovingBox(),
],
),
),
);
}
}
Upvotes: 3
Reputation: 17756
There are a few things you show keep in mind:
You don't want to have a state
variable in your Stateful Widget
,
neither you'll be wanting to access it by myWidget.state
. Let the
widget itself handle its own state.
You are getting null
because when you do setState
in your
VerticalSliderState
object, it will rebuild your tree, thus,
generating a new updated VerticalSliderState
. That means that your
state reference will now be null.
If you want to access some data within your state, you can take
advantage of final callbacks (like your onValueChanged
) or you
could use Streams.
Also, use methods to return Widgets
instead of variables, that's just more correct.
For example, where you have seekBar2.state.setValue(10-newValue);
you'd want to just create a new seekBar2 = VerticalSlider(updated properties)
with updated value instead of doing it this way.
Upvotes: 0