How to create sticky shop button animation in Flutter?
Output:
void main() => runApp(MaterialApp(home: Scaffold(body: HomePage(), appBar: AppBar())));
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
ScrollController _controller = ScrollController();
double _boxHeight = 200, _screenHeight;
int _itemIndex = 5;
bool _itemVisibility = true;
@override
void initState() {
super.initState();
double offsetEnd;
WidgetsBinding.instance.addPostFrameCallback((_) {
RenderBox box = context.findRenderObject();
_screenHeight = box.globalToLocal(Offset(0, MediaQuery.of(context).size.height)).dy;
offsetEnd = ((_itemIndex + 1) - (_screenHeight / _boxHeight)) * _boxHeight;
});
_controller.addListener(() {
if (_controller.position.pixels >= offsetEnd) {
if (_itemVisibility) setState(() => _itemVisibility = false);
} else {
if (!_itemVisibility) setState(() => _itemVisibility = true);
}
});
}
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
ListView.builder(
controller: _controller,
itemCount: 8,
itemBuilder: (context, index) {
return _buildBox(
index: index,
color: index == _itemIndex ? Colors.cyan : Colors.blue[((index + 1) * 100) % 900],
);
},
),
Positioned(
bottom: 0,
right: 0,
left: 0,
child: Visibility(
visible: _itemVisibility,
child: _buildBox(index: _itemIndex, color: Colors.cyan),
),
),
],
);
}
Widget _buildBox({int index, Color color}) {
return Container(
height: _boxHeight,
color: color,
alignment: Alignment.center,
child: Text(
"${index}",
style: TextStyle(fontSize: 52, fontWeight: FontWeight.bold),
),
);
}
}
Another answer (variable height boxes)
void main() => runApp(MaterialApp(home: Scaffold(body: HomePage(), appBar: AppBar())));
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
ScrollController _controller = ScrollController();
double _screenHeight, _hRatings = 350, _hSize = 120, _hWidth = 130, _hComfort = 140, _hQuality = 150, _hBuy = 130, _hQuestions = 400;
bool _itemVisibility = true;
@override
void initState() {
super.initState();
double offsetEnd;
WidgetsBinding.instance.addPostFrameCallback((_) {
RenderBox box = context.findRenderObject();
_screenHeight = box.globalToLocal(Offset(0, MediaQuery.of(context).size.height)).dy;
offsetEnd = (_hRatings + _hSize + _hWidth + _hComfort + _hQuality + _hBuy) - _screenHeight;
});
_controller.addListener(() {
if (_controller.position.pixels >= offsetEnd) {
if (_itemVisibility) setState(() => _itemVisibility = false);
} else {
if (!_itemVisibility) setState(() => _itemVisibility = true);
}
});
}
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
ListView(
controller: _controller,
children: <Widget>[
_buildBox(_hRatings, "Ratings box", Colors.blue[200]),
_buildBox(_hSize, "Size box", Colors.blue[300]),
_buildBox(_hWidth, "Width box", Colors.blue[400]),
_buildBox(_hComfort, "Comfort box", Colors.blue[500]),
_buildBox(_hQuality, "Quality box", Colors.blue[600]),
_buildBox(_hBuy, "Buy box", Colors.orange[700]),
_buildBox(_hQuestions, "Questions part", Colors.blue[800]),
],
),
Positioned(
bottom: 0,
right: 0,
left: 0,
child: Visibility(
visible: _itemVisibility,
child: _buildBox(_hBuy, "Buy box", Colors.orange[700]),
),
),
],
);
}
Widget _buildBox(double height, String text, Color color) {
return Container(
height: height,
color: color,
alignment: Alignment.center,
child: Text(
text,
style: TextStyle(
fontSize: 32,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
);
}
}