Course App 布局学习

先决条件

  • 安装flutter_staggered_grid_view

UI概览

临摹项目地址

abuanwar072/Course-App (github.com)

UI展示

Pasted image 20230206231240.png

开始学习

第一次做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)

Pasted image 20230206235235.png

第一个页面就完成啦


image.png
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容