codeKiller
codeKiller

Reputation: 5739

SingleChildScrollView not keeping scroll position

I have a SingleChildScrollView widget in my app that contains a Column as a child.

The Column has many children and the last one in the very bottom of the scrolled screen is a StreamBuilder that I use to change a child Image

The issue is that when I tap on the image, the logic of the StreamBuilder works and the image is changed, but then the SingleChildScrollView scrolls a bit up so that the image is not visible and forces the user to scroll down again to be able to see the new loaded image.

Widget _buildScroll() => SingleChildScrollView(
child: Container(
  width: 2080,
  child: Column(
    children: <Widget>[
      _buildTopBar(),
      _buildMainContent(),
      SizedBox(height: 30),
      Container(
        child: Image.asset(
          "assets/images/chart_legend.png",
          width: 300,
        ),
      ),
      SizedBox(height: 30),
      Image.asset("assets/images/road_map.png", width: 600),
      StreamBuilder<int>(
          initialData: 1,
          stream: _compareStream,
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              if (snapshot.data == 1) {
                return Padding(
                  padding: const EdgeInsets.only(right: 10),
                  child: GestureDetector(
                    child: Image.asset("assets/images/compare1.png"),
                    onTap: () => _compareSubject.add(2),
                  ),
                );
              } else if (snapshot.data == 2) {
                return Padding(
                  padding: const EdgeInsets.only(right: 10),
                  child: GestureDetector(
                    child: Image.asset("assets/images/compare2.png"),
                    onTap: () => _compareSubject.add(3),
                  ),
                );
              } else {
                return Padding(
                  padding: const EdgeInsets.only(right: 10),
                  child: GestureDetector(
                    child: Image.asset("assets/images/compare3.png"),
                    onTap: () => _compareSubject.add(1),
                  ),
                );
              }
            }
            return Padding(
              padding: const EdgeInsets.only(right: 10),
              child: GestureDetector(
                child: Image.asset("assets/images/compare1.png"),
                onTap: () {},
              ),
            );
          }),
    ],
  ),
),
);

However, even more weird is that, once I have done tap on all images, they will be showed as expected without scrolling up...meaning that, if there is the second time i tap on a image, the second time the image is replaced the scrolling up in not happening.

Upvotes: 5

Views: 6314

Answers (1)

creativecreatorormaybenot
creativecreatorormaybenot

Reputation: 126574

The problem here is that the size of your children changes and the SingleChildScrollView cannot handle that.

I think there could be two solutions that might work here:

  • If you know the sizes of your images before they are loaded, you should enforce it using a SizedBox. This way, the scroll position will stay the same:
SizedBox(
  width: 300,
  height: 120,
  child: StreamBuilder<int>(...),
)
  • Use ensureVisible that you trigger once the stream builder is updated, which lets you control exactly where the image should be displayed.
    You would need to assign a ScrollController to your SingleChildScrollView (controller parameter). Then, you also need a GlobalKey for your StreamBuilder that you want to show (key parameter).
    If you have saved instances of the two to variables, you will be able to call the following once your image is loaded:
scrollController.position.ensureVisible(
  globalKey.currentContext.findRenderObject(),
  alignment: 0.5, // Aligns the image in the middle.
  duration: const Duration(milliseconds: 120), // So it does not jump.
);

Upvotes: 9

Related Questions