1. MaterialApp
- 首先在项目里面使用的是MaterialApp。
MaterialApp 是一个方便的Widget, 它封装了应用程序实现Material Design所需要的一些Widget,一般作为顶层widget使用 在MaterialApp里面有Home(主页)属性 title(标题) color(颜色) Theme(主题) routes(路由)
- MaterialApp中的属性
// Scaffold组件是MaterialApp Design布局结构的基本实现,此类提供了用于显示
// drawer、snackbar和sheet的API
// Scaffold有下面几个主要属性
// appbar 显示在界面顶部的一个AppBar
// body 当前界面所显示的主要内容 Widget
// drawer 抽屉菜单控件
// onGenerateRoute 路由传值, 配置路由
2. 路由的处理
- 对路由统一管理
新建路由文件,对路由进行统一拦截处理,主要是来处理路由携带的参数
//固定写法
class RouterUtil{
static Route<dynamic> ? onGenerateRoute (RouteSettings settings) {
// 统一处理
print("------------");
final String? name = settings.name;
final Function pageContentBuilder = routers[name] as Function;
if (pageContentBuilder != null) {
if (settings.arguments != null) {
final Route route = MaterialPageRoute(
builder: (context) =>
pageContentBuilder(context, arguments: settings.arguments));
return route;
}else{
final Route route = MaterialPageRoute(
builder: (context) =>
pageContentBuilder(context));
return route;
}
}
}
}
3.组件
在flutter中万物都是组件
-
有状态组件与无状态组件
StatefulWidget与StatelessWidget
import 'package:flutter/material.dart';
import 'package:flutterTanhua/pages/friends/components/RecommendList.dart';
class FansLike extends StatefulWidget {
final arguments;
final TabController ?tabController;
const FansLike({this.tabController, this.arguments});
_FansLikeState createState() => _FansLikeState(arguments: this.arguments);
}
class _FansLikeState extends State<FansLike>
with SingleTickerProviderStateMixin {
Map ? arguments;
TabController ? tabController;
_FansLikeState({this.tabController, this.arguments});
@override
void initState() {
super.initState();
tabController = TabController(length: 3, vsync: this);
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(colors: [
Colors.purple,
Colors.deepOrange,
], begin: Alignment.centerLeft, end: Alignment.centerRight),
),
),
title: TabBar(
indicatorColor: Colors.white,
indicatorSize: TabBarIndicatorSize.label, // 指示器是类型, label是这样的,tab是沾满整个tab的空间的
isScrollable: true, // 是否可以滑动
indicatorWeight: 3.0, // 指示器的高度/厚度
unselectedLabelStyle: TextStyle(fontSize: 16), // 未选择样式
labelStyle: TextStyle( fontSize: 24, height: 2), // 选择的样式
tabs: [
Tab(
child: Text("互相关注", style: TextStyle(color: Colors.white),),
// icon: Icon(Icons.recommend),
// text: "推荐",
),
Tab(
// icon: Icon(Icons.directions_bike),
child: Text("关注", style: TextStyle(color: Colors.white),),
),
Tab(
// icon: Icon(Icons.directions_bike),
child: Text("粉丝", style: TextStyle(color: Colors.white),),
),
],
controller: tabController,
),
),
body: TabBarView(
children: [
Center(child:
Container(
padding: EdgeInsets.all(10),
child: RecommendList(arguments: {"isIcon": "btn", "eachOther": "all"},),
)),
Center(child:
Container(
padding: EdgeInsets.all(10),
child: RecommendList(arguments: {"isIcon": "btn", "eachOther": "like"},),
)
),
Center(child:
Container(
padding: EdgeInsets.all(10),
child: RecommendList(arguments: {"isIcon": "btn", "eachOther": "fans"},),
)
),
],
controller: tabController,
),
);
}
@override
void dispose() {
tabController!.dispose();
super.dispose();
}
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false, // 是否显示debugger
theme: ThemeData(
primarySwatch: Colors.deepPurple,
),
home: Tabs(),
// 路由传值, 配置路由
onGenerateRoute: RouterUtil.onGenerateRoute,
// routes: routers,
);
}
}
-
自定义堆叠滑动组件
import 'dart:math';
import 'package:flutter/material.dart';
import '../../../data/SwiperData.dart';
class MySwiper extends StatefulWidget {
final arguments;
const MySwiper({this.arguments}) ;
_MySwiperState createState() => _MySwiperState(arguments: this.arguments);
}
class _MySwiperState extends State<MySwiper> {
var currentPage = images.length - 1.0;
PageController ? controller;
Map arguments;
_MySwiperState({ required this.arguments});
@override
void initState() {
super.initState();
controller = PageController(initialPage: images.length - 1);
// print(controller);
controller!.addListener(() {
setState(() {
currentPage = controller!.page!;
});
});
}
@override
Widget build(BuildContext context) {
// TODO: implement build
print(arguments);
return Scaffold(
backgroundColor: Colors.transparent,
body: Center(
child: Stack(
children: <Widget>[
// 两者堆叠在一起。通过PageView滑动的Controller来控制当前显示的page
CardScrollWidget(currentPage),
Positioned.fill(
child: PageView.builder(
itemCount: images.length,
controller: controller,
reverse: true,
itemBuilder: (context, index) {
return Container();
},
),
)
],
),
),
);
}
}
class CardScrollWidget extends StatelessWidget {
final currentPage;
final padding = 20.0;
final verticalInset = 20.0;
CardScrollWidget(this.currentPage);
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: (12.0 / 16.0) * 1.2,
child: LayoutBuilder(
builder: (context, contraints) {
var width = contraints.maxWidth;
var height = contraints.maxHeight;
var safeWidth = width - 2 * padding;
var safeHeight = height - 2 * padding;
var heightOfPrimaryCard = safeHeight;
var widthOfPrimaryCard = heightOfPrimaryCard * 12 / 16;
var primaryCardLeft = safeWidth - widthOfPrimaryCard;
var horizontalInset = primaryCardLeft / 2;
List<Widget> cardList = [];
for (int i = 0; i < images.length; i++) {
var leftPage = i - currentPage;
bool isOnRight = leftPage > 0;
var start = padding +
max(
primaryCardLeft -
horizontalInset * -leftPage * (isOnRight ? 15 : 1),
0);
var cardItem = Positioned.directional(
top: padding + verticalInset * max(-leftPage, 0.0),
bottom: padding + verticalInset * max(-leftPage, 0.0) ,
start: start,
textDirection: TextDirection.rtl,
child: ClipRRect(
borderRadius: BorderRadius.circular(16.0),
child: Container(
decoration: BoxDecoration(color: Colors.white, boxShadow: [
BoxShadow(
color: Colors.black12,
offset: Offset(3.0, 6.0),
blurRadius: 10.0)
]),
child: AspectRatio(
aspectRatio: 12 / 16,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Align(
child: Image.network("${images[i]["header"]}", fit: BoxFit.cover,),
),
Align(
alignment: Alignment.bottomLeft,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// 设置标题
Padding(
padding: EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
child: Column(
children: <Widget>[
Text(
images[i]["nick_name"],
style: TextStyle(
color: Colors.black,
fontSize: 18,
),
),
Text("${images[i]["marry"]} | ${images[i]["degree"]} | 年龄相仿", textAlign: TextAlign.left),
Padding(
padding:
EdgeInsets.only(left: 12, top: 10),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 22.0, vertical: 6.0),
decoration: BoxDecoration(
color: Colors.purpleAccent,
borderRadius:
BorderRadius.circular(20.0)),
child: Text("点击查看",
style: TextStyle(color: Colors.white)),
),
)
],
)
),
SizedBox(
height: 10,
),
],
),
)
],
),
),
),
));
cardList.add(cardItem);
}
return Stack(
children: cardList,
);
},
),
);
}
}
-
通用头部组件
因为有些样式需要自定义,感觉使用AppBar有些局限,索性直接去掉这个,在body里面自定义并下沉
/// 自定义app头部, 默认返回上一页 /// 参数 title:header显示的文字 import 'package:flutter/material.dart'; class CommonHeader extends StatefulWidget { final title; const CommonHeader({this.title}); _CommonHeaderState createState() => _CommonHeaderState(title: this.title); } class _CommonHeaderState extends State<CommonHeader> { Map title; _CommonHeaderState({required this.title}); @override Widget build(BuildContext context) { // TODO: implement build return Container( constraints: BoxConstraints(maxHeight: 80), width: double.infinity, alignment: Alignment.center, decoration: BoxDecoration( image: DecorationImage( image: new ExactAssetImage("images/headbg.png"), fit: BoxFit.cover)), child: Stack( alignment: Alignment.center, children: <Widget>[ Positioned( left: 10, bottom: 15, child: GestureDetector( onTap: () { Navigator.pop(context, true); }, child: Container( alignment: Alignment.center, child: Row( children: <Widget>[ Icon( Icons.arrow_back_ios, color: Colors.white, ) ], ), ), )), Positioned( bottom: 0, child: Container( constraints: BoxConstraints(maxHeight: 50), alignment: Alignment.center, child: Text( "${title["title"]}", style: TextStyle( color: Colors.white, fontSize: 22, fontWeight: FontWeight.w500), ), ), ) ], )); } } -
button组件
/// 渐变按钮组件 /// 参数:wh: 宽度 double /// 参数 ht: 高度 double /// 参数 src: 按钮背景图片,用来设置按钮渐变色 /// 参数 text: 按钮需要显示的文字 import 'package:flutter/material.dart'; typedef OnPressedChangeState(); class LineGradientButton extends StatefulWidget { final OnPressedChangeState ? onPressedChangeState; final arguments; LineGradientButton(this.onPressedChangeState, {this.arguments}); _LineGradientButtonState createState() => _LineGradientButtonState(this.onPressedChangeState, arguments:this.arguments); } class _LineGradientButtonState extends State<LineGradientButton> { Map ? arguments; OnPressedChangeState ? onPressedChangeState; _LineGradientButtonState(this.onPressedChangeState, {this.arguments}); @override Widget build(BuildContext context) { // TODO: implement build print(arguments!["src"]); return GestureDetector( child: Container( width: arguments !["wd"], height: arguments !["ht"], // constraints: BoxConstraints(), alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), image: DecorationImage( image: new ExactAssetImage(arguments!["src"]), fit: BoxFit.cover)), child: Text("${arguments!["text"]}", style: TextStyle(color: Colors.white, fontSize: 18),), ), onTap: onPressedChangeState, ); } }
4.沉浸式头部
/// 交友页面顶部组件
/// Align控件即对齐控件,能将子控件所指定方式对齐,并根据子控件的大小调整自己的大小。
/// Expanded组件是flutter中使用率很高的一个组件,它可以动态调整child组件沿主轴的尺寸,比如填充剩余空间,比如设置尺寸比例。它常常和Row或Column组合起来使用。
import 'package:flutter/material.dart';
class Header extends StatefulWidget {
const Header({Key? key}) : super(key: key);
_HeaderState createState() => _HeaderState();
}
class _HeaderState extends State<Header> {
// 创建Icon图标
List <Widget> _createIcon(){
Color color;
String title = "";
List<Widget> tempList = [SizedBox(width: 50,)];
for(int i = 0; i < 3; i++){
switch (i) {
case 0:
color = Colors.red;
title = "探花";
break;
case 1:
title = "搜附近";
color = Colors.blue;
break;
default:
title = "测灵魂";
color = Colors.deepOrangeAccent;
break;
}
tempList.add(Expanded(
child: GestureDetector(
child: Column(
children: <Widget>[
SizedBox(height: 50,),
Align(
child: Container(
width: 60,
constraints: BoxConstraints(maxHeight: 60),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: color
),
// color: Colors.red,
child: Align(
child: Container(
width: 40,
constraints: BoxConstraints(maxHeight: 40),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
image: DecorationImage(
image: new ExactAssetImage("images/${i}.png"),
fit: BoxFit.cover)),
),
)
),
),
Text("${title}", style: TextStyle(color: Colors.white),)
],
),
onTap: (){
switch (i) {
case 0:
Navigator.pushNamed(context, "/searchFlower");
break;
case 1:
Navigator.pushNamed(context, "/searchNear");
break;
case 2:
Navigator.pushNamed(context, "/testSoul");
break;
}
},
)
));
}
tempList.add(SizedBox(width: 50,));
return tempList;
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return
Container(
constraints: BoxConstraints(maxHeight: 160),
width: double.infinity,
decoration: BoxDecoration(
image: DecorationImage(
image: new ExactAssetImage("images/img.png"),
fit: BoxFit.cover)),
child: Row(
children: this._createIcon()
),
);
}
}
5.遇到的问题
-
Container 嵌套 Container 时,明明指定了子组件的宽高,为什么不起作用 ?
这是因为 Container 的宽高计算机制造成的,因为 Container 在计算宽高的时候,不仅需要考虑 width 和 height 属性,还要遵循父组件的尺寸约束,即 BoxConstraints 。
BoxConstraints 有四个属性,分别为 minWidth、maxWidth、minHeight、maxHeight。默认情况下,minWidth 和 maxWidth 的默认值为屏幕宽度,minHeight 和 maxHeight 的默认值为屏幕高度。
父组件通过设置 BoxConstraints 来约束子组件的最小和最大尺寸,如果子组件的 width 和 height 不在父组件 Constraints 限制的范围内,则子组件的尺寸会被强制设置为符合父组件 Constraints 约束的值。
给子组件设置的宽高都为 50 ,而父组件约束的最小宽高分别为屏幕宽度和高度,子组件的宽高不满足父组件的约束,所以当我们给子组件设置了宽高时,并没有起到作用,所以子组件会充满父组件。
解决的方式有多种,其中最简单的就是在子组件外层套 Center 组件,查看 Center 组件的源码可知,被 Center 组件包裹的子组件,该子组件将不再受父组件的尺寸约束。Center 组件又是继承自 Align 组件的,所以用 Align 组件嵌套子组件也是可以的。
-
Null check operator used on a null value
这个主要是我在定义参数的时候允许为空,但是在使用的时候没有判断是否为空造成。
-
RenderFlex children have non-zero flex but incoming height constraints are unbounded.
原因是ListView垂直方向的计算是包裹子View的,也就是说子View必须有一个明确的高度,或者尽可能小的高度,而不能是无限高。 Row是横向排列,在Row中使用Expanded是填充水平方向的剩余空间,这和ListView的这一特性没有冲突,可以使用。
而Column是竖直排列,在Column中使用Expanded是填充竖直方向的剩余空间,这将和ListView的这一特性发生冲突,因为ListView将无法计算自己的子View的高度。
这个主要是我使用了ListView但是又把他当作是Container 的中的Colum组件去使用,所以我给这个ListView包了一层Expand解决掉了。