先决条件
- 安装
flutter_staggered_grid_view
包
UI概览
临摹项目地址
abuanwar072/Course-App (github.com)
UI展示
开始学习
第一次做flutter工程,先不赘述前面的Android Studio的部分以及各种SDK的安装。
大致就是记得开启Studio的Proxy到自己的Clash上(http://127.0.0.1:7890
)
绘制主界面
主界面分为以下几个部分
- Header
- Hi Text
- Search
- Category Grid View
Header
从本质上来说,flutter的UI就是一层一层的返回(通过child或者children),好比XML的一层一层的结构。
对于这种一行内两个元素,并且分开排版的界面,应该使用Row进行绘制。
而与此同时,设置mainAxisAlignment:MainAxisAlignment.spaceBetween
来使得两个元素分别在头尾布局。
在使用IconButton的时候,其会自带一个padding,导致位置和预想的不一致。因此,设置一个padding: const EdgeInsets.all(0)
是必要的。
最后,为Header编写一个独立的继承StatelessWidget
的类HomeScreenHeader
class HomeScreenHeader extends StatelessWidget {
const HomeScreenHeader({super.key});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.all(0), //必须要加
icon: const Icon(Icons.menu)
),
Image.asset('assets/images/user.png'),
]);
}
}
提示:
- Icon在开启
uses-material-design
后可以使用谷歌MD的Icon,可以自行寻找使用,直接Icons即可。如 Icon(Icons.menu) 。
Hi Text
不太清楚该怎么说这种文本的名字,就叫Hi Text好(罢
本质上就是两个Text竖向排列,使用Column
这里的TextStyle都是预设的一些文本属性,无需在意
class HiText extends StatelessWidget {
const HiText({super.key});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text('Hi Heer', style: kHeadingTextStyle),
Text('Find a course you want to learn ',
style: kSubheadingTextStyle)
],
);
}
}
Search
绘制一个简单的不能输入的搜索框(
对于margin来说,我需要在padding上拥有上下的数值,曾经在XAML中,可以使用Padding="0,30"
来编写垂直20的边距。在flutter中,可以给容器的padding赋值一个EdgeInsets.symmetric(vertical: 30)
来指定一个对称的EdgeInsets
而对于容器的圆角,可以为Container赋予decoration来实现。
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(40))
这样子我们就绘制了一个圆角的灰色的小框框,我们要在里面继续绘制搜索框的Placeholder和Search Icon。设置其child为一个Row,而Row中包含着一个Icon,一个用于分开的SizedBox,以及Text。
class Search extends StatelessWidget{
const Search({super.key});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 30),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
height: 60,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(40)),
child: Row(
children: const [
Icon(Icons.search),
SizedBox(width: 16),
Text(
'Search for anything',
style: TextStyle(fontSize: 18, color: Colors.grey),
)
],
),
);
}
}
Category Grid View
暂时不会自己实现。要用到瀑布流的包
这里我们import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
来引入包。这个包可以自行查看相关用法。
同时,使用Expand展开内部的Row或者Column使其使用完整的高度、宽度。
使用MasonryGridView
来创建瀑布流Grid视图。
itemCount是数据模型的数组长度,在代码中编写itemBuilder函数来返回每一个item对应的Widget。
var category = categories[index];
return Container(
padding: const EdgeInsets.all(20),
height: index.isEven ? 200 : 240,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
image: DecorationImage(
image: AssetImage(category.image),
fit: BoxFit.fill,
)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(category.name, style: kTitleTextStyle),
Text("${category.numOfCourses} Courses",
style: TextStyle(color: kTextColor.withOpacity(0.5)))
],
),
);
这里还有个点,就是在flutter中,主轴对齐就是mainAxisAlignment
,就是从上到下那个轴。而横向的对齐就是CrossAxisAlignment
提示:
- Color可以使用
withXXX()
来生成一个改变某个属性的新Color,TextStyle同理使用.copyWith()
来改变属性返回新的style
最后,完成这个Expand与GridView的Widget。
Expanded(
child: MasonryGridView.count(
crossAxisCount: 2,
crossAxisSpacing: 20,
mainAxisSpacing: 20,
itemCount: categories.length,
itemBuilder: (context, index) {
var category = categories[index];
return Container(
padding: const EdgeInsets.all(20),
height: index.isEven ? 200 : 240,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
image: DecorationImage(
image: AssetImage(category.image),
fit: BoxFit.fill,
)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(category.name, style: kTitleTextStyle),
Text("${category.numOfCourses} Courses",
style: TextStyle(color: kTextColor.withOpacity(0.5)))
],
),
);
},
))
收工
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Course App',
debugShowCheckedModeBanner: false,
theme: ThemeData(),
home: const HomeScreen(),
);
}
}
在MyApp的home Widget中拼接这些部分。
顺便给Scaffold一个padding(不包括btm)
第一个页面就完成啦