【精选】Flutter之试学经验整理
编辑日期 2020-04-02 作者 AbyssKitty github
序:在19年底至今20年4月,我通过闲暇时间进行学习以及编写Flutter的小Demo,见证版本从1.0到1.5以及今天1.12.9的快速发展后,成为一名Flutter粉丝。
本文主要还是讲解学习经验,理论知识涉及不多。
本文将从技术对比切入,再到技术优势以及相关学习资料分享,最后将附上实际开发代码来收尾。
相信绝大部分开发者在学习一门新知识的时候都会与类似的技术做对比。
Flutter与ReactNative对比(个人见解):
序:根据我两年多ReaceNative开发经验,以及一万行代码量Flutter亲身体验编程经验,以及原生开发经验来做一个全方位的对比和经验分享。
(新技术坑多很正常,大家要享受这种过程,心照不宣)
可复用性:
因为我本身在外包公司工作,讲究的是开发效率以及复用,这一点我还是更看好Flutter。
ReactNative:是基于原生组件开发的,受原生Api限制,基本上每过6个月就会产生重大更新,导致以前的代码运行需要适配,而三方库中,绝大部分都会标注这么一句话(本项目旨在支持最新的RN版本)这一点大大增加复用难度。
Flutter:使用自绘UI,极大脱离原生组件的限制,相对复用简单,但坑也不少,理论上将来会更适合外包开发,未来可期。
学习难度:
ReactNative:
使用JavaScript开发:js相信大家都会,毕竟是基础课程之一。ES6语法大家基本也能驾驭的住,所以上手ReactNative相对简单。
入手难度:js、html基础编程经验,React经验。
学习过程:会js和html就可以上手,相对比较简单,后期需要学习Android和iOS的基础原生知识学习与原生桥接等。
使用Dart开发:Dart这玩意还挺好使的,性能也很好,这么介绍他吧--既有JavaScript的简便,又有Java的严谨,是弱类型写法的强类型语言,这句话可能说的不严谨,但我想表达的就是这个意思,简单用一个代码示例说明一下(Dart可以使用 js中的 var来命名变量,比如var str; str这个变量将会在他第一次赋值之后,来确定他的类型,比如赋值为str=1;那么他以后就是int类型。如果第一次赋值为str='abc',那么他以后就都是String类型)Dart的同类还有TypeScript等他们都是用的最新的渐进类型。
入手难度:java、JavaScript编程经验,Android、ios原生编程经验,
学习过程:因为Dart和Java一样,所以有Java等类似的强类型语言或其他面向对象语言的编程经验会有很大的帮助,比如Android开发者上手比较简单,但Dart的写法简化的Java繁琐的过程,所以写法类似于ES6的写法,所以js开发者也应该很好上手,同理苹果*炸天的Swift也是同类所以iOS开发者也很好上手。写法很简单,在Dart2中便省略了new这个关键词,从new一个对象变成了直接就有一个对象(new String() === String())。
性能对比:
这里目前都差不多,大家有兴趣可以搜索相关文章查看
ReactNative:60帧
Flutter:60帧
完善度:
ReactNative:基本满足外包项目开发,三方充足,部分有坑。
Flutter:基础UI库满足外包项目95%的开发工作,上有5%的UI需要自己造*。预计在2020年下半年到年底会完善起来并可以投入外包开发使用。
静静等待阿里等带头大哥开源组件库。
个人见解:我的理解是ReactNative是移动端跨平台的解决方案,而Flutter的格局除了跨平台解决方案这一tag,还有更深远的格局,例如web端以及桌面应用的支持,例如即将诞生的新系统Fuchsia。说白了就是通过现在流行的“跨平台”这一特性来提高Flutter的认知度以及推广Flutter的底层语言Dart以及新系统Fuchsia。
天下大势分久必合合久必分,散了这么久的大前端...也许Flutter就是“多端一体化”的革命性技术开端吧。
整体而言,ReactNative更适合目前web的前端工程师入手,Flutter更适合Android、iOS原生开发者入手。
搭建开发环境:
我这里只介绍使用AndroidStudio搭建开发环。
AndroidStudio下载:http://www.android-studio.org/ 现在最近版本是3.5.x
1.下载Flutter SDK 我这里采用直接下载的方式,并没有使用GIT方式
下载官方网站:https://flutter.dev/docs/development/tools/sdk/releases#windows
2.下载好后解压,配置环境变量
export FLUTTER_HOME=/Users/abysskitty/Desktop/local/flutter1_12_13
export PATH=${PATH}:${FLUTTER_HOME}/bin
3.AndroidStudio安装插件Flutter 安装Flutter插件会自动引入Dart插件
4.创建项目开始编程。
学习资料:
目前发现的学习资料:
目前发现的学习资料:
目前发现的学习资料:
https://github.com/alibaba/flutter-go
https://flutter-go.pub/website/
flutter-go是由“阿里拍卖”前端团队几位Flutter粉丝,用业余时间开发的一款,用于Flutter教学帮助的App,这里没有高大尚的概念,只有一个一个亲身经历的尝试,用最直观的方式展示的Flutter官方Demo。
类似于node中npm里的东西,一个专用的组件库,Dart和Flutter组件都有。
据说阿里有一套自己的Flutter组件库,像阿里开源的RN的组件库andt一样,目前我还没找到开源路径。
https://book.flutterchina.club/
Flutter中文网创始人编写的书籍电子版
https://github.com/Solido/awesome-flutter
Flutter组件库整理集合(注意筛选优质项目)
推荐文章:
字节跳动为什么选用Flutter:并非跨平台终极之选,但它可能是不一样的未来
https://juejin.im/post/5e81e37f6fb9a03c7e200370
试学代码分享:
序:新手学习的时候(我也是新手),最直接的阻碍就是无法系统性的学习,因技术过新,以及中文文档匮乏的情况下,无法获取到系统性的学习资料,这里我通过实际开发经验来分享我这边,学习到的常用的经验,以及教程上没有提及但依然很重要的知识点。
这里没有做教程,只是简单的代码展示,结合实际的官方文档或者三方文档,有不会的,可以参考我的写法,应该在某处可以帮助到你。或者官方文档没有的组件,在这里可以偶然发现一种。
入口文件及路由写法之一(包含国际化):
import 'package:flutter/material.dart';
import './pages/TabMain.dart';
import './pages/login/Login.dart';
import './pages/login/Register.dart';
import './pages/newslist/NewsList.dart';
import './pages/user/SetChildren.dart';
import './pages/user/SetParent.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
void main() => runApp(Main());
class Main extends StatelessWidget{
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "",
home: Login(),
// home: TabMain(),
routes: <String, WidgetBuilder>{
'/Login': (BuildContext context) => Login(),
'/TabMain': (BuildContext context) => TabMain(),
'/Register': (BuildContext context) => Register(),
'/NewsList': (BuildContext context) => NewsList(),
'/SetChildren': (BuildContext context) => SetChildren(),
'/SetParent': (BuildContext context) => SetParent(),
},
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
const FallbackCupertinoLocalisationsDelegate(),
],
supportedLocales: [
const Locale('zh', 'CH'),
const Locale('en', 'US'),
],
);
}
}
class FallbackCupertinoLocalisationsDelegate
extends LocalizationsDelegate<CupertinoLocalizations> {
const FallbackCupertinoLocalisationsDelegate();
@override
bool isSupported(Locale locale) => true;
@override
Future<CupertinoLocalizations> load(Locale locale) =>
DefaultCupertinoLocalizations.load(locale);
@override
bool shouldReload(FallbackCupertinoLocalisationsDelegate old) => false;
}
BottomNavigationBar用法:
import 'package:flutter/material.dart';
import './fragment/HomeFragment.dart';
import './fragment/DataFragment.dart';
import './fragment/UserFragment.dart';
import '../r.dart';
/// This Widget is the main application widget.
class TabMain extends StatelessWidget {
static const String _title = 'Flutter Code';
@override
Widget build(BuildContext context) {
return MyStatefulWidget();
}
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
@override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _selectedIndex = 0;
List<Widget> _widgetOptions = <Widget>[
HomeFragment(),
DataFragment(),
UserFragment(),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: null,
body: Container(
child: _widgetOptions.elementAt(_selectedIndex),
width: double.infinity,
height: double.infinity,
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
activeIcon: Image(image: AssetImage("assets/images/tab_icon_home_s.png"),width: 22,height: 22,),
icon: Image(image: AssetImage("assets/images/tab_icon_home_n.png"),width: 22,height: 22,),
title: Text('首页'),
),
BottomNavigationBarItem(
activeIcon: Image(image: AssetImage("assets/images/tab_icon_data_s.png"),width: 22,height: 22,),
icon: Image(image: AssetImage("assets/images/tab_icon_data_n.png"),width: 22,height: 22,),
title: Text('数据收集'),
),
BottomNavigationBarItem(
activeIcon: Image(image: AssetImage("assets/images/tab_icon_me_s.png"),width: 22,height: 22,),
icon: Image(image: AssetImage("assets/images/tab_icon_me_n.png"),width: 22,height: 22,),
title: Text('我'),
),
],
currentIndex: _selectedIndex,
unselectedItemColor: Color(0xFF999999),
selectedItemColor: Color(0xFFEC94AF),
selectedFontSize: 11,
unselectedFontSize: 11,
onTap: _onItemTapped,
),
);
}
}
FutureBuilder(异步渲染用法“推荐”):
@override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder<List<BannerBean>>(
future: this.getBannerFut(),
builder: (BuildContext context, AsyncSnapshot<List<BannerBean>> snapshot){
switch (snapshot.connectionState) {
case ConnectionState.none:
return Container(
child: Image(
image: AssetImage(R.assetsImagesHomeTopBanner),
fit: BoxFit.cover,
));
case ConnectionState.waiting:
return Container(
child: Image(
image: AssetImage(R.assetsImagesHomeTopBanner),
fit: BoxFit.cover,
));
case ConnectionState.active:
return Container(
child: Image(
image: AssetImage(R.assetsImagesHomeTopBanner),
fit: BoxFit.cover,
));
case ConnectionState.done:
return SwiperDiy(swiperDataList: snapshot.data);
}
},
),
width: double.infinity,
height: 220,
);
}
轮播基础用法(三方组件:flutter_swiper: ^1.1.6):
import 'package:flutter_swiper/flutter_swiper.dart';
......
class SwiperDiy extends StatelessWidget {
final List<BannerBean> swiperDataList;
SwiperDiy({Key key, this.swiperDataList}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: 220,
child: Swiper(
onTap: ((index) => { print(swiperDataList[index].id) }),
itemBuilder: (BuildContext context, int index) {
return Image.network(swiperDataList[index].pic,fit: BoxFit.cover,);
},
itemCount: swiperDataList.length, // 数量
pagination: SwiperPagination(
alignment: Alignment.bottomCenter,
builder: DotSwiperPaginationBuilder(
size: 8,
activeSize: 8,
color: Color(0x66000000),
activeColor: Color(0xBB000000)
)
), //点
autoplay: true, //是否自动播放
autoplayDelay: 5000,
),
);
}
}
下拉刷新用法(三方组件:flutter_easyrefresh: ^2.1.0):
import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:white_careflow/models/CardClassBean.dart';
import 'package:white_careflow/models/HomeArticleBean.dart';
import '../../r.dart';
import 'package:dio/dio.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
import 'package:flutter_swiper/flutter_swiper.dart';
import '../../models/BannerBean.dart';
import '../../models/ResListBean.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:flutter_easyrefresh/material_header.dart';
import 'package:flutter_easyrefresh/material_footer.dart';
......
class ListViewtj extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return ListViewtjState();
}
}
class ListViewtjState extends State<ListViewtj> {
List str = new List();
EasyRefreshController _controller = EasyRefreshController();
int page;
@override
void initState() {
super.initState();
page = 1;
this._getList();
// _controller.callRefresh();
}
_getList() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
try {
String url = "http://leukemia.pro6.liuniukeji.net/api.php/Index/recommendArticle";
FormData formData = FormData.fromMap({
"token": prefs.get("token"),
"p": page,
});
print("res url="+url);
Response response = await Dio().post(url,data: formData);
print("response="+response.toString());
ResListBean res = ResListBean.fromJson(response.data);
List<HomeArticleBean> calist = new List<HomeArticleBean>();
if(res.status == 1){
for(int i = 0;i<res.data.length;i++){
calist.add(HomeArticleBean.fromJson(res.data[i]));
}
if(calist == null){
_controller.finishLoad(success: true, noMore: true);
}else{
if(page == 1){
setState(() {
str.clear();
str.addAll(calist);
});
_controller.finishRefresh(success: true);
}else{
setState(() {
str.addAll(calist);
});
_controller.finishLoad(success: true, noMore: false);
}
}
}else{
_controller.finishLoad(success: true, noMore: true);
}
} catch (e) {
print(e);
}
}
@override
Widget build(BuildContext context) {
return EasyRefresh(
controller: _controller,
header: MaterialHeader(
// showInfo: false,
// refreshText: "下拉刷新",
// refreshReadyText: "释放立即刷新",
// refreshingText: "正在努力刷新...",
// refreshedText: "刷新完成",
// bgColor: Colors.transparent,
// textColor: Color(0xFF666666),
),
footer: ClassicalFooter(
showInfo: false,
loadText: "上拉加载更多",
loadReadyText: "释放立即加载",
loadingText: "正在加载更多内容...",
loadedText: "加载完成",
noMoreText: "没有更多内容了",
bgColor: Colors.transparent,
textColor: Color(0xFF666666),
),
child: CustomScrollView(
slivers: <Widget>[
SliverToBoxAdapter(
child: Banner(),
),
SliverToBoxAdapter(
child: HeaderSearch(),
),
SliverToBoxAdapter(
child: CardClass(),
),
SliverToBoxAdapter(
child: Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("推荐文章",style: TextStyle(color: Color(0xFF333333),fontSize: 16),)
],
),
width: double.infinity,
height: 50,
margin: EdgeInsets.fromLTRB(0, 10, 0, 0),
),
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index){
return ListItem(item: str[index]);
}, childCount: str.length),
),
],
),
onRefresh: () async{
setState(() {
page = 1;
this._getList();
});
},
onLoad: () async{
setState(() {
page++;
this._getList();
});
},
);
}
}
class ListItem extends StatelessWidget {
final HomeArticleBean item;
ListItem({Key key, this.item}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Text(""),
width: 110,
height: 98,
margin: EdgeInsets.fromLTRB(15, 0, 0, 0),
decoration: ShapeDecoration(
image: DecorationImage(
image: NetworkImage(item.cover_id),
fit: BoxFit.cover),
shape: RoundedRectangleBorder( borderRadius: BorderRadiusDirectional.circular(5)),
),
),
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: Text(item.name ,style: TextStyle(color: Color(0Xff333333),fontSize: 14),maxLines: 2,),
margin: EdgeInsets.fromLTRB(10, 15, 10, 0),
),
Container(
child: Text(item.describe ,style: TextStyle(color: Color(0Xff999999),fontSize: 12),maxLines: 2,),
margin: EdgeInsets.fromLTRB(10, 3, 10, 0),
),
Expanded(flex: 1,child: Container(),),
Container(
child: Row(
children: <Widget>[
Container(
child: Image(image: AssetImage(R.assetsImagesArticleIconBrowse)),
width: 19,
height: 19,
),
Expanded(
child: Text(" 浏览量:${item.click_count}",style: TextStyle(color: Color(0Xff999999),fontSize: 10),maxLines: 1),
),
Container(
child: Text(item.create_time ,style: TextStyle(color: Color(0Xff999999),fontSize: 10),maxLines: 1),
),
],
),
margin: EdgeInsets.fromLTRB(10, 0, 10, 15),
),
],
),
),
],
),
width: double.infinity,
height: 128,
);
}
}
dio接口调用(三方组件dio: ^3.0.9):
void login() async{
if(this._phoneController.text.length == 0){
Fluttertoast.showToast(msg: "请输入手机号");
return;
}
if(this._passwordController.text.length == 0){
Fluttertoast.showToast(msg: "请输入密码");
return;
}
// mobile 文本 必填 手机号
// password 文本 必填 密码
try {
Response response = await Dio().post("http://xxxxxxxxxxxxxxxxxxxxxxxx/login",data: {
"mobile" : this._phoneController.text,
"password" : this._passwordController.text, });
print(response.data);
Map<String, dynamic> map = json.decode(json.encode(response.data));
//输出
Fluttertoast.showToast(msg: map['info']);
if(map['status'] == 1){
Map<String, dynamic> data = json.decode(json.encode(map['data']));
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString("token", data['token']);
Navigator.pushReplacementNamed(context,"/TabMain");
// Navigator.pushNamed(context, "/TabMain");
}else{
}
} catch (e) {
print(e);
}
}
Fluttertoast(上面例子中的toast组件:fluttertoast: ^4.0.0):
Fluttertoast.showToast(msg: "请输入密码");
持久化存储(shared_preferences: ^0.5.6+3):
//保存简单用法 void fun() async{ ... await ... }
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString("token", data['token']);
//使用简单用法
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.get("token")
json解析三方组件(json_annotation json_serializable)具体用法详见三方:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
json_annotation: ^3.0.0
dev_dependencies:
flutter_test:
sdk: flutter
json_serializable: ^3.2.0
ResBean.dart:
import 'package:json_annotation/json_annotation.dart';
part 'ResBean.g.dart';
@JsonSerializable()
class ResBean{
final String status;
final String info;
final String data;
ResBean(this.status, this.info, this.data);
factory ResBean.fromJson(Map<String, dynamic> json) => _$ResBeanFromJson(json);
Map<String,dynamic> toJson() => _$ResBeanToJson(this);
}
ResBean.g.dart:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'ResBean.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ResBean _$ResBeanFromJson(Map<String, dynamic> json) {
return ResBean(
json['status'] as String,
json['info'] as String,
json['data'] as String,
);
}
Map<String, dynamic> _$ResBeanToJson(ResBean instance) => <String, dynamic>{
'status': instance.status,
'info': instance.info,
'data': instance.data,
};
图片资源文件引入插件(注意这是插件:flutter-img-sync):
组要是flutter的图片必须这样引入(引入后自动生成的写法):
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
assets:
# assets-generator-begin
# assets/images/*
- assets/images/article_img_8.png
- assets/images/article_img_9.png
# assets-generator-end
使用插件后,这样写,用# assets-generator-begin # assets-generator-end包裹住你的图片存放路径 # assets/images/*(这里是自定义的路径):然后运行插件在AndroidStudio中的Tool -> flutter-img-sync 就可以了(引入前写法)
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
assets:
# assets-generator-begin
# assets/images/*
# assets-generator-end
粘性滚动用法CustomScrollView(这个组件内部的孩子都是slivers家族的组件,):
CustomScrollView(
slivers: <Widget>[
SliverToBoxAdapter(
child: Banner(),
),
SliverToBoxAdapter(
child: HeaderSearch(),
),
SliverToBoxAdapter(
child: CardClass(),
),
SliverToBoxAdapter(
child: Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("推荐文章",style: TextStyle(color: Color(0xFF333333),fontSize: 16),)
],
),
width: double.infinity,
height: 50,
margin: EdgeInsets.fromLTRB(0, 10, 0, 0),
),
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index){
return ListItem(item: str[index]);
}, childCount: str.length),
),
],
)
不超过两屏或三屏的滚动组件SingleChildScrollView,超过的话建议使用上面那个:CustomScrollView
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../r.dart';
import 'package:dio/dio.dart';
/// This Widget is the main application widget.
class UserFragment extends StatelessWidget {
@override
Widget build(BuildContext context) {
return (
new SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
width: 64,
height: 64,
margin: EdgeInsets.fromLTRB(15, 45, 15, 0),
decoration: BoxDecoration(
border: Border.all(color: Color(0xaaFFFFFF),width: 1,style: BorderStyle.solid),
shape: BoxShape.circle,
image: DecorationImage(
image: AssetImage(R.assetsImagesHomeTopBanner),
fit: BoxFit.cover,
),
),
),
Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("昵称",style: TextStyle(color: Colors.white,fontSize: 16,fontWeight: FontWeight.bold),maxLines: 1,),
Container(
child: Container(),
height: 4,
),
Text("女丨23岁",style: TextStyle(color: Color(0xFFFEEFEC),fontSize: 11,fontWeight: FontWeight.bold)),
],
),
margin: EdgeInsets.fromLTRB(0, 45, 0, 0),
),
Expanded(child: Container(),),
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
child: Text("个人信息",style: TextStyle(color: Color(0xFFFEEFEC),fontSize: 13)),
margin: EdgeInsets.fromLTRB(0, 0, 15, 0),
),
// Container(
// child: Image(
// image: AssetImage(R.assetsImagesCurrencyIconMore),
// color: Colors.white,
// colorBlendMode: BlendMode.dstIn,
// ),
// width: 20,
// height: 20,
// margin: EdgeInsets.fromLTRB(0, 0, 15, 0),
// )
],
),
margin: EdgeInsets.fromLTRB(0, 45, 0, 0),
)
],
),
width: double.infinity, //1500 × 696
height: MediaQuery.of(context).size.width * 696 / 1500,
decoration: BoxDecoration(
image: DecorationImage(image: AssetImage(R.assetsImagesMeTopBg),fit: BoxFit.cover)
),
),
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Container(),
width: 4,
height: 22,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(11),
color: Color(0xFFEC94AF),
),
margin: EdgeInsets.fromLTRB(15, 0, 10, 0),
),
Text(
"患儿信息",style: TextStyle(color: Color(0xff333333),fontSize: 16,fontWeight: FontWeight.bold),
),
Expanded(
child: Container(),
),
Container(
child: Image(image: AssetImage(R.assetsImagesCurrencyIconMore)),
width: 24,
height: 24,
margin: EdgeInsets.fromLTRB(0, 0, 15, 0),
)
],
),
width: double.infinity,
height: 50,
color: Colors.white,
margin: EdgeInsets.fromLTRB(0, 15, 0, 0),
),
Column(
children: <Widget>[
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Text("姓名:",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),
width: 76,
),
Container(
child: Text("汪汪",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),margin: EdgeInsets.fromLTRB(0, 1, 0, 0),
)
],
),
width: double.infinity,
height: 26,
color: Colors.white,
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
),
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Text("性别:",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),
width: 76,
),
Container(
child: Text("无",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),margin: EdgeInsets.fromLTRB(0, 1, 0, 0),
)
],
),
width: double.infinity,
height: 26,
color: Colors.white,
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
),
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Text("生日:",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),
width: 76,
),
Container(
child: Text("2020",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),margin: EdgeInsets.fromLTRB(0, 1, 0, 0),
)
],
),
width: double.infinity,
height: 26,
color: Colors.white,
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
),
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Text("就诊医院:",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),
width: 76,
),
Container(
child: Text("2020",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),margin: EdgeInsets.fromLTRB(0, 1, 0, 0),
)
],
),
width: double.infinity,
height: 26,
color: Colors.white,
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
),
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Text("疾病分型:",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),
width: 76,
),
Container(
child: Text("2020",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),margin: EdgeInsets.fromLTRB(0, 1, 0, 0),
)
],
),
width: double.infinity,
height: 26,
color: Colors.white,
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
),
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Text("危险程度分型:",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),
width: 104,
),
Container(
child: Text("2020",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),margin: EdgeInsets.fromLTRB(0, 1, 0, 0),
)
],
),
width: double.infinity,
height: 26,
color: Colors.white,
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
),
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Text("就诊日期:",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),
width: 76,
),
Container(
child: Text("2020",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),margin: EdgeInsets.fromLTRB(0, 1, 0, 0),
)
],
),
width: double.infinity,
height: 26,
color: Colors.white,
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
),
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Text("治疗阶段:",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),
width: 76,
),
Container(
child: Text("这里看龙骨架OS的即佛了",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),margin: EdgeInsets.fromLTRB(0, 1, 0, 0),
)
],
),
width: double.infinity,
height: 26,
color: Colors.white,
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
),
Container(
child: Container(),
width: double.infinity,
height: 12,
color: Colors.white,
),
],
),
GestureDetector(
onTap: (() => {}),
child: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("系统消息",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),
Expanded(child: Container(),),
Container(
child: Image(image: AssetImage(R.assetsImagesCurrencyIconMore)),
width: 26,
height: 26,
),
],
),
width: double.infinity,
height: 49,
margin: EdgeInsets.fromLTRB(0, 10, 0, 0),
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
color: Colors.white,
),
),
GestureDetector(
onTap: (() => { Navigator.pushNamed(context, "/SetChildren" ) }),
child: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("修改密码",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),
Expanded(child: Container(),),
Container(
child: Image(image: AssetImage(R.assetsImagesCurrencyIconMore)),
width: 26,
height: 26,
),
],
),
width: double.infinity,
height: 49,
margin: EdgeInsets.fromLTRB(0, 1, 0, 0),
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
color: Colors.white,
),
),
GestureDetector(
onTap: (() => { Navigator.pushNamed(context, "/SetParent" ) }),
child: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("关于我们",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),
Expanded(child: Container(),),
Container(
child: Image(image: AssetImage(R.assetsImagesCurrencyIconMore)),
width: 26,
height: 26,
),
],
),
width: double.infinity,
height: 49,
margin: EdgeInsets.fromLTRB(0, 1, 0, 0),
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
color: Colors.white,
),
),
GestureDetector(
onTap: (() => {}),
child: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("版本信息",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),
Expanded(child: Container(),),
Container(
child: Image(image: AssetImage(R.assetsImagesCurrencyIconMore)),
width: 26,
height: 26,
),
],
),
width: double.infinity,
height: 49,
margin: EdgeInsets.fromLTRB(0, 1, 0, 0),
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
color: Colors.white,
),
),
Container(
child: RaisedButton(
onPressed: () => { this._quit(context) },
color: Color(0xFFFFFFFF),
child: Text(
"退出登录",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
color: Color(0xFFEC94AF)
),
),
),
width: double.infinity,
height: 50,
margin: EdgeInsets.fromLTRB(0, 10, 0, 40),
),
],
),
)
);
}
void _quit(context){
showCupertinoDialog(
context:context,
builder:(BuildContext context){
return CupertinoAlertDialog(
title: new Text(
"提醒!",style: TextStyle(color: Colors.black87,fontSize: 16),
),
content: Container(
child: Text("确定要退出登录嘛?",style: TextStyle(color: Colors.black54,fontSize: 14)),
margin: EdgeInsets.fromLTRB(0, 5, 0, 0),
),
actions: <Widget>[
new Container(
decoration: BoxDecoration(
// border: Border(right:BorderSide(color: Color(0xfff4f5f6),width: 1.0),top:BorderSide(color: Color(0xfff4f5f6),width: 1.0))
),
child: FlatButton(
child: new Text("确定",style: TextStyle(color: Colors.blueAccent,fontSize: 14)),
onPressed:(){
Navigator.pop(context);
Navigator.pushReplacementNamed(context,"/Login");
},
),
),
new Container(
decoration: BoxDecoration(
// border: Border(top:BorderSide(color: Color(0xfff4f5f6),width: 1.0))
),
child: FlatButton(
child: new Text("取消",style: TextStyle(color: Colors.black54,fontSize: 14)),
onPressed:(){
Navigator.pop(context);
},
),
)
],
);
}
);
}
}
flutter中的flex布局 Expanded(child: xxx) Expanded这个组件就是flex:1:
GestureDetector(
onTap: (() => { Navigator.pushNamed(context, "/SetChildren" ) }),
child: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("修改密码",style: TextStyle(color: Color(0xFF333333),fontSize: 14)),
Expanded(child: Container(),),
Container(
child: Image(image: AssetImage(R.assetsImagesCurrencyIconMore)),
width: 26,
height: 26,
),
],
),
width: double.infinity,
height: 49,
margin: EdgeInsets.fromLTRB(0, 1, 0, 0),
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
color: Colors.white,
),
),
其他事例不再一一介绍。
文章结尾(完)。
上一篇: js 放大镜
推荐阅读