Skip to content

Commit aedb550

Browse files
committed
增加趋势动画
1 parent fa8b76f commit aedb550

File tree

1 file changed

+162
-44
lines changed

1 file changed

+162
-44
lines changed

lib/page/trend_page.dart

Lines changed: 162 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import 'dart:async';
2+
import 'dart:math' as math;
23

34
import 'package:flutter/material.dart';
5+
import 'package:flutter/rendering.dart';
46
import 'package:flutter_redux/flutter_redux.dart';
57
import 'package:fluttertoast/fluttertoast.dart';
68
import 'package:gsy_github_app_flutter/bloc/trend_bloc.dart';
@@ -10,6 +12,7 @@ import 'package:gsy_github_app_flutter/common/style/gsy_style.dart';
1012
import 'package:gsy_github_app_flutter/common/utils/common_utils.dart';
1113
import 'package:gsy_github_app_flutter/common/utils/navigator_utils.dart';
1214
import 'package:gsy_github_app_flutter/widget/gsy_card_item.dart';
15+
import 'package:gsy_github_app_flutter/widget/nested/nested_refresh.dart';
1316
import 'package:gsy_github_app_flutter/widget/repos_item.dart';
1417
import '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

196269
class 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

Comments
 (0)