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解決掉了。