往期
上期回顾
上期主要完成环境的搭建和部署,最终在jetpack.net.cn地址上呈现,这期我们就开始搭建主页,构建一个可以兼容三端(Android、Ios、Web)的主页。
开始
在lib文件夹下创建home.dart文件,如图
打开文件,输入st出来的提示选择第一个,如图
这样就自动生成一个完整的模版代码,一个小小的技巧,请笑纳。最后命名PageHome,这里简单说下命名规则,只是建议,页面以Page开头,非页面以Widget开头,这样在其他地方引用的时候容易寻找,提高查找效率。接下来,我们就用一个兼容三端的组件来构建主页,不卖关子,我们使用Material主题,因为我是Android开发,更喜欢这个主题,我们在新增组件的时候总感觉很麻烦,其实也有个小技巧,如图
选第一个就可以生成一个包裹组件,继续看图
然后将widget改为Material。然后添加Padding,Scaffold组件如图
Padding的用意很简单,加一个左右边距,而在web,和手机上的边距是不一样的,不能固定死,我画个图展示展示下,如图
所以使用ResponsiveWidget判断是否是小屏幕,如果屏幕变小则使用小边距来适配,这里注意是小屏幕,不是针对的Android或者苹果手机,要注意不能混淆理解,浏览器也可以缩小到手机屏幕大小对吧,要理解ResponsiveWidget请看前期博客:一个Flutter widget自动适配不同UI到Web、Android
还有一个ScreenUtil,这个你可以理解为相当于Android中的dp单位,他负责的就是等比适配,不会让大小分辨率的屏幕看起来差距很大。这个工具有个初始化的过程
ScreenUtil.instance = ScreenUtil.getInstance()..init(context);
而且初始化必须在MaterialApp包裹中,为什么呢?因为我们使用了MediaQuery动态获取屏幕的宽高,详细请看官方文档https://api.flutter.dev/flutter/widgets/MediaQuery-class.html里面讲的很清楚:
WidgetsApp and MaterialApp, which introduce a MediaQuery and keep it up to date with the current screen metrics as they change.
目前WidgetsApp和MaterialApp实现了MediaQuery的逻辑,所以你不在MaterialApp中初始化是会报错地,知道了吧。接下来看下实际运行效果,我们将内容设置成红色来看,如图
屏幕缩小后如图
这就是我们要的效果,其实已经有了两个框架封装的很完整,但前期我们为什么没有选择引用它们呢,而且他们实现的似乎更合理,请看他们的代码风格如图
一个ScreenTypeLayout这个组件实现了四个屏幕的适配,框架引用:
responsive_builder: ^0.1.5
我为什么没有选择直接使用呢?其实我们在明白其中原理后确实可以直接引用,但我还是建议自己写一遍,通过自己的实现,更清楚其中的道理不是吗。前期的学习过程中,我们尽量的不去引用,选择自己实现,也是加快提高自己的一个办法。多多学习,多多练习。接下来我们实现Scaffold部分,Scaffold这个组件太好用了,先看下它原本的样子
它就像一个大的容器,帮住我们组织好了各个组件的位置,是对一个基础UI的高度抽象。
简单分析下它的参数源码
其实这里最主要的三个常用的部分appBar、body、floatingActionButton,正好是我们构建一个UI的常用构造,所以这个组件基本是我们使用频率很高的一个组件,请认真学习它,更详细的学习,请看https://api.flutter.dev/flutter/material/Scaffold-class.html,这里面有几个例子,认真学习哦,我们现在通过它,来构建我们的主页代码,请看效果图:
我们先构建了AppBar的内容,怎么实现的呢,请看代码
@override Widget build(BuildContext context) { ScreenUtil.instance = ScreenUtil.getInstance()..init(context); return Material( child: Padding( padding: EdgeInsets.symmetric( horizontal: !ResponsiveWidget.isSmallScreen(context) ? (ScreenUtil.getInstance().setWidth(108)) : (ScreenUtil.getInstance().setWidth(6))), child: Scaffold( appBar: AppBar( title: _buildTitle(), /// 左边标题 backgroundColor: Color(0xFFf1f3f4), actions: !ResponsiveWidget.isSmallScreen(context) ? _buildActions(context) : null, /// 右边的menu菜单,这里在小屏幕不显示。 ), body: Center(child: Text('You have pressed the button $_count times.')), ), )); } Widget _buildTitle() { return RichText( text: TextSpan( // Note: Styles for TextSpans must be explicitly defined. // Child text spans will inherit styles from parent style: TextStyle( fontSize: 14.0, color: Colors.black, ), children: <TextSpan>[ TextSpan( text: "Jetpack", style: TextStyles.logo, ), TextSpan( text: ".net.cn", style: TextStyles.logo.copyWith( color: Color(0xFF50AFC0), ), ), ], ), ); } _buildActions(BuildContext context) { return <Widget>[ MaterialButton( child: Text( 'Home', style: TextStyles.menuItem, ), onPressed: () { if (ResponsiveWidget.isSmallScreen(context)) Navigator.pop(context); }, ), MaterialButton( child: Text( 'About', style: TextStyles.menuItem, ), onPressed: () { if (ResponsiveWidget.isSmallScreen(context)) Navigator.pop(context); }, ), ]; }
分别通过_buildTitle,_buildActions 实现左边的标题,右边的菜单按钮,里面有个细节:
大屏幕的返回_buildActions,小屏幕直接隐藏,这里你可能有疑问,为什么不用drawer实现菜单?drawer适合小屏幕实现,而大屏慕我们就要充分利用空间,尽可能的操作简单,一目了然。下面我们来实现,小屏幕的drawer,请看代码
///省略部分代码 Scaffold( ///省略部分代码 drawer: _buildDrawer(context), ), _buildDrawer(BuildContext context) { return ResponsiveWidget.isSmallScreen(context) ? Drawer( child: ListView( padding: const EdgeInsets.all(20), children: _buildActions(context), ), ) : null; }
给Scaffold添加一个drawer组件,这里用ListView把刚才的_buildActions组件再放进来,原来是以横向展示,这次因为ListView默认是纵向,所以就是上下的展示方式,运行项目后,如图:
未展开的样子,标题右边的菜单消失
展开的样子,这就是我们要实现的小屏幕的样子。也符合手机的使用习惯。
有没有发现这个东东显示看不清楚,接下来我们改造它,可我没做过,我怎么查呢?我这里分享我的经验,首先你要明白这里的关键字是什么,当我鼠标停留在上面的时候提示如图
是一个navigation menu,所以我会在google里搜索,flutter scaffold drawer navigation menu,这么几个关键字,然后搜到的如图
第一个是官方文档如何添加,第二个是custom drawer,自定义的,有可能讲到了如何修改,所以我就点进去寻找
看到没,还是很好找的哈,然后copy过来代码,发现它用的图不对,我们修改下,如图,这样是不是看着舒服了。
接下来就是给Scaffold的body填充内容了,先看实现的效果
Home页面黄色
About页蓝色
点击切换,具体实现请看下面代码
int _selectedDrawerIndex = 0; ///定义一个坐标值 /// 添加动态的body _getDrawerItemWidget child: Scaffold( body: _getDrawerItemWidget(_selectedDrawerIndex), ), /// 省略部分代码 /// 根据一个index值,来确定加载哪个页面 _getDrawerItemWidget(int selectedDrawerIndex) { switch(selectedDrawerIndex){ case 0: return WidgetMenuHome(); break; case 1: return WidgetMenuAbout(); break; } } /// 在_buildActions函数中的组件onPressed的时候调用,这样就可以更新UI setState(() { _selectedDrawerIndex = 0; });
好了这期我们就先这样,学习到此结束,下期继续。
总结
这次主要是对Scaffold的使用,以及如何构建不同平台的UI,确切说是不同宽度屏幕的构建,实现了大屏幕的Menu菜单,和小屏幕的Drawer菜单显示,而且没有考虑分包,下期将进行分包设计,并往页面里面填充内容,来构建我们的需求UI 实现一个Flutter Jetpack,并把之前做的Android Jetpack也挪过来。