Mobile Development 6 min read

Fixing Jumping Behavior in Flutter Chat Lists with CustomScrollView Center

This article explains why a reverse‑ordered Flutter chat ListView jumps when inserting messages at both ends, analyzes the underlying Viewport‑Scrollable‑Sliver architecture, and demonstrates how configuring the center property of a CustomScrollView with multiple SliverLists eliminates the unwanted scroll offset changes.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Fixing Jumping Behavior in Flutter Chat Lists with CustomScrollView Center

When implementing a chat interface in Flutter, a simple ListView with reverse: true works for basic scrolling, but inserting items at both the top and bottom causes the list to jump because the underlying SliverList length changes and shifts its position within the Viewport .

The Flutter scrolling system consists of three core components: Viewport (the visible window), Scrollable (handles gestures), and RenderSliver (lays out sliver children such as SliverList ). When new items are inserted at the head, the SliverList grows, moving its start offset and causing the visible content to shift.

One naive fix is to record the current scroll offset, insert the data, calculate the offset change, and then jump back, but this results in a noticeable flash.

The proper solution leverages the center property of CustomScrollView . By placing a SliverPadding (or any sliver) with a key at the desired center position, you can split the list into two SliverList sections—one for older messages above the center and one for newer messages below—so that inserting items on either side does not affect the scroll offset.

Example using ListView.builder (original approach):

ListView.builder(
        controller: scroller,
        reverse: true,
        itemBuilder: (context, index) {
          var item = data[index];
          if (item.type == "Right")
            return renderRightItem(item);
          else
            return renderLeftItem(item);
        },
        itemCount: data.length,
      )

Improved implementation with CustomScrollView :

CustomScrollView(
        controller: scroller,
        reverse: true,
        center: centerKey,
        slivers: [
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (BuildContext context, int index) {
                var item = newData[index];
                if (item.type == "Right")
                  return renderRightItem(item);
                else
                  return renderLeftItem(item);
              },
              childCount: newData.length,
            ),
          ),
          SliverPadding(
            padding: EdgeInsets.zero,
            key: centerKey,
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (BuildContext context, int index) {
                var item = loadMoreData[index];
                if (item.type == "Right")
                  return renderRightItem(item);
                else
                  return renderLeftItem(item);
              },
              childCount: loadMoreData.length,
            ),
          ),
        ],
      )

With this configuration, inserting new messages at the top adds them before the center sliver, extending the negative scroll extent, while inserting at the bottom adds after the center, extending the positive extent; the scroll offset remains stable, eliminating the jump.

Understanding the center key and how Viewport calculates scrollOffset = 0 is essential for building robust, bidirectional chat lists in Flutter.

FluttermobilescrollinglistviewslivercustomscrollviewChat
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.