开发的过程就是最好的学习过程,也是检验自己能力过程,更是升华自己的过程。
明事理的壮汉——Expanded
在我们在开发Flutter页面的过程中,Column和Row无疑是布局中用的最多的控件,但有的时候会发现个别布局太大会超出屏幕,调试时受影响的边缘会出现黄色和黑色条纹图案。入下图:
解决办法:在Text外面套上一个Expanded控件。最终的效果,如下图:
为什么会出现这种情况呢?搜索了半天也没找到答案。我猜想:是像Row和Column这样的控件只会给子控件一个方向和对齐方式,没有在大小上约束,子控件按父的方向上依次绘制自己,所以才会超出屏幕。这个时候,Expanded控件就派上了用场。他好比我们在安卓中的控件的weight属性,这个控件在默认约束的范围内尽可能的扩大自己。比如上面的例子里,Expanded会占据屏幕中除了Image控件宽度外的空间,给Text在宽度上加上限制。除此之外,多个Expanded控件在同一个Row或者Column的时候,Expanded通过属性flex设置比重值来决定本身的大小,用法和android中的weight使用方法相同。
PageView中子页面切换重复刷新问题
我们在用PageView的时候,发现页面切换后,子页面会重新初始化。这种交互给用户的体验很不好。如何避免页面切换页面刷新呢。子页面必须是StatefulWidget,在State中添加AutomaticKeepAliveClientMixin。具体使用请参考:https://blog.csdn.net/xcf111/article/details/95318987。这篇帖子,很详细。
TabBar+PageView联动
在开发中用到TabBar+PageView这个组合的时候非常多,两个控件的联动效果很重要。在联动过程中这两个控件的滚动事件的相互作用,在TabBar跨顺序选中时,会出现不正常的效果。在这里提前给大家指明一下。下面给出一种解决办法:
import 'package:flutter/material.dart';
class MyHomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() => HomeState();
}
class TabTitle {
String title;
int id;
TabTitle(this.title, this.id);
}
class HomeState extends State<MyHomePage> with SingleTickerProviderStateMixin {
TabController mTabController;
PageController mPageController = PageController(initialPage: 0);
List<TabTitle> tabList;
var currentPage = 0;
var isPageCanChanged = true;
@override
void initState() {
super.initState();
initTabData();
mTabController = TabController(
length: tabList.length,
vsync: this,
);
mTabController.addListener(() {
//TabBar的监听
if (mTabController.indexIsChanging) {
//判断TabBar是否切换
print(mTabController.index);
onPageChange(mTabController.index, p: mPageController);
}
});
}
initTabData() {
tabList = [
new TabTitle('推荐', 10),
new TabTitle('热点', 0),
new TabTitle('社会', 1),
new TabTitle('娱乐', 2),
new TabTitle('体育', 3)
];
}
//联动的的关键方法
onPageChange(int index, {PageController p, TabController t}) async {
//判断是哪一个切换,
if (p != null) {//TabBar导致的滚动
//TabBar切换导致的,屏蔽PageView的滚动事件
isPageCanChanged = false;
await mPageController.animateToPage(index,
duration: Duration(milliseconds: 500),
curve: Curves.ease); //等待pageview切换完毕,再释放pageivew监听
isPageCanChanged = true;
} else {//PageView导致的滚动
mTabController.animateTo(index); //切换Tabbar
}
}
@override
void dispose() {
super.dispose();
mTabController.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("首页"),
backgroundColor: Color(0xffd43d3d),
),
body: Column(
children: <Widget>[
Container(
color: new Color(0xfff4f5f6),
height: 38.0,
child: TabBar(
isScrollable: true,//是否可以滚动
controller: mTabController,
labelColor: Colors.red,
unselectedLabelColor: Color(0xff666666),
labelStyle: TextStyle(fontSize: 16.0),
tabs: tabList.map((item) {
return Tab(
text: item.title,
);
}).toList(),
),
),
Expanded(
child: PageView.builder(
itemCount: tabList.length,
onPageChanged: (index) {
if (isPageCanChanged) {
//由于pageview切换是会回调这个方法,又会触发切换tabbar的操作,所以定义一个flag,控制pageview的回调
onPageChange(index);
}
},
controller: mPageController,
itemBuilder: (BuildContext context, int index) {
return Text(tabList[index].title);
},
),
)
],
),
);
}
}
为方便大家观看,特此贴出整理后的代码。逻辑很简单,请参考注释。
以上代码抽取自帖子:https://www.jianshu.com/p/7f5b7e7d3c9a。
Dart语言小亮点——方法函数中的可选参数
在上个问题中我们在看提供的代码时,会发现onPageChage这个关键方法的参数有点蹊跷。我们在其他的开发语言中没接触过这种写法。通过了解发现这是Dart语言中函数方法还有可选参数这么一说。熟悉以后会发现这种写法,也挺人性化。具体学习请参考下文:
可选参数可以是位置参数,也可以是命名参数,但不能同时是位置参数和命名参数。
1.可选命名参数
调用函数时,可以使用paramName : value
指定命名参数。例如:
enableFlags(bold: true, hidden: false);
定义函数时,使用{ param 1,param 2,... }指定命名参数:
/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold, bool hidden}) {...}
Flutter
实例创建表达式可能会变得复杂,因此Widget
构造函数专门使用命名参数。这使得实例创建表达式更容易阅读。
你可以用@required
注释任何Dart代码中的命名参数(不仅仅是Flutter
),以指示它是必需的参数。例如:
const Scrollbar({Key key, @required Widget child})
当Scrollbar
被构建时,分析器会在缺少child
参数时报错。
Require
被定义在meta包里。要么直接导入package:meta/meta.dart
,要么导入另一个导出meta
包,如Flutter的package:flutter/material.dart.
2.可选位置参数
在[]
中包装一组函数参数,将它们标记为可选位置参数:
String say(String from, String msg, [String device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
这里有一个调用这个函数的例子,没有可选参数:
assert(say('Bob', 'Howdy') == 'Bob says Howdy');
下面是用第三个参数调用这个函数的例子:
assert(say('Bob', 'Howdy', 'smoke signal') ==
'Bob says Howdy with a smoke signal');
3.默认参数值
函数可以使用=
为命名参数和位置参数定义默认值。默认值必须是编译时常量。如果未提供默认值,则默认值为null。
下面是为命名参数设置默认值的示例:
///设置bold默认值为true,实质hidden'默认值为false
void enableFlags({bool bold = false, bool hidden = false}) {...}
// bold为true; hidden为 false.
enableFlags(bold: true);
下一个示例显示如何设置位置参数的默认值:
String say(String from, String msg,
[String device = 'carrier pigeon', String mood]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
if (mood != null) {
result = '$result (in a $mood mood)';
}
return result;
}
assert(say('Bob', 'Howdy') ==
'Bob says Howdy with a carrier pigeon');
你也可以将集合(lists)
或映射(maps)
作为默认值传递。下面的示例定义了一个函数doStuff()
,该函数指定了一个默认的集合
来放置list
参数和默认的映射
来放置gitfts
的参数
void doStuff(
{List<int> list = const [1, 2, 3],
Map<String, String> gifts = const {
'first': 'paper',
'second': 'cotton',
'third': 'leather'
}}) {
print('list: $list');
print('gifts: $gifts');
}
本篇文章到这里结束了,敬请期待下篇文章吧。^_^
悄悄话:
安卓开发者的福利:小绿人 一个实用的安卓开发工具箱,搜集了数千个开源项目。拿走不谢^_^。