Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NestedScrollView SliverAppBar TabBarView listview combination physics:BouncingScrollPhysics Scroll abnormal #33367

Closed
janiokq opened this issue May 26, 2019 · 18 comments
Labels
f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels.

Comments

@janiokq
Copy link

janiokq commented May 26, 2019

code:

List _tabs = ["111","dasdsa","dsad"];

@OverRide
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.

return  Material(
  child: Scaffold(
    body: DefaultTabController(
      length: _tabs.length, // This is the number of tabs.
      child: NestedScrollView(
        headerSliverBuilder:
            (BuildContext context, bool innerBoxIsScrolled) {
          // These are the slivers that show up in the "outer" scroll view.
          return <Widget>[
            SliverOverlapAbsorber(
              handle:
              NestedScrollView.sliverOverlapAbsorberHandleFor(context),
              child: SliverSafeArea(
                top: false,
                sliver: SliverAppBar(
                  title: const Text('Books'),

// floating: true,
pinned: true,
expandedHeight: 300,
snap: false,
primary: true,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
// These are the widgets to put in each tab in the tab bar.
tabs: _tabs.map((String name) => Tab(text: name)).toList(),
),
),
),
),
];
},
body: TabBarView(
children: _tabs.map((String name) {
return SafeArea(
top: false,
bottom: false,
child: Builder(

                builder: (BuildContext context) {
                  return CustomScrollView(

// physics:new ClampingScrollPhysics(),
key: PageStorageKey(name),
slivers: [
SliverOverlapInjector(
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
sliver: SliverFixedExtentList(
itemExtent: 60.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
// This builder is called for each child.
// In this example, we just number each list item.
return Container(
color: Color((math.Random().nextDouble() *
0xFFFFFF)
.toInt() <<
0)
.withOpacity(1.0));
},
// The childCount of the SliverChildBuilderDelegate
// specifies how many children this inner list
// has. In this example, each tab has a list of
// exactly 30 items, but this is arbitrary.
childCount: 30,
),
),
),
],
);
},
),
);
}).toList(),
),
),
),
),
);

}

000

@indigo-dev
Copy link

I am glad not to be the only one experiencing this behavior, so I can definitely confirm this problem. It only appears when running on iOS. All in all this makes using the app quite quirky.

It happens in the combination:

  • nested scroll view
    -- SliverAppBar (outer scroll)
    -- tabView (CustomScrollView inner scroll)
  • iOS (bounce effect as per default)

I'm pretty sure that since NestedScrollView shares its ScrollViewController with both outer and inner ScrollViews, the bounce effect of the inner ScrollView (normally a CustomScrollView representing the current selected TabView) is bringing the ScrollController out of sync.
In result, if you start scrolling down, while the View is still doing the bounce animation, the SliverAppBar does not now where to go. Reason might be that SliverAppBar is in a normal state (not bouncing), while the inner CustomScrollView is still moving due to the bounce animation. So the ScrollController seams to be out of sync in this situation.
It appears both when scrolling to the top as well as scrolling to the bottom.

flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel beta, v1.6.3, on Microsoft Windows [Version 10.0.18912.1001], locale de-DE)

[√] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[!] Android Studio (version 3.3)
X Flutter plugin not installed; this adds Flutter specific functionality.
X Dart plugin not installed; this adds Dart specific functionality.
[√] Connected device (1 available)

! Doctor found issues in 1 category.

@Piinks Piinks added f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels. labels Jun 24, 2019
@meetqy
Copy link

meetqy commented Aug 23, 2019

ListView中增加physics属性进行判断,默认状态ClampingScrollPhysics(),当满足条件的时候再把状态改为BouncingScrollPhysics()。

eg:

Container(
  color: hex('#ff0000'),
  width: screenWidth(context) - 90,
  padding: EdgeInsets.symmetric(horizontal: 14),
  child: ListView(
    physics: _nestedScrollOffet >= 130 ? BouncingScrollPhysics() : ClampingScrollPhysics(),
    children: createGoodsList(goodsList),
  ),
)

@firatcetiner
Copy link

Is this solution working as @meetqy suggest? I haven't tested it yet.

@telinx
Copy link

telinx commented Nov 29, 2019

I found a solution that might work:

  1. NestedScrollView add ScrollController1
  2. CustomScrollView add ScrollController2
  3. Bind Listen to ScrollController2
    eg:
// add _scrollController1 _scrollController2
NestedScrollView(
  physics: NeverScrollableScrollPhysics(),
  controller: this._scrollController1,
  headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
    return <Widget>[
       SliverPersistentHeader(
         floating: false,
         pinned: false,
         delegate: SliverAppBarDelegate(
           minHeight: AppConfig.trendHeaderImageHeight,
           maxHeight: AppConfig.trendHeaderImageHeight,
           child: trendingHeader,
         )
       )
    ];
  },
  body: CustomScrollView(
    controller: this._scrollController2
	...
  ),
);


// initState bind listen
WidgetsBinding.instance.addPostFrameCallback((_) {
  this._scrollController2.addListener(() {
    double offset = this. _scrollController2.offset;
    if(offset <= AppConfig.trendHeaderImageHeight){
       _scrollController1.jumpTo(offset);
    }else if(offset > AppConfig.trendHeaderImageHeight && widget.scrollController.offset < AppConfig.trendHeaderImageHeight){
       _scrollController1.jumpTo(AppConfig.trendHeaderImageHeight);
    }
    // ScrollDirection direction = this.listViewController.position.userScrollDirection;
  });
});


@rootinn123
Copy link

I found a solution that might work:

  1. NestedScrollView add ScrollController1
  2. CustomScrollView add ScrollController2
  3. Bind Listen to ScrollController2
    eg:
// add _scrollController1 _scrollController2
NestedScrollView(
  physics: NeverScrollableScrollPhysics(),
  controller: this._scrollController1,
  headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
    return <Widget>[
       SliverPersistentHeader(
         floating: false,
         pinned: false,
         delegate: SliverAppBarDelegate(
           minHeight: AppConfig.trendHeaderImageHeight,
           maxHeight: AppConfig.trendHeaderImageHeight,
           child: trendingHeader,
         )
       )
    ];
  },
  body: CustomScrollView(
    controller: this._scrollController2
	...
  ),
);


// initState bind listen
WidgetsBinding.instance.addPostFrameCallback((_) {
  this._scrollController2.addListener(() {
    double offset = this. _scrollController2.offset;
    if(offset <= AppConfig.trendHeaderImageHeight){
       _scrollController1.jumpTo(offset);
    }else if(offset > AppConfig.trendHeaderImageHeight && widget.scrollController.offset < AppConfig.trendHeaderImageHeight){
       _scrollController1.jumpTo(AppConfig.trendHeaderImageHeight);
    }
    // ScrollDirection direction = this.listViewController.position.userScrollDirection;
  });
});

modify

WidgetsBinding.instance.addPostFrameCallback((_) {
  this.listViewController.addListener(() {
	double offset = this._scrollController2.offset;
	if(offset <= AppConfig.trendHeaderImageHeight && widget._scrollController1.offset <= AppConfig.trendHeaderImageHeight ){
	  _scrollController1.jumpTo(offset);
	}else if(offset > AppConfig.trendHeaderImageHeight && _scrollController1.offset < AppConfig.trendHeaderImageHeight){
	  _scrollController1.jumpTo(AppConfig.trendHeaderImageHeight);
	}
  });
});

@WiRight
Copy link

WiRight commented Dec 9, 2019

Maybe, I can help you!

Widget build(BuildContext context) {
  return Scaffold(
    body: NestendScrollView(
      headerSliverBuilder: (BuildContext context, bool value) {
        return <Widget>[
           SliverAppBar(),
        ];
      },
        body: SafeArea(
          child: Builder(
            builder: (context) {
              final _scr = PrimaryScrollController.of(context);
              _scr.addListener(() {
                if (_scr.position.pixels == _scr.position.maxScrollExtent) {
                  print('At DOWNW!!!');
                }
              });

              return CustomScrollView(
                controller: _scr,
                slivers: <Widget>[
                  SliverOverlapAbsorber(
                    handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
                      context,
                    ),
                  ),
                  SliverList(
                    delegate: SliverChildListDelegate(
                      List.generate(100, (int index) {
                        return Text('ITEM -> $index');
                      }),
                    ),
                  )
                ],
              );
            },
          ),
        ),
    ),
  );
}

@maddada
Copy link

maddada commented Dec 22, 2019

Same issue here, still not fixed!

https://youtu.be/408Xgkic9Ts

@ErikReider
Copy link

Same issue here...

@Hixie Hixie added this to the Goals milestone Jan 10, 2020
@Hixie
Copy link
Contributor

Hixie commented Jan 10, 2020

It would be very helpful to have a complete demo app that shows this problem, it's not 100% clear how the code above is supposed to be run to show the issue.

@indigo-dev
Copy link

// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@OverRide
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);

final String title;

@OverRide
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State
with SingleTickerProviderStateMixin {
final List tabs = [
Tab(text: "One"),
Tab(text: "Two"),
Tab(text: "Three")
];

final List tabViews = [
MyTabContainer(),
MyTabContainer(),
MyTabContainer()
];

@OverRide
build(BuildContext context) {
return DefaultTabController(
length: tabs.length,
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (context, isScrolled) {
return [
SliverAppBar(
pinned: true,
floating: true,
expandedHeight: 200,
title: Text("Tab Boucing Problem"),
flexibleSpace: FlexibleSpaceBar(),
bottom: TabBar(tabs: tabs),
),
];
},
body: TabBarView(
children: tabViews,
),
),
),
);
}
}

class MyTabContainer extends StatelessWidget {
final Widget cardItem = Card(
child: Container(
padding: const EdgeInsets.all(24),
child: Text(
"Bouncing Problem",
style: TextStyle(fontSize: 24),
),
),
);

@OverRide
Widget build(BuildContext context) {
return CustomScrollView(
physics: BouncingScrollPhysics(),
slivers: [
SliverList(
delegate: SliverChildListDelegate([
cardItem,
cardItem,
cardItem,
cardItem,
cardItem,
cardItem,
cardItem,
cardItem,
cardItem,
cardItem,
cardItem,
]),
)
],
);
}
}

@zayedelfasa
Copy link

// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@OverRide
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);

final String title;

@OverRide
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State
with SingleTickerProviderStateMixin {
final List tabs = [
Tab(text: "One"),
Tab(text: "Two"),
Tab(text: "Three")
];

final List tabViews = [
MyTabContainer(),
MyTabContainer(),
MyTabContainer()
];

@OverRide
build(BuildContext context) {
return DefaultTabController(
length: tabs.length,
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (context, isScrolled) {
return [
SliverAppBar(
pinned: true,
floating: true,
expandedHeight: 200,
title: Text("Tab Boucing Problem"),
flexibleSpace: FlexibleSpaceBar(),
bottom: TabBar(tabs: tabs),
),
];
},
body: TabBarView(
children: tabViews,
),
),
),
);
}
}

class MyTabContainer extends StatelessWidget {
final Widget cardItem = Card(
child: Container(
padding: const EdgeInsets.all(24),
child: Text(
"Bouncing Problem",
style: TextStyle(fontSize: 24),
),
),
);

@OverRide
Widget build(BuildContext context) {
return CustomScrollView(
physics: BouncingScrollPhysics(),
slivers: [
SliverList(
delegate: SliverChildListDelegate([
cardItem,
cardItem,
cardItem,
cardItem,
cardItem,
cardItem,
cardItem,
cardItem,
cardItem,
cardItem,
cardItem,
]),
)
],
);
}
}

Cool!

@ezamagni
Copy link

Same problem here. This is really, REALLY problematic for us since we are going to showcase a flutter application to our CTO in order to consider future flutter adoption in our company and we have a nested scrollview right in our home page.

@tedhenry100
Copy link

We are experiencing this also.

Similar to @ezamagni, we cannot show our Flutter demo app to our CEO and CTO because of this issue. We are worried that they will see the problem and judge that we've made a poor choice in proposing we use Flutter as a fundamental technology going forward.

A simplified version of @zayedelfasa app below. On iOS with a very quick swipe upwards, the scrolling starts and then unexpected snaps back to the position before swipe began.

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        body: NestedScrollView(
          headerSliverBuilder: (BuildContext context, bool isScrolled) {
            return [
              SliverAppBar(
                pinned: true,
                floating: true,
                expandedHeight: 200,
                title: Text("Tab Bouncing Problem"),
                flexibleSpace: FlexibleSpaceBar(),
                bottom: TabBar(
                  tabs: [
                    Tab(text: "One"),
                    Tab(text: "Two"),
                    Tab(text: "Three"),
                  ],
                ),
              ),
            ];
          },
          body: TabBarView(
            children: [
              MyTabContainer(),
              MyTabContainer(),
              MyTabContainer(),
            ],
          ),
        ),
      ),
    );
  }
}

class MyTabContainer extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (BuildContext context, int index) {
        return ListTile(
          title: Text("Item: $index"),
        );
      },
    );
  }
}

@tedhenry100
Copy link

Is this issue actually a duplicate of #29264 ?

@indigo-dev
Copy link

Yes, it's the exact same problem

@tedhenry100
Copy link

@Hixie should this issue be closed as a duplicate in favor of the earlier issue?

@VladyslavBondarenko
Copy link

Please follow up on #29264
Closing current as duplicate,
If you disagree please write in the commentsand I will reopen it.
Thank you

@github-actions
Copy link

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 13, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

No branches or pull requests