寫一個仿掘金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了, 歡迎同學們來指導討論。懂得分享才能共同進步。