写一个仿掘金app主页的效果,这里面不涉及到网络请求,直接是页面的铺设。
构建页面
想要实现效果首先需要把页面构建出来,然后在研究动画的效果,
上半部分
效果:
把这个抽离成一个组件:home_top.dart文件
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class HomeTopPage extends StatefulWidget {
HomeTopPage({Key key, this.onSearchPress, this.onTagPress}) : super(key: key);
final Function onTagPress;
final Function onSearchPress;
@override
_HomeTopPageState createState() => _HomeTopPageState();
}
class _HomeTopPageState extends State<HomeTopPage> {
String currentString = '推荐';
tagButton({title = '标签'}) {
return FlatButton(
onPressed: () {
setState(() {
currentString = title;
});
},
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(title,
style: currentString == title
? TextStyle(fontSize: 20, color: Colors.blue)
: TextStyle(fontSize: 18, color: Colors.grey)),
Offstage(
offstage: currentString != title,
child: Container(
height: 2,
width: 40,
decoration: BoxDecoration(color: Colors.blue),
),
)
],
));
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: Get.context.mediaQueryPadding.top),
decoration: BoxDecoration(color: Colors.white),
child: Column(
children: [
Container(
width: Get.width,
height: 40,
child: Row(
children: [
Expanded(
child: InkWell(
onTap: () {
widget.onSearchPress();
},
child: Container(
margin: EdgeInsets.only(left: 10),
height: 36,
decoration: BoxDecoration(
color: Color.fromRGBO(235, 242, 244, 1),
borderRadius: BorderRadius.all(Radius.circular(5)),
),
child: Row(
children: [
Padding(padding: EdgeInsets.symmetric(horizontal: 5)),
Icon(
Icons.search,
size: 18,
color: Colors.grey,
),
Padding(padding: EdgeInsets.symmetric(horizontal: 5)),
Text(
'搜索掘金',
style: TextStyle(fontSize: 18, color: Colors.grey),
)
],
),
),
)),
FlatButton(
minWidth: 80,
onPressed: () {
widget.onTagPress();
},
child: Row(
children: [
Icon(
Icons.settings,
size: 18,
color: Colors.grey,
),
Text('标签',
style: TextStyle(fontSize: 18, color: Colors.grey))
],
))
],
),
),
Container(
height: 50,
width: Get.width,
padding: EdgeInsets.only(top: 15),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
'关注',
'推荐',
'热榜',
'后端',
'前端',
'Android',
'iOS',
'人工智能',
'开发工具',
'代码人生',
'阅读'
].map<Widget>((e) => tagButton(title: e)).toList(),
),
),
)
],
),
);
}
}
主页面引用 曝漏两个方法onSearchPress,onTagPress, 故名思意,一个是搜索点击方法,一个是标签点击方法。
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromRGBO(235, 242, 244, 1),
body: Column(
children: [
HomeTopPage(
onSearchPress: () {},
onTagPress: () {},
)
],
));
}
列表部分
新建home_list.dart文件。这里直接使用之前封装的ListView。传送门。
代码如下:
import 'package:flutter/material.dart';
import 'package:flutter_common_app/widget/common_list.dart';
class HomeList extends StatefulWidget {
HomeList({Key key}) : super(key: key);
@override
_HomeListState createState() => _HomeListState();
}
class _HomeListState extends State<HomeList> {
List dataList = ['1', '2', '3'];
itemWidget(isContainUrl) {
return Container(
constraints: BoxConstraints(
minHeight: 80,
),
margin: EdgeInsets.only(top: 1),
padding: EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration(color: Colors.white),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'有新工具了,赶紧来看看来看看有新工具了,赶紧来看看来看看有新工具了,赶紧来看看来看看有新工具了,赶紧来看看来看看',
style: TextStyle(fontSize: 18),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
'点赞89·阅读123·一天清晨',
style: TextStyle(fontSize: 14, color: Colors.grey),
),
)
],
)),
Offstage(
offstage: isContainUrl,
child: Container(
height: 60,
width: 60,
decoration: BoxDecoration(color: Colors.red),
),
)
],
),
);
}
hotWidget() {
return Container(
margin: EdgeInsets.only(top: 10),
child: Column(
children: [
Container(
decoration: BoxDecoration(color: Colors.white),
padding: EdgeInsets.only(left: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('🔥 热门推荐',
style: TextStyle(fontSize: 14, color: Colors.grey)),
FlatButton(
onPressed: () {},
child: Row(
children: [
Text('文章榜',
style: TextStyle(fontSize: 14, color: Colors.grey)),
Icon(
Icons.keyboard_arrow_right,
color: Colors.grey,
size: 18,
)
],
))
],
),
),
itemWidget(true),
itemWidget(true),
itemWidget(false),
],
),
);
}
listItemWidget() {
return Container(
constraints: BoxConstraints(
minHeight: 80,
),
margin: EdgeInsets.only(top: 5),
padding: EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration(color: Colors.white),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(padding: EdgeInsets.symmetric(vertical: 5)),
Row(
children: [
Icon(
Icons.sentiment_very_satisfied,
size: 16,
color: Colors.grey,
),
Text(
' 一天清晨',
style: TextStyle(fontSize: 16),
),
],
),
Padding(padding: EdgeInsets.symmetric(vertical: 5)),
Text(
'有新工具了,赶紧来看看来看看有新工具了,赶紧来看看来看看有新工具了,赶紧来看看来看看有新工具了,赶紧来看看来看看',
style: TextStyle(fontSize: 18),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
Padding(padding: EdgeInsets.symmetric(vertical: 5)),
Text(
'有新工具了,赶紧来看看来看看有新工具了',
style: TextStyle(fontSize: 14, color: Colors.grey),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Padding(padding: EdgeInsets.symmetric(vertical: 5)),
],
),
);
}
@override
Widget build(BuildContext context) {
return CommonListWiget(
networkApi: (currentPage) async {
Future.delayed(Duration(seconds: 2)).then((value) => {
dataList.addAll(['1', '2'])
});
return dataList;
},
itemBuilder: (BuildContext context, int position) {
if (position == 0) {
return hotWidget();
}
return listItemWidget();
},
);
}
}
同样在页面引用一下这个文件
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromRGBO(235, 242, 244, 1),
body: Column(
children: [
HomeTopPage(
onSearchPress: () {},
onTagPress: () {},
),
Expanded(child: HomeList()),
],
));
}
加入动画
首先改造一下home_list.dart文件,检测一下滚动的高度
AnimationController _controller;
ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_scrollController
..addListener(() {
setState(() {
if (_scrollController.offset > 88) {
// 把值传出去
widget.onScroll(255);
} else if (_scrollController.offset <= 0) {
widget.onScroll(0);
} else {
widget.onScroll(_scrollController.offset * 255 ~/ 88);
}
});
});
}
接着改造一下home_top.dart文件,让头部随着滚动的状态变化, 定义一个alpha属性接受滑动的传值。
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: Get.context.mediaQueryPadding.top),
decoration: BoxDecoration(color: Colors.white),
child: Stack(
children: [
renderSearch(),
Container(
height: 40,
width: Get.width,
margin: EdgeInsets.only(top: 55 + (0 - 40 * widget.alpha / 255)),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
'关注',
'推荐',
'热榜',
'后端',
'前端',
'Android',
'iOS',
'人工智能',
'开发工具',
'代码人生',
'阅读'
].map<Widget>((e) => tagButton(title: e)).toList(),
),
),
)
],
),
);
renderSearch() {
return Positioned(
top: 0 - 40 * widget.alpha / 255,
child: Container(
width: Get.width,
height: 40,
child: Row(
children: [
Expanded(
child: InkWell(
onTap: () {
widget.onSearchPress();
},
child: Container(
margin: EdgeInsets.only(left: 10),
height: 36,
decoration: BoxDecoration(
color: Color.fromRGBO(235, 242, 244, 1)
.withAlpha(255 - widget.alpha),
borderRadius: BorderRadius.all(Radius.circular(5)),
),
child: Row(
children: [
Padding(padding: EdgeInsets.symmetric(horizontal: 5)),
Icon(
Icons.search,
size: 18,
color: Colors.grey.withAlpha(255 - widget.alpha),
),
Padding(padding: EdgeInsets.symmetric(horizontal: 5)),
Text(
'搜索掘金',
style: TextStyle(
fontSize: 18,
color: Colors.grey.withAlpha(255 - widget.alpha)),
)
],
),
),
)),
FlatButton(
minWidth: 80,
onPressed: () {
widget.onTagPress();
},
child: Row(
children: [
Icon(
Icons.settings,
size: 18,
color: Colors.grey.withAlpha(255 - widget.alpha),
),
Text('标签',
style: TextStyle(
fontSize: 18,
color: Colors.grey.withAlpha(255 - widget.alpha)))
],
))
],
),
));
}
主页面调用
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromRGBO(235, 242, 244, 1),
body: Column(
children: [
HomeTopPage(
onSearchPress: () {},
onTagPress: () {},
alpha: _alpha,
),
Expanded(child: HomeList(onScroll: (value) {
setState(() {
_alpha = value;
});
})),
],
));
}
就ok了, 欢迎同学们来指导讨论。懂得分享才能共同进步。