11import 'dart:async' ;
2+ import 'dart:math' as math;
23
34import 'package:flutter/material.dart' ;
5+ import 'package:flutter/rendering.dart' ;
46import 'package:flutter_redux/flutter_redux.dart' ;
57import 'package:fluttertoast/fluttertoast.dart' ;
68import 'package:gsy_github_app_flutter/bloc/trend_bloc.dart' ;
@@ -10,6 +12,7 @@ import 'package:gsy_github_app_flutter/common/style/gsy_style.dart';
1012import 'package:gsy_github_app_flutter/common/utils/common_utils.dart' ;
1113import 'package:gsy_github_app_flutter/common/utils/navigator_utils.dart' ;
1214import 'package:gsy_github_app_flutter/widget/gsy_card_item.dart' ;
15+ import 'package:gsy_github_app_flutter/widget/nested/nested_refresh.dart' ;
1316import 'package:gsy_github_app_flutter/widget/repos_item.dart' ;
1417import 'package:redux/redux.dart' ;
1518
@@ -24,12 +27,19 @@ class TrendPage extends StatefulWidget {
2427 _TrendPageState createState () => _TrendPageState ();
2528}
2629
27- class _TrendPageState extends State <TrendPage > with AutomaticKeepAliveClientMixin <TrendPage > {
28- static TrendTypeModel selectTime = null ;
30+ class _TrendPageState extends State <TrendPage >
31+ with
32+ AutomaticKeepAliveClientMixin <TrendPage >,
33+ SingleTickerProviderStateMixin {
2934
30- static TrendTypeModel selectType = null ;
35+ TrendTypeModel selectTime = null ;
3136
32- final GlobalKey <RefreshIndicatorState > refreshIndicatorKey = new GlobalKey <RefreshIndicatorState >();
37+ TrendTypeModel selectType = null ;
38+
39+ final GlobalKey <NestedScrollViewRefreshIndicatorState > refreshIndicatorKey =
40+ new GlobalKey <NestedScrollViewRefreshIndicatorState >();
41+
42+ final ScrollController scrollController = new ScrollController ();
3343
3444 final TrendBloc trendBloc = new TrendBloc ();
3545
@@ -44,55 +54,76 @@ class _TrendPageState extends State<TrendPage> with AutomaticKeepAliveClientMixi
4454 _renderItem (e) {
4555 ReposViewModel reposViewModel = ReposViewModel .fromTrendMap (e);
4656 return new ReposItem (reposViewModel, onPressed: () {
47- NavigatorUtils .goReposDetail (context, reposViewModel.ownerName, reposViewModel.repositoryName);
57+ NavigatorUtils .goReposDetail (
58+ context, reposViewModel.ownerName, reposViewModel.repositoryName);
4859 });
4960 }
5061
51- _renderHeader (Store <GSYState > store) {
52- if (selectType == null && selectType == null ) {
62+ _renderHeader (Store <GSYState > store, Radius radius ) {
63+ if (selectTime == null && selectType == null ) {
5364 return Container ();
5465 }
5566 return new GSYCardItem (
5667 color: store.state.themeData.primaryColor,
57- margin: EdgeInsets .all (10 .0 ),
68+ margin: EdgeInsets .all (0 .0 ),
5869 shape: new RoundedRectangleBorder (
59- borderRadius: BorderRadius .all (Radius . circular ( 4.0 ) ),
70+ borderRadius: BorderRadius .all (radius ),
6071 ),
6172 child: new Padding (
62- padding: new EdgeInsets .only (left: 0.0 , top: 5.0 , right: 0.0 , bottom: 5.0 ),
73+ padding:
74+ new EdgeInsets .only (left: 0.0 , top: 5.0 , right: 0.0 , bottom: 5.0 ),
6375 child: new Row (
6476 children: < Widget > [
65- _renderHeaderPopItem (selectTime.name, trendTime (context), (TrendTypeModel result) {
77+ _renderHeaderPopItem (selectTime.name, trendTime (context),
78+ (TrendTypeModel result) {
6679 if (trendBloc.isLoading) {
67- Fluttertoast .showToast (msg: CommonUtils .getLocale (context).loading_text);
80+ Fluttertoast .showToast (
81+ msg: CommonUtils .getLocale (context).loading_text);
6882 return ;
6983 }
70- setState (() {
71- selectTime = result;
84+ scrollController
85+ .animateTo (0 ,
86+ duration: Duration (milliseconds: 200 ),
87+ curve: Curves .bounceInOut)
88+ .then ((_) {
89+ setState (() {
90+ selectTime = result;
91+ });
92+ _showRefreshLoading ();
7293 });
73- _showRefreshLoading ();
7494 }),
75- new Container (height: 10.0 , width: 0.5 , color: Color (GSYColors .white)),
76- _renderHeaderPopItem (selectType.name, trendType (context), (TrendTypeModel result) {
95+ new Container (
96+ height: 10.0 , width: 0.5 , color: Color (GSYColors .white)),
97+ _renderHeaderPopItem (selectType.name, trendType (context),
98+ (TrendTypeModel result) {
7799 if (trendBloc.isLoading) {
78- Fluttertoast .showToast (msg: CommonUtils .getLocale (context).loading_text);
100+ Fluttertoast .showToast (
101+ msg: CommonUtils .getLocale (context).loading_text);
79102 return ;
80103 }
81- setState (() {
82- selectType = result;
104+ scrollController
105+ .animateTo (0 ,
106+ duration: Duration (milliseconds: 200 ),
107+ curve: Curves .bounceInOut)
108+ .then ((_) {
109+ setState (() {
110+ selectType = result;
111+ });
112+ _showRefreshLoading ();
83113 });
84- _showRefreshLoading ();
85114 }),
86115 ],
87116 ),
88117 ),
89118 );
90119 }
91120
92- _renderHeaderPopItem (String data, List <TrendTypeModel > list, PopupMenuItemSelected <TrendTypeModel > onSelected) {
121+ _renderHeaderPopItem (String data, List <TrendTypeModel > list,
122+ PopupMenuItemSelected <TrendTypeModel > onSelected) {
93123 return new Expanded (
94124 child: new PopupMenuButton <TrendTypeModel >(
95- child: new Center (child: new Text (data, style: GSYConstant .middleTextWhite)),
125+ child: new Center (
126+ child: new Text (data, style: GSYConstant .middleTextWhite)),
96127 onSelected: onSelected,
97128 itemBuilder: (BuildContext context) {
98129 return _renderHeaderPopItemChild (list);
@@ -133,9 +164,16 @@ class _TrendPageState extends State<TrendPage> with AutomaticKeepAliveClientMixi
133164
134165 ///空页面
135166 Widget _buildEmpty () {
136- var statusBar = MediaQueryData .fromWindow (WidgetsBinding .instance.window).padding.top;
137- var bottomArea = MediaQueryData .fromWindow (WidgetsBinding .instance.window).padding.bottom;
138- var height = MediaQuery .of (context).size.height - statusBar - bottomArea - kBottomNavigationBarHeight - kToolbarHeight;
167+ var statusBar =
168+ MediaQueryData .fromWindow (WidgetsBinding .instance.window).padding.top;
169+ var bottomArea = MediaQueryData .fromWindow (WidgetsBinding .instance.window)
170+ .padding
171+ .bottom;
172+ var height = MediaQuery .of (context).size.height -
173+ statusBar -
174+ bottomArea -
175+ kBottomNavigationBarHeight -
176+ kToolbarHeight;
139177 return SingleChildScrollView (
140178 child: new Container (
141179 height: height,
@@ -145,10 +183,14 @@ class _TrendPageState extends State<TrendPage> with AutomaticKeepAliveClientMixi
145183 children: < Widget > [
146184 FlatButton (
147185 onPressed: () {},
148- child: new Image (image: new AssetImage (GSYICons .DEFAULT_USER_ICON ), width: 70.0 , height: 70.0 ),
186+ child: new Image (
187+ image: new AssetImage (GSYICons .DEFAULT_USER_ICON ),
188+ width: 70.0 ,
189+ height: 70.0 ),
149190 ),
150191 Container (
151- child: Text (CommonUtils .getLocale (context).app_empty, style: GSYConstant .normalText),
192+ child: Text (CommonUtils .getLocale (context).app_empty,
193+ style: GSYConstant .normalText),
152194 ),
153195 ],
154196 ),
@@ -163,34 +205,65 @@ class _TrendPageState extends State<TrendPage> with AutomaticKeepAliveClientMixi
163205 builder: (context, store) {
164206 return new Scaffold (
165207 backgroundColor: Color (GSYColors .mainBackgroundColor),
166- appBar: new AppBar (
167- flexibleSpace: _renderHeader (store),
168- backgroundColor: Color (GSYColors .mainBackgroundColor),
169- leading: new Container (),
170- elevation: 0.0 ,
171- ),
172208 ///采用目前采用纯 bloc 的 rxdart(stream) + streamBuilder
173209 body: StreamBuilder <List <TrendingRepoModel >>(
174210 stream: trendBloc.stream,
175211 builder: (context, snapShot) {
176- return new RefreshIndicator (
212+ return new NestedScrollViewRefreshIndicator (
177213 key: refreshIndicatorKey,
214+ child: NestedScrollView (
215+ controller: scrollController,
216+ physics: const AlwaysScrollableScrollPhysics (),
217+ headerSliverBuilder: (context, innerBoxIsScrolled) {
218+ return _sliverBuilder (context, innerBoxIsScrolled, store);
219+ },
220+ body: (snapShot.data == null || snapShot.data.length == 0 )
221+ ? _buildEmpty ()
222+ : new ListView .builder (
223+ physics: const AlwaysScrollableScrollPhysics (),
224+ itemBuilder: (context, index) {
225+ return _renderItem (snapShot.data[index]);
226+ },
227+ itemCount: snapShot.data.length,
228+ ),
229+ ),
178230 onRefresh: requestRefresh,
179- child: (snapShot.data == null || snapShot.data.length == 0 )
180- ? _buildEmpty ()
181- : new ListView .builder (
182- physics: const AlwaysScrollableScrollPhysics (),
183- itemBuilder: (context, index) {
184- return _renderItem (snapShot.data[index]);
185- },
186- itemCount: snapShot.data.length,
187- ),
188231 );
189232 }),
190233 );
191234 },
192235 );
193236 }
237+
238+ List <Widget > _sliverBuilder (BuildContext context, bool innerBoxIsScrolled, Store store) {
239+ return < Widget > [
240+ ///动态放大缩小的选择案件
241+ SliverPersistentHeader (
242+ pinned: true ,
243+ delegate: _InfoHeaderDelegate (
244+ maxHeight: 65 ,
245+ minHeight: 65 ,
246+ changeSize: true ,
247+ snapConfig: FloatingHeaderSnapConfiguration (
248+ vsync: this ,
249+ curve: Curves .bounceInOut,
250+ duration: const Duration (milliseconds: 10 ),
251+ ),
252+ builder: (BuildContext context, double shrinkOffset,
253+ bool overlapsContent) {
254+ ///根据数值计算偏差
255+ var lr = 10 - shrinkOffset / 65 * 10 ;
256+ var radius = Radius .circular (4 - shrinkOffset / 65 * 4 );
257+ return SizedBox .expand (
258+ child: Padding (
259+ padding: EdgeInsets .only (top: lr, bottom: 15 , left: lr, right: lr),
260+ child: _renderHeader (store, radius),
261+ ),
262+ );
263+ }),
264+ ),
265+ ];
266+ }
194267}
195268
196269class TrendTypeModel {
@@ -227,3 +300,48 @@ trendType(BuildContext context) {
227300 TrendTypeModel ("C#" , "c%23" ),
228301 ];
229302}
303+
304+ ///动态头部处理
305+ class _InfoHeaderDelegate extends SliverPersistentHeaderDelegate {
306+ _InfoHeaderDelegate (
307+ {@required this .minHeight,
308+ @required this .maxHeight,
309+ @required this .snapConfig,
310+ this .child,
311+ this .builder,
312+ this .changeSize = false });
313+
314+ final double minHeight;
315+ final double maxHeight;
316+ final Widget child;
317+ final Builder builder;
318+ final bool changeSize;
319+ final FloatingHeaderSnapConfiguration snapConfig;
320+ AnimationController animationController;
321+
322+ @override
323+ double get minExtent => minHeight;
324+
325+ @override
326+ double get maxExtent => math.max (maxHeight, minHeight);
327+
328+ @override
329+ Widget build (
330+ BuildContext context, double shrinkOffset, bool overlapsContent) {
331+ if (builder != null ) {
332+ return builder (context, shrinkOffset, overlapsContent);
333+ }
334+ return child;
335+ }
336+
337+ @override
338+ bool shouldRebuild (_InfoHeaderDelegate oldDelegate) {
339+ return true ;
340+ }
341+
342+ @override
343+ FloatingHeaderSnapConfiguration get snapConfiguration => snapConfig;
344+ }
345+
346+ typedef Widget Builder (
347+ BuildContext context, double shrinkOffset, bool overlapsContent);
0 commit comments