https://pub.dev/packages/extended_nested_scroll_view
中文说明:https://github.com/fluttercandies/extended_nested_scroll_view/blob/master/README-ZH.md

扩展NestedScrollView来修复了下面的问题
1.pinned的Header的问题
2.body里面TabView列表滚动同步,互相影响的问题
3.下拉刷新不能工作
4.在NestedScrollView的body中不通过设置ScrollController(设置了会跟内部Controller冲突)来完成下拉刷新,增量加载,滚动到顶部

安装依赖
  1. dependencies:
  2. extended_nested_scroll_view: ^2.0.0

导入
  1. import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';

示例
image.png

  1. import 'dart:async';
  2. import 'package:app1/utils/Base.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter_easyrefresh/easy_refresh.dart';
  5. import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart' as extended;
  6. class NestedScrollViewPage extends StatefulWidget {
  7. @override
  8. _NestedScrollViewPageState createState() => _NestedScrollViewPageState();
  9. }
  10. class _NestedScrollViewPageState extends State<NestedScrollViewPage>
  11. with SingleTickerProviderStateMixin {
  12. TabController _tabController; //tab控制器
  13. int _tabIndex = 0; //Tab索引
  14. int _listCount = 10; //tab-列表数量
  15. int _gridCount = 10; //tab-表格数量
  16. @override
  17. void initState() {
  18. super.initState();
  19. _tabController = TabController(length: 2, vsync: this);
  20. }
  21. @override
  22. void dispose() {
  23. super.dispose();
  24. _tabController.dispose();
  25. }
  26. @override
  27. Widget build(BuildContext context) {
  28. // 在NestedScrollView当中,有2个ScrollController.
  29. // 一个是inner,一个outer。
  30. // outer是负责headerSliverBuilder里面的滚动widgets
  31. // inner是负责body里面的滚动widgets 当outer滚动到底了之后,就会看看inner里面是否有能滚动的东东,开始滚动。
  32. return extended.NestedScrollView(
  33. // 在pinnedHeaderSliverHeightBuilder回调中设置全部pinned的header的高度,
  34. // demo里面高度为 状态栏高度+SliverAppbar的高度
  35. pinnedHeaderSliverHeightBuilder: () {
  36. return MediaQuery.of(context).padding.top + kToolbarHeight;
  37. },
  38. // innerScrollPositionKeyBuilder回调中给出当前tab的key.
  39. innerScrollPositionKeyBuilder: () {
  40. if (_tabController.index == 0) {
  41. return Key('tab0');
  42. } else {
  43. return Key('tab1');
  44. }
  45. },
  46. headerSliverBuilder: (context, innerBoxIsScrolled) {
  47. return [
  48. SliverAppBar(
  49. title: Text("NestedScrollView"),
  50. pinned: true,
  51. expandedHeight: 120.0,
  52. flexibleSpace: SingleChildScrollView(
  53. physics: NeverScrollableScrollPhysics(),
  54. child: Container(),
  55. ),
  56. floating: false,
  57. ),
  58. ];
  59. },
  60. body: Column(
  61. children: [
  62. Container(
  63. height: 50,
  64. color: Colors.green,
  65. child: TabBar(
  66. controller: _tabController,
  67. onTap: (index) {
  68. setState(() {
  69. _tabIndex = index;
  70. });
  71. },
  72. tabs: [
  73. Tab(text: 'List'),
  74. Tab(text: 'Grid'),
  75. ],
  76. ),
  77. ),
  78. Expanded(
  79. child: IndexedStack(
  80. index: _tabIndex,
  81. children: [
  82. // TabbarView里面的列表,使用NestedScrollViewInnerScrollPositionKeyWidget包住,
  83. // 并且设置唯一key, 这个key跟列表是第几个tab有关系。
  84. extended.NestedScrollViewInnerScrollPositionKeyWidget(
  85. Key('Tab0'),
  86. EasyRefresh(
  87. child: ListView.builder(
  88. padding: EdgeInsets.all(0.0),
  89. itemBuilder: (context, index) {
  90. return Container(
  91. color: Color(Base.getRandomColor()),
  92. height: 100,
  93. child: Text('我是列表文本 ${index + 1}', textScaleFactor: 3),
  94. );
  95. },
  96. itemCount: _listCount,
  97. ),
  98. onRefresh: () async {
  99. await Future.delayed(Duration(seconds: 2), () {
  100. if (mounted) {
  101. setState(() {
  102. _listCount = 10;
  103. });
  104. }
  105. });
  106. },
  107. onLoad: () async {
  108. await Future.delayed(Duration(seconds: 2), () {
  109. if (mounted) {
  110. setState(() {
  111. _listCount += 10;
  112. });
  113. }
  114. });
  115. },
  116. ),
  117. ),
  118. extended.NestedScrollViewInnerScrollPositionKeyWidget(
  119. Key('Tab1'),
  120. EasyRefresh(
  121. child: GridView.builder(
  122. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  123. crossAxisCount: 2,
  124. childAspectRatio: 6 / 7,
  125. ),
  126. itemBuilder: (context, index) {
  127. return Container(
  128. color: Color(Base.getRandomColor()),
  129. height: 100,
  130. child: Text('我是表格文本 ${index + 1}', textScaleFactor: 2),
  131. );
  132. },
  133. itemCount: _gridCount,
  134. ),
  135. onRefresh: () async {
  136. await Future.delayed(Duration(seconds: 2), () {
  137. if (mounted) {
  138. setState(() {
  139. _gridCount = 10;
  140. });
  141. }
  142. });
  143. },
  144. onLoad: () async {
  145. await Future.delayed(Duration(seconds: 2), () {
  146. if (mounted) {
  147. setState(() {
  148. _gridCount += 10;
  149. });
  150. }
  151. });
  152. },
  153. ),
  154. ),
  155. ],
  156. ),
  157. ),
  158. ],
  159. ),
  160. );
  161. }
  162. }