首页实现总结
目录
- 根布局
- Banner图
- AppBar
- 卡片布局
根布局
根布局使用Scaffold
,这个是material包下的组件,它是一个路由页的骨架,可以非常容易的拼装出一个完整的页面。进入首页先显示加载,通过网络访问得到数据后关闭,还要支持下拉刷新的功能。
技术点:
MediaQuery.removePadding移除系统栏Padding
RefreshIndicator控制下拉刷新。ListView滚动的时候,并且检查滚动的深度为0,防止子View的滚动造成性能损耗。
ListView利用显示的自列表来构造 List
。此构造函数适合于具有少量子元素的列表视图,因为构造列表需要为可能显示在列表视图中的每个子元素执行工作,而不仅仅是那些实际可见的子元素。
_handleRefresh 是Flutter中的异步函数,在未来某个时刻执行。主要用来获取网络数据。
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xfff2f2f2),
body: LoadingContainer(// 加载页面
isLoading: _loading,// 控制加载页面
child: Stack(// ListView与AppBar 类似Stack结构摆放
children: [
MediaQuery.removePadding(// 去掉系统栏的Padding
// 去掉顶部屏幕适配
removeTop: true,
context: context,
child: RefreshIndicator(// 下拉刷新
child: NotificationListener(
onNotification: (scrollNotification) {// 监听下拉的状态
if (scrollNotification is ScrollUpdateNotification &&
scrollNotification.depth == 0) {
// 优化, 防止scrollNotification=0的时候也监听,只有在ScrollUpdateNotification更新的时候才监听并且只监听ListView的滚动滚动且是列表滚动的时候
_onScroll(scrollNotification.metrics.pixels);
}
},
child: _listView,
),
onRefresh: _handleRefresh),
),
_appBar,
],
)
)
);
}
Future _handleRefresh() async {// Fullter中的异步,返回一个Future
try {
HomeModel mode = await HomeDao.fetch();
setState(() {// 更新Model
localNavList = mode.localNavList;
subNavList = mode.subNavList;
gridNavModel = mode.gridNav;
salesBoxModel = mode.salesBox;
bannerList = mode.bannerList;
_loading = false;// 显示视图
});
} catch (e) {
print(e);
_loading = false;
}
return null;
}
载入布局LoadingContainer:
此Widget由外部isLoading控制显示隐藏,内部持有子Widget的引用,cover控制是否覆盖整个页面@override Widget build(BuildContext context) { return !cover // // 是否覆盖整个页面 ? isLoading ? _loadingView : child : Stack( children:
[child, isLoading ? _loadingView : null], ); }
1. Banner图
引入flutter_swiper
库。进入到pubspec.yaml文件里添加,同时,引入三方库也是一样。图片通过网络加载。
pagination: SwiperController
用于控制 Swiper的index属性, 停止和开始自动播放. 通过 new SwiperController()
创建一个SwiperController实例,并保存,以便将来能使用。
Widget get _banner {
return Container(// Container设置固定高度
height: 160,
child: Swiper(//第三方控件
itemCount: bannerList.length,// 长度
autoplay: true,// 开启自动播放
itemBuilder: (BuildContext context, int index) {
return GestureDetector(// 返回一个可以点击的图片
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
CommonModel model = bannerList[index];
return WebView(
url: model.url,
title: model.title,
hideAppBar: model.hideAppBar,
);
}),
);
},
child: Image.network(bannerList[index].icon, fit: BoxFit.fill),
);
},
pagination: SwiperPagination(), // 指示器
),
);
}
2.AppBar
流程分析:AppBar 在下滑的过程中透明度发生改变,AppBar可以选择城市,进入搜索页面。
decoration(装饰器)DecoratedBox可以在其子widget绘制前(或后)绘制一个装饰Decoration(如背景、边框、渐变等)。主要由外部的appBarAlpha值控制透明度。
控制透明度逻辑:监听ListView滑动的距离,滑动过程中不断改变透明值,通过setState()更新视图。
Widget get _appBar {
return Column(
// 上面输入,下面阴影
children: [
Container(
decoration: BoxDecoration(
gradient: LinearGradient(//线性渐变
// AppBar 渐变遮罩背景, 由透明到
colors: [Color(0x66000000), Colors.transparent],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
)),
child: Container(
padding: EdgeInsets.fromLTRB(0, 30, 0, 0),
height: 80.0,
decoration: BoxDecoration(
color:
Color.fromARGB((appBarAlpha * 255).toInt(), 255, 255, 255)
),
child: SearchBar(
searchBarType: appBarAlpha > 0.2
? SearchBarType.homeLight
: SearchBarType.home,
inputBoxClick: _jumpToSearch,
speakClick: _jumpToSpeak,
defaultText: SEARCH_BAR_DEFAULT_TEXT,
leftButtonClick: () {},
),
),
),
Container(
// 阴影设置
height: appBarAlpha > 0.2 ? 0.5 : 0,
decoration: BoxDecoration(
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 0.5)]),// 设置阴影
)
],
);
}
// 滚动的逻辑
_onScroll(offset) {
var alpha = offset / APPBAR_SCROLL_OFFSET;
if (alpha < 0) {
// alpha 值的保护
alpha = 0;
} else if (alpha > 1) {
alpha = 1;
}
setState(() {
appBarAlpha = alpha;
});
print(appBarAlpha);
}
SeacherBar
对InputDecoration进行了封装,拥有多种成员函数,是否禁止搜索、按钮隐藏、背景颜色、函数回调等多种功能。
final void Function() leftButtonClick;
将函数从其他地方传入,加大扩展性。
_wrapTap()
封装函数对可点击的按钮进行封装
ValueChanged
文本内容变换监听,用于改变按钮的样式
优化:原因:由于每次文本变化需要从网络拉取数据,可能造成用户输入的内容不是真正需要显示的内容。
解决:在网络请求方法保存keyword
。检查请求来的keyword是否一样。检查Widget重绘的次数,并且解决BUG。_wrapTap(Widget child, void Function() callback) { return GestureDetector( onTap: () { if (callback != null) callback(); }, child: child, ); } SearchDao.fetch(url, text).then((SearchModel model) { // 只有当当前输入的内容与服务端返回的内容一致才渲染 if (model.keyword == keyword) {// 检验keyword setState(() { searchModel = model; }); } }).catchError((e) { print(e); });
3. 卡片布局
布局分成三部分,再将其中一部分分为,一个大卡片两个小卡片。
PhysicalModel 实现卡片圆角
FractionallySizedBox:撑满父布局,FractionallySizedBox控件会根据现有空间,来调整child的尺寸,所以说child就算设置了具体的尺寸数值,也不起作用。
@override
Widget build(BuildContext context) {
return PhysicalModel(//
color: Colors.transparent,
borderRadius: BorderRadius.circular(6),// 圆角
clipBehavior: Clip.antiAlias, // 裁切
child: Column(
children: _gridNavItems(context),
),
);
}
4. 底部卡片
顶部是可点击的图片与文本,下面是三组卡片,第一组高度较大
Image: BoxFit.fill:会拉伸填充满显示空间,图片本身长宽比会发生变化,图片会变形。
width: MediaQuery.of(context).size.width:获得手机屏幕宽度,需要计算padding
参考
- flutter 轮播组件 Swiper
- flutter widget: ListView
- Flutter 布局(四)- Baseline、FractionallySizedBox、IntrinsicHeight、IntrinsicWidth详解
- BoxDecoration
- Image