I have a stateful widget class where I have a SingleChildScrollView which takes Column and a few widgets let's say w1, w2, w3, w4, and w5 all are scrollable what I want to achieve is when the user scrolls up the screen w1, w2, w4, w5 should behave as expected but w3 should stick when it reached to a fix position let say (screen height - 50).
Here is my code I am able to get the position and added a flag too "_isStuck", now I need to stick w3 widget when the flag turns true else it should scroll with the flow when the flag is false.
`import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
final GlobalKey _key = GlobalKey();
ScrollController _controller = ScrollController();
bool _isStuck = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
}
void _afterLayout(_) {
_controller.addListener(
() {
final RenderBox renderBox =
_key.currentContext!.findRenderObject() as RenderBox;
final Offset offset = renderBox.localToGlobal(Offset.zero);
final double startY = offset.dy;
if (startY <= 120) {
setState(() {
_isStuck = true;
});
} else {
setState(() {
_isStuck = false;
});
}
print("Check position: - $startY - $_isStuck");
},
);
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
controller: _controller,
child: Column(
children: [
Container(
height: 400,
color: Colors.red,
child: const Text('w1'),
),
Container(
height: 400,
color: Colors.green,
child: const Text('w2'),
),
RepaintBoundary(
child: Container(
height: 100,
color: Colors.blue.shade400,
key: _key,
child: const Text('w3'),
),
),
Container(
height: 500,
color: Colors.yellow,
child: const Text('w4'),
),
Container(
height: 500,
color: Colors.orange,
child: const Text('w5'),
),
],
),
);
}
}
First, create a Stack
. Add a SingleChildScrollView
as the first item in the Stack
. Next, add a Positioned
widget with w3
as its child as the second item in the Stack
. This Positioned
widget will only be rendered if _isStuck
is true.
Inside the SingleChildScrollView
widget, you will have the w3
widget as well but it will only be visible if _isStuck
is false.
Here is the code.
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
final GlobalKey _key = GlobalKey();
final ScrollController _controller = ScrollController();
bool _isStuck = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
}
void _afterLayout(_) {
_controller.addListener(
() {
final RenderBox renderBox =
_key.currentContext?.findRenderObject() as RenderBox;
final Offset offset = renderBox.localToGlobal(Offset.zero);
final double startY = offset.dy;
setState(() {
_isStuck = startY <= 120;
});
print("Check position: - $startY - $_isStuck");
},
);
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
SingleChildScrollView(
controller: _controller,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
height: 400,
color: Colors.red,
child: const Text('w1'),
),
Container(
height: 400,
color: Colors.green,
child: const Text('w2'),
),
Visibility(
visible: !_isStuck,
maintainAnimation: true,
maintainState: true,
maintainSize: true,
child: _w3(key: _key),
),
Container(
height: 500,
color: Colors.yellow,
child: const Text('w4'),
),
Container(
height: 500,
color: Colors.orange,
child: const Text('w5'),
),
],
),
),
if (_isStuck)
Positioned(
top: 120,
left: 0,
right: 0,
child: _w3(),
),
const Padding(
padding: EdgeInsets.fromLTRB(0, 120, 0, 0),
child: Divider(
color: Colors.purple,
),
),
],
);
}
Widget _w3({GlobalKey<State<StatefulWidget>>? key}) {
return RepaintBoundary(
child: Container(
height: 100,
color: Colors.blue.shade400,
child: const Text('w3'),
key: key,
),
);
}
}
EDIT: I added the key only to the first w3
because the logic is based on the position of that widget. Also instead of not rendering the w3
at all, inside the SingleChildScrollView
we are using Visibility
widget to avoid the removal of the widget from the tree which causes the _key.currentContext
to be null.
Finally I changed from
_isStuck = startY <= 120;
to
_isStuck = startY <= -120;
to make sure it shows the sticky positioned w3
when the other one offscreen.
EDIT 2: Based on the new information