Reader Stacks

Scrolling in Flutter: ScrollView and a TikTok-Style PageView

A vertical content feed and a TikTok-style full-screen swipe feed are built from two genuinely different scrolling widgets — SingleChildScrollView versus PageView — not variations of the same one.

Scrolling in Flutter: ScrollView and a TikTok-Style PageView

A standard scrollable content area and a TikTok-style full-screen swipeable feed are built from two genuinely different Flutter widgets — SingleChildScrollView (or ListView) for the former, PageView for the latter.

A basic scroll view for content taller than the screen

SingleChildScrollView(
  child: Column(
    children: [
      // many widgets, together taller than the screen
      Image.asset('assets/banner.jpg'),
      const Text('Long article content...'),
      // ...
    ],
  ),
)

Column alone doesn't scroll — it simply overflows and throws a rendering error if its children exceed the available height. Wrapping it in SingleChildScrollView is what makes that same content scrollable.

ListView, for a long list of similar items

ListView.builder(
  itemCount: products.length,
  itemBuilder: (context, index) {
    return ListTile(title: Text(products[index].name));
  },
)

ListView.builder only builds the items currently visible on screen (plus a small buffer), unlike a SingleChildScrollView wrapping a Column, which builds every child up front regardless of visibility — this matters significantly for a long list, where building thousands of off-screen widgets up front would waste memory and hurt performance.

A horizontal scroll view

SingleChildScrollView(
  scrollDirection: Axis.horizontal,
  child: Row(
    children: categories.map((c) => CategoryChip(category: c)).toList(),
  ),
)

PageView, for a full-screen swipeable feed

PageView.builder(
  scrollDirection: Axis.vertical,
  itemCount: videos.length,
  itemBuilder: (context, index) {
    return VideoPlayerScreen(video: videos[index]);
  },
)

PageView snaps to show exactly one full child at a time, with a swipe gesture moving to the next or previous one — scrollDirection: Axis.vertical is what produces the TikTok-style up/down swipe behavior, as opposed to the horizontal swipe more commonly seen in an onboarding-screen carousel.

Detecting which page is currently visible in a PageView

final PageController _pageController = PageController();
int _currentPage = 0;

PageView.builder(
  controller: _pageController,
  scrollDirection: Axis.vertical,
  onPageChanged: (index) {
    setState(() {
      _currentPage = index;
    });
  },
  itemBuilder: (context, index) => VideoPlayerScreen(video: videos[index]),
)

onPageChanged fires whenever the currently visible page changes — genuinely essential for a video feed, since it's exactly the signal needed to pause the previous video and start playing the newly visible one.

Combining infinite scroll pagination with ListView

final ScrollController _scrollController = ScrollController();

@override
void initState() {
  super.initState();
  _scrollController.addListener(() {
    if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) {
      _loadMoreItems();
    }
  });
}

Following the same underlying concept as the web-based infinite scroll pattern covered elsewhere on this site, listening for the scroll position approaching the maximum extent is how a Flutter list triggers loading its next page of data as the user nears the bottom.