Tuesday, September 26, 2023

Flutter scroll component drop down refresh

A widget provided to the flutter scroll component drop-down refresh and pull up load.


  • pull up load and pull down refresh
  • It’s almost fit for all Scroll witgets,like GridView,ListView…
  • provide global setting of default indicator and property
  • provide some most common indicators
  • Support Android and iOS default ScrollPhysics,the overScroll distance can be controlled,custom spring animate,damping,speed.
  • horizontal and vertical refresh,support reverse ScrollView also(four direction)
  • provide more refreshStyle: Behind,Follow,UnFollow,Front,provide more loadmore style
  • Support twoLevel refresh,implments just like TaoBao twoLevel,Wechat TwoLevel
  • enable link indicator which placing other place,just like Wechat FriendCircle refresh effect


add this line to pubspec.yaml


    pull_to_refresh: ^2.0.0

import package

    import 'package:pull_to_refresh/pull_to_refresh.dart';

simple example,It must be noted here that ListView must be the child of SmartRefresher and cannot be separated from it. For detailed reasons, see here

  List<String> items = ["1", "2", "3", "4", "5", "6", "7", "8"];
  RefreshController _refreshController =
      RefreshController(initialRefresh: false);

  void _onRefresh() async{
    // monitor network fetch
    await Future.delayed(Duration(milliseconds: 1000));
    // if failed,use refreshFailed()

  void _onLoading() async{
    // monitor network fetch
    await Future.delayed(Duration(milliseconds: 1000));
    // if failed,use loadFailed(),if no data return,use LoadNodata()
    setState(() {


  Widget build(BuildContext context) {
    return Scaffold(
      body: SmartRefresher(
        enablePullDown: true,
        enablePullUp: true,
        header: WaterDropHeader(),
        footer: CustomFooter(
          builder: (BuildContext context,LoadStatus mode){
            Widget body ;
              body =  Text("pull up load");
            else if(mode==LoadStatus.loading){
              body =  CupertinoActivityIndicator();
            else if(mode == LoadStatus.failed){
              body = Text("Load Failed!Click retry!");
            else if(mode == LoadStatus.canLoading){
                body = Text("release to load more");
              body = Text("No more Data");
            return Container(
              height: 55.0,
              child: Center(child:body),
        controller: _refreshController,
        onRefresh: _onRefresh,
        onLoading: _onLoading,
        child: ListView.builder(
          itemBuilder: (c, i) => Card(child: Center(child: Text(items[i]))),
          itemExtent: 100.0,
          itemCount: items.length,

  // from 1.5.0, it is not necessary to add this line
 // void dispose() {
    // TODO: implement dispose
  //  _refreshController.dispose();
  //  super.dispose();
 // }

The global configuration RefreshConfiguration, which configures all Smart Refresher representations under the subtree, is generally stored at the root of MaterialApp and is similar in usage to ScrollConfiguration. In addition, if one of your SmartRefresher behaves differently from the rest of the world, you can use RefreshConfiguration.copyAncestor() to copy attributes from your ancestor RefreshConfiguration and replace attributes that are not empty.

    // Smart Refresher under the global configuration subtree, here are a few particularly important attributes
         headerBuilder: () => WaterDropHeader(),        // Configure the default header indicator. If you have the same header indicator for each page, you need to set this
         footerBuilder:  () => ClassicFooter(),        // Configure default bottom indicator
         headerTriggerDistance: 80.0,        // header trigger refresh trigger distance
         springDescription:SpringDescription(stiffness: 170, damping: 16, mass: 1.9),         // custom spring back animate,the props meaning see the flutter api
         maxOverScrollExtent :100, //The maximum dragging range of the head. Set this property if a rush out of the view area occurs
         maxUnderScrollExtent:0, // Maximum dragging range at the bottom
         enableScrollWhenRefreshCompleted: true, //This property is incompatible with PageView and TabBarView. If you need TabBarView to slide left and right, you need to set it to true.
         enableLoadingWhenFailed : true, //In the case of load failure, users can still trigger more loads by gesture pull-up.
         hideFooterWhenNotFull: false, // Disable pull-up to load more functionality when Viewport is less than one screen
         enableBallisticLoad: true, // trigger load more by BallisticScrollActivity
        child: MaterialApp(

1.5.6 add new feather: localization ,you can add following code in MaterialApp or CupertinoApp:

            localizationsDelegates: [
              // this line is important
            supportedLocales: [
              const Locale('en'),
              const Locale('zh'),
                (Locale locale, Iterable<Locale> supportedLocales) {
              //print("change language");
              return locale;

about SmartRefresher’s child explain

Since 1.4.3, the child attribute has changed from ScrollView to Widget, but this does not mean that all widgets are processed the same. SmartRefresher’s internal implementation mechanism is not like NestedScrollView

There are two main types of processing mechanisms here, the first categoryis the component inherited from ScrollView. At present, there are only three types, ListViewGridViewCustomScrollViewThe second category is components that are not inherited from ScrollView, which generally put empty views, NoScrollable views (NoScrollable convert Scrollable), PageView, and you don’t need to estimate height by LayoutBuilder yourself.

For the first type of mechanism, slivers are taken out of the system “illegally”. The second is to put children directly into classes such as `SliverToBox Adapter’. By splicing headers and footers back and forth to form slivers, and then putting slivers inside Smart Refresher into CustomScrollView, you can understand Smart Refresher as CustomScrollView, because the inside is to return to CustomScrollView. So, there’s a big difference between a child node and a ScrollView.

Now, guess you have a requirement: you need to add background, scrollbars or something outside ScrollView. Here’s a demonstration of errors and correct practices

      child: ScrollBar(
          child: ListView(

   // right
      child: SmartRefresher(
          child: ListView(

Demonstrate another wrong doing,put ScrollView in another widget


   class MainView extends StatelessWidget{
       Widget build(){
          return ListView(


The above mistake led to scrollable nesting another scrollable, causing you to not see the header and footer no matter how slippery you are. Similarly, you may need to work with components like NotificationListener, ScrollConfiguration…, remember, don’t store them outside ScrollView (you want to add refresh parts) and Smart Refresher memory.。