今天文章来到有点迟,今天周五,一大早公司就开会,所以来晚了,今天到内容给大家放大招,一个漂亮到自定义 Scaffold到实现,还有漂亮到动画,建议收藏以后用。 欢迎来到技术刚刚好头条,本头条是个人维护,每天至少更新一篇Flutter技术文章,实时为大家播报Flutter最新消息。如果你刚好也在关注Flutter这门技术,那就跟我一起学习进步吧,你的赞,收藏,转发是对我个人最大的支持,维护不易,欢迎关注。 近几年,移动端跨平台开发技术层出不穷,从Facebook家的ReactNative,到阿里家WEEX,前端技术在移动端跨平台开发中大展身手,技术刚刚好作为一名Android开发,经历了从Reactjs到Vuejs的不断学习。而在2018年,我们的主角变成了Flutter,这是Goolge开源的一个移动端跨平台解决方案,可以快速开发精美的移动App。希望跟大家一起学习,一起进步! dart到封装思路,library的使用方法,part的功能介绍和使用。自定义图标到使用,Flutter当中PageView到实现,CurvedAnimation动画到使用,半圆角的矩形边框RoundedRectangleBorder学习。part of使用说明,关键字 with学习。 大概就是上面知识点学习,我相信远远不止这些,其实学习代码开发最重要到核心思想还是动手,本文会把所有到代码都复制进来,也欢迎大家自己动手敲一遍代码,你会有很大到收获,谢谢大家。 library指令可以帮助创建一个模块化的,可共享的代码库。库不仅提供了API,还提供隐私单元:以下划线(_)开头的标识符只对内部库可见。每个Dartapp就是一个库,即使它不使用库指令。 这也算是核心实现类了,可以具体代码请看下面,我一一列出,自己copy到项目可以直接使用。 pubspec文件只用到了#使用自定义图标flutter_vector_icons: ^0.2.1 希望大家能够希望,本文耗时几个小时弄出来的,刚好写完去吃饭了。 谢谢观看技术刚刚好头条文章,本头条是个人维护,每天至少更新一篇Flutter技术文章,实时为大家播报Flutter最新消息。如果你刚好也在关注Flutter这门技术,那就跟我一起学习进步吧,你的赞,收藏,转发是对我个人最大的支持,维护不易,欢迎关注。本头条核心宗旨
技术刚刚好经历
本文核心要点
整个项目到运行效果GIF展示
主页main.dart代码
import 'package:eva_icons_flutter/eva_icons_flutter.dart';
import 'navbar_scaffold.dart';
import 'package:flutter/material.dart';
import 'package:flutter_vector_icons/flutter_vector_icons.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ExtendedNavBar(),
);
}
}
class ExtendedNavBar extends StatefulWidget {
ExtendedNavBar({Key key}) : super(key: key);
_ExtendedNavBarState createState() => _ExtendedNavBarState();
}
class _ExtendedNavBarState extends State<ExtendedNavBar> {
@override
Widget build(BuildContext context) {
return ExtendedNavigationBarScaffold(
body: Container(
color: Colors.blueAccent,
),
elevation: 0,
floatingAppBar: true,
//首页到搜索框
appBar: AppBar(
shape: kAppbarShape,
leading: IconButton(
icon: Icon(
EvaIcons.person,
color: Colors.black,
),
onPressed: () {},
),
title: Text(
'扩展脚手架的示例',
style: TextStyle(color: Colors.black),
),
centerTitle: true,
backgroundColor: Colors.white,
),
navBarColor: Colors.white,
navBarIconColor: Colors.black,
//更多按钮到数组
moreButtons: [
MoreButtonModel(
icon: MaterialCommunityIcons.wallet,
label: '钱包',
onTap: () {},
),
MoreButtonModel(
icon: MaterialCommunityIcons.parking,
label: '停车场',
onTap: () {},
),
MoreButtonModel(
icon: MaterialCommunityIcons.car_multiple,
label: '我到汽车',
onTap: () {},
),
MoreButtonModel(
icon: FontAwesome.book,
label: '书籍',
onTap: () {},
),
MoreButtonModel(
icon: MaterialCommunityIcons.home_map_marker,
label: '银行',
onTap: () {},
),
MoreButtonModel(
icon: FontAwesome5Regular.user_circle,
label: '我的',
onTap: () {},
),
null,
MoreButtonModel(
icon: EvaIcons.settings,
label: '设置',
onTap: () {},
),
null,
],
//搜索
searchWidget: Container(
height: 50,
color: Colors.yellow,
),
parallexCardPageTransformer: PageTransformer(
pageViewBuilder: (context, visibilityResolver) {
//可以滑动到组件
return PageView.builder(
controller: PageController(viewportFraction: 0.85),
itemCount: parallaxCardItemsList.length,
itemBuilder: (context, index) {
final item = parallaxCardItemsList[index];
final pageVisibility =
visibilityResolver.resolvePageVisibility(index);
return ParallaxCardsWidget(
item: item,
pageVisibility: pageVisibility,
);
},
);
},
),
);
}
//page的具体内容
final parallaxCardItemsList = <ParallaxCardItem>[
ParallaxCardItem(
title: '技术刚刚好',
body: '技术刚刚好,学习笔记',
background: Container(
color: Colors.orange,
)),
ParallaxCardItem(
title: '技术刚刚好2',
body: '欢迎观看,欢迎关注',
background: Container(
color: Colors.redAccent,
)),
ParallaxCardItem(
title: '技术刚刚好3',
body: '欢迎收藏',
background: Container(
color: Colors.blue,
)),
];
}
声明navbar_scaffold框架
//import,part,library指令可以帮助创建一个模块化的,可共享的代码库。库不仅提供了API,
// 还提供隐私单元:以下划线(_)开头的标识符只对内部库可见。每个Dartapp就是一个库,即使它不使用库指令。
library navbar_scaffold;//声明库
import 'dart:math';
import 'package:eva_icons_flutter/eva_icons_flutter.dart';
import 'package:flutter/material.dart';
//添加实现文件,把part fileUri放在有库的文件,其中fileURI是实现文件的路径。然后在实现文件中,添加部分标识符
part 'scaffoldlib/ConstantMethods.dart';
part 'scaffoldlib/ease_in_widget.dart';
part 'scaffoldlib/ExtendedNavigationBarScaffold.dart';
part 'scaffoldlib/page_transformer.dart';
part 'scaffoldlib/ParallexCardWidet.dart';
scaffoldlib,5个类到结构查看
ConstantMethods.dart 代码如下
//part of加库名 表示该文件属于那个库
part of navbar_scaffold;
TextStyle ktitleStyle = TextStyle(fontWeight: FontWeight.w800);
TextStyle ksubtitleStyle = TextStyle(fontWeight: FontWeight.w600);
//半圆角的矩形边框
ShapeBorder kAppbarShape = RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topRight: Radius.circular(20),
topLeft: Radius.circular(20),
),
);
ease_in_widget.dart代码如下
part of navbar_scaffold;
//StatefulWidget又被称为有状态组件,开发者可以根据用户的操作来选择性的更新界面上的组件。
class EaseInWidget extends StatefulWidget {
final Widget child;
final Function onTap;
const EaseInWidget({Key key, @required this.child, @required this.onTap})
: super(key: key);
@override
State<StatefulWidget> createState() => _EaseInWidgetState();
}
//关键字 with 就是dart当中到 mixin,你需要使用with关键字,后跟一个或多个mixin的名
//mixin 的中文意思是混入,就是在类中混入其他功能。
//复用代码,mixin 最早的根源来自于Lisp
//在面向对象的语言中,mixin 类是一个可以把自己的方法提供给其他类使用,但却不需要成为其他类的父类。
//mixin 是要通过非继承的方式来复用类中的代码。
class _EaseInWidgetState extends State<EaseInWidget>
with TickerProviderStateMixin<EaseInWidget> {
AnimationController controller;
Animation<double> easeInAnimation;
@override
void initState() {
super.initState();
//动画到实现
controller = AnimationController(
vsync: this,
duration: Duration(
milliseconds: 200,
),
value: 1.0);
easeInAnimation = Tween(begin: 1.0, end: 0.95).animate(
CurvedAnimation(
parent: controller,
curve: Curves.easeIn,
),
);
controller.reverse();
}
@override
Widget build(BuildContext context) {
//Flutter中用于处理手势
// onTap: () //点击
// onDoubleTap: () //双击
//onLongPress: () //长按
return GestureDetector(
////点击
onTap: () {
controller.forward().then((val) {
controller.reverse().then((val) {
widget.onTap();
});
});
},
//动画,变换后的小部件的比例动画化
child: ScaleTransition(
scale: easeInAnimation,
child: widget.child,
),
);
}
//flutter生命周期方法
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
ExtendedNavigationBarScaffold.dart代码如下
part of navbar_scaffold;
double bottomBarVisibleHeight = 55.0;
double bottomBarOriginalHeight = 80.0;
double bottomBarExpandedHeight = 300.0;
class MoreButtonModel {
final IconData icon;
final String label;
final Function onTap;
MoreButtonModel({
@required this.icon,
@required this.label,
@required this.onTap,
});
}
class ExtendedNavigationBarScaffold extends StatefulWidget {
ExtendedNavigationBarScaffold({
Key key,
this.onTap,
this.currentBottomBarCenterPercent,
this.currentExternalAnimationPercentage = 0.0,
this.currentBottomBarMorePercent,
this.currentBottomBarSearchPercent,
this.moreButtons,
this.searchWidget,
this.parallexCardPageTransformer,
this.appBar,
this.body,
this.elevation,
this.navBarColor,
this.navBarIconColor,
this.floatingAppBar = false,
}) : assert(moreButtons != null ? moreButtons.length <= 9 : true),
assert(body != null),
super(key: key);
final Function(int) onTap;
final Function(double) currentBottomBarCenterPercent;
final Function(double) currentBottomBarMorePercent;
final Function(double) currentBottomBarSearchPercent;
final double currentExternalAnimationPercentage;
final Widget searchWidget;
final PreferredSizeWidget appBar;
final Widget body;
final double elevation;
final Color navBarColor;
final Color navBarIconColor;
final bool floatingAppBar;
///If you want Empty Space then put null.
///Maximum 9 buttons can be added.
///Buttons will be placed according to the list order.
final List<MoreButtonModel> moreButtons;
/// Parallex Cards
/// ```dart
/// PageTransformer(
/// pageViewBuilder: (context, visibilityResolver) {
/// return PageView.builder(
/// controller: PageController(viewportFraction: 0.85),
/// itemCount: parallaxCardItemsList.length,
/// itemBuilder: (context, index) {
/// final item = parallaxCardItemsList[index];
/// final pageVisibility =
/// visibilityResolver.resolvePageVisibility(index);
/// return ParallaxCardsWidget(
/// item: item,
/// pageVisibility: pageVisibility,
/// );
/// },
/// );
/// },
/// ),
/// ```
final PageTransformer parallexCardPageTransformer;
_ExtendedNavigationBarScaffoldState createState() =>
_ExtendedNavigationBarScaffoldState();
}
class _ExtendedNavigationBarScaffoldState
extends State<ExtendedNavigationBarScaffold> with TickerProviderStateMixin {
CurvedAnimation curve;
//*Parallex Animation Code
AnimationController animationControllerBottomBarParallex;
var offsetBottomBarParallex = 0.0;
get currentBottomBarCenterPercentage => max(
0.0,
min(
1.0,
offsetBottomBarParallex /
(bottomBarExpandedHeight - bottomBarOriginalHeight),
),
);
bool isBottomBarParallexOpen = false;
Animation<double> animationParallex;
void onParallexVerticalDragUpdate(details) {
offsetBottomBarParallex -= details.delta.dy;
if (offsetBottomBarParallex > bottomBarExpandedHeight) {
offsetBottomBarParallex = bottomBarExpandedHeight;
} else if (offsetBottomBarParallex < 0) {
offsetBottomBarParallex = 0;
}
if (widget.currentBottomBarCenterPercent != null)
widget.currentBottomBarCenterPercent(currentBottomBarCenterPercentage);
setState(() {});
}
void animateBottomBarParallex(bool open) {
// if (isBottomBarParallexOpen) {
// isBottomBarParallexOpen = false;
// }
if (isBottomBarMoreOpen) {
animateBottomBarMore(!isBottomBarMoreOpen);
}
if (isBottomBarSearchOpen) {
animateBottomBarSearch(!isBottomBarSearchOpen);
}
animationControllerBottomBarParallex = AnimationController(
duration: Duration(
milliseconds: 1 +
(800 *
(isBottomBarParallexOpen
? currentBottomBarCenterPercentage
: (1 - currentBottomBarCenterPercentage)))
.toInt()),
vsync: this);
curve = CurvedAnimation(
parent: animationControllerBottomBarParallex, curve: Curves.ease);
animationParallex = Tween(
begin: offsetBottomBarParallex,
end: open ? bottomBarExpandedHeight : 0.0)
.animate(curve)
..addListener(() {
setState(() {
offsetBottomBarParallex = animationParallex.value;
});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
isBottomBarParallexOpen = open;
}
});
animationControllerBottomBarParallex.forward();
}
//* More Button Animation Code
AnimationController animationControllerBottomBarMore;
var offsetBottomBarMore = 0.0;
get currentBottomBarMorePercentage => max(
0.0,
min(
1.0,
offsetBottomBarMore /
(bottomBarExpandedHeight - bottomBarOriginalHeight),
),
);
bool isBottomBarMoreOpen = false;
Animation<double> animationMore;
void onMoreVerticalDragUpdate(details) {
offsetBottomBarMore -= details.delta.dy;
if (offsetBottomBarMore > bottomBarExpandedHeight) {
offsetBottomBarMore = bottomBarExpandedHeight;
} else if (offsetBottomBarMore < 0) {
offsetBottomBarMore = 0;
}
if (widget.currentBottomBarMorePercent != null)
widget.currentBottomBarMorePercent(currentBottomBarMorePercentage);
setState(() {});
}
void animateBottomBarMore(bool open) {
if (isBottomBarSearchOpen) {
animateBottomBarSearch(!isBottomBarSearchOpen);
}
if (isBottomBarParallexOpen) {
animateBottomBarParallex(!isBottomBarParallexOpen);
}
animationControllerBottomBarMore = AnimationController(
duration: Duration(
milliseconds: 1 +
(1000 *
(isBottomBarMoreOpen
? currentBottomBarMorePercentage
: (1 - currentBottomBarMorePercentage)))
.toInt()),
vsync: this);
curve = CurvedAnimation(
parent: animationControllerBottomBarMore, curve: Curves.ease);
animationMore = Tween(
begin: offsetBottomBarMore,
end: open ? bottomBarExpandedHeight : 0.0)
.animate(curve)
..addListener(
() {
setState(() {
offsetBottomBarMore = animationMore.value;
});
},
)
..addStatusListener(
(status) {
if (status == AnimationStatus.completed) {
isBottomBarMoreOpen = open;
}
},
);
animationControllerBottomBarMore.forward();
}
//* Search Button Animation(bring center button downwards) *//
AnimationController animationControllerBottomBarSearch;
var offsetBottomBarSearch = 0.0;
get currentBottomBarSearchPercentage => max(
0.0,
min(
1.0,
offsetBottomBarSearch / 28.0,
),
);
bool isBottomBarSearchOpen = false;
Animation<double> animationSearch;
void onSearchVerticalDragUpdate(details) {
offsetBottomBarSearch -= details.delta.dy;
if (offsetBottomBarSearch > bottomBarExpandedHeight) {
offsetBottomBarSearch = bottomBarExpandedHeight;
} else if (offsetBottomBarSearch < 0) {
offsetBottomBarSearch = 0;
}
if (widget.currentBottomBarSearchPercent != null)
widget.currentBottomBarSearchPercent(currentBottomBarSearchPercentage);
setState(() {});
}
void animateBottomBarSearch(bool open) {
if (isBottomBarParallexOpen) {
animateBottomBarParallex(!isBottomBarParallexOpen);
}
if (isBottomBarMoreOpen) {
animateBottomBarMore(!isBottomBarMoreOpen);
}
animationControllerBottomBarSearch = AnimationController(
duration: Duration(
milliseconds: 1 +
(1000 *
(isBottomBarSearchOpen
? currentBottomBarSearchPercentage
: (1 - currentBottomBarSearchPercentage)))
.toInt()),
vsync: this);
curve = CurvedAnimation(
parent: animationControllerBottomBarSearch, curve: Curves.ease);
animationSearch =
Tween(begin: offsetBottomBarSearch, end: open ? 28.0 : 0.0)
.animate(curve)
..addListener(
() {
setState(() {
offsetBottomBarSearch = animationSearch.value;
});
},
)
..addStatusListener(
(status) {
if (status == AnimationStatus.completed) {
isBottomBarSearchOpen = open;
}
},
);
animationControllerBottomBarSearch.forward();
if (widget.currentBottomBarSearchPercent != null)
widget.currentBottomBarSearchPercent(currentBottomBarSearchPercentage);
}
@override
Widget build(BuildContext context) {
if (widget.currentExternalAnimationPercentage > 0.30) {
if (isBottomBarParallexOpen) {
animateBottomBarParallex(false);
}
if (isBottomBarMoreOpen) {
animateBottomBarMore(false);
}
}
return Scaffold(
appBar: widget.floatingAppBar ? null : widget.appBar,
// backgroundColor: ,
extendBody: true,
body: Stack(
children: <Widget>[
widget.body,
// widget.appBar
widget.floatingAppBar
? Positioned(
top: 0,
left: 10,
right: 10,
child: SafeArea(
// height: 50,
child: widget.appBar,
),
)
: Container(),
_CustomBottomNavigationBar(
moreButtons: widget.moreButtons,
searchWidget: widget.searchWidget,
parallexCardPageTransformer: widget.parallexCardPageTransformer,
elevation: widget.elevation,
navBarColor: widget.navBarColor,
navBarIconColor: widget.navBarIconColor,
//* "Parallex" Animation
animateBottomBarParallex: animateBottomBarParallex,
currentBottomBarParallexPercentage:
currentBottomBarCenterPercentage,
isBottomBarParallexOpen: isBottomBarParallexOpen,
onTap: widget.onTap,
onParallexVerticalDragUpdate: onParallexVerticalDragUpdate,
onParallexPanDown: () =>
animationControllerBottomBarParallex?.stop(),
//* "More" Animation
animateBottomBarMore: animateBottomBarMore,
currentBottomBarMorePercentage: currentBottomBarMorePercentage,
isBottomBarMoreOpen: isBottomBarMoreOpen,
onMoreVerticalDragUpdate: onMoreVerticalDragUpdate,
onMorePanDown: () => animationControllerBottomBarMore?.stop(),
//* Search
animateBottomBarSearch: animateBottomBarSearch,
currentBottomBarSearchPercentage: currentBottomBarSearchPercentage,
isBottomBarSearchOpen: isBottomBarSearchOpen,
onSearchVerticalDragUpdate: onSearchVerticalDragUpdate,
onSearchPanDown: () => animationControllerBottomBarSearch?.stop(),
),
],
),
);
}
@override
void dispose() {
super.dispose();
animationControllerBottomBarParallex?.dispose();
animationControllerBottomBarMore?.dispose();
animationControllerBottomBarSearch?.dispose();
}
}
class _CustomBottomNavigationBar extends StatelessWidget {
_CustomBottomNavigationBar({
Key key,
this.onTap,
this.searchWidget,
this.parallexCardPageTransformer,
this.elevation,
this.navBarColor,
this.navBarIconColor,
//Parallex
this.animateBottomBarParallex,
this.currentBottomBarParallexPercentage,
this.isBottomBarParallexOpen,
this.onParallexPanDown,
this.onParallexVerticalDragUpdate,
//更多
this.animateBottomBarMore,
this.currentBottomBarMorePercentage,
this.isBottomBarMoreOpen,
this.onMorePanDown,
this.onMoreVerticalDragUpdate,
this.moreButtons,
//搜索
this.animateBottomBarSearch,
this.currentBottomBarSearchPercentage,
this.isBottomBarSearchOpen,
this.onSearchPanDown,
this.onSearchVerticalDragUpdate,
}) : super(key: key);
final Function(int) onTap;
final Widget searchWidget;
final List<MoreButtonModel> moreButtons;
final double currentBottomBarParallexPercentage;
final Function(bool) animateBottomBarParallex;
final bool isBottomBarParallexOpen;
final Function(DragUpdateDetails) onParallexVerticalDragUpdate;
final Function() onParallexPanDown;
final double currentBottomBarMorePercentage;
final Function(bool) animateBottomBarMore;
final bool isBottomBarMoreOpen;
final Function(DragUpdateDetails) onMoreVerticalDragUpdate;
final Function() onMorePanDown;
final double currentBottomBarSearchPercentage;
final Function(bool) animateBottomBarSearch;
final bool isBottomBarSearchOpen;
final Function(DragUpdateDetails) onSearchVerticalDragUpdate;
final Function() onSearchPanDown;
final PageTransformer parallexCardPageTransformer;
final double elevation;
final Color navBarColor;
final Color navBarIconColor;
@override
Widget build(BuildContext context) {
return Positioned(
bottom: 10,
left: 10,
right: 10,
// alignment: Alignment.bottomCenter,
child: Card(
// color: Colors.transparent,
color: Colors.transparent,
elevation: elevation ?? 10,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
),
child: SizedBox(
height: bottomBarOriginalHeight +
//* Increase height when parallex card is expanded *//
(bottomBarExpandedHeight - bottomBarOriginalHeight) *
currentBottomBarParallexPercentage +
//* Increase height when More Button is expanded *//
(bottomBarExpandedHeight) * currentBottomBarMorePercentage +
//* Increase Height For Search Bar */
(bottomBarExpandedHeight) * currentBottomBarSearchPercentage,
child: Stack(
children: <Widget>[
_buildBackgroundForParallexCard(context),
_builtSearchBar(),
_buildOtherButtons(context),
_buildParallexCards(context),
// : Container(),
_buildMoreExpandedCard(context),
_buildCenterButton(context),
],
),
),
),
);
}
Widget _builtSearchBar() {
return Positioned(
left: 50 - 50 * currentBottomBarSearchPercentage,
right: 50 - 50 * currentBottomBarSearchPercentage,
bottom: 0 + 55 * currentBottomBarSearchPercentage,
child: Opacity(
opacity: currentBottomBarSearchPercentage,
child: searchWidget ?? Container(),
),
);
}
Widget _buildMoreExpandedCard(BuildContext context) {
return Positioned(
left: 0,
right: 0,
bottom: 60,
child: Opacity(
opacity: currentBottomBarMorePercentage,
child: Container(
height: (bottomBarExpandedHeight - bottomBarVisibleHeight - 10) *
currentBottomBarMorePercentage,
// color: Colors.blue,
child: Stack(
// alignment: Alignment.center,
children: <Widget>[
Align(
alignment: Alignment.topLeft,
child: MoreButtons(
currentBottomBarMorePercentage:
currentBottomBarMorePercentage,
model: moreButtons[0],
),
),
Align(
alignment: Alignment.topCenter,
child: MoreButtons(
currentBottomBarMorePercentage:
currentBottomBarMorePercentage,
model: moreButtons[1],
),
),
Align(
alignment: Alignment.topRight,
child: MoreButtons(
currentBottomBarMorePercentage:
currentBottomBarMorePercentage,
model: moreButtons[2],
),
),
Align(
alignment: Alignment.centerLeft,
child: MoreButtons(
currentBottomBarMorePercentage:
currentBottomBarMorePercentage,
model: moreButtons[3],
),
),
Align(
alignment: Alignment.center,
child: MoreButtons(
currentBottomBarMorePercentage:
currentBottomBarMorePercentage,
model: moreButtons[4],
),
),
Align(
alignment: Alignment.centerRight,
child: MoreButtons(
currentBottomBarMorePercentage:
currentBottomBarMorePercentage,
model: moreButtons[5],
),
),
Align(
alignment: Alignment.bottomLeft,
child: MoreButtons(
currentBottomBarMorePercentage:
currentBottomBarMorePercentage,
model: moreButtons[6],
),
),
Align(
alignment: Alignment.bottomCenter,
child: MoreButtons(
currentBottomBarMorePercentage:
currentBottomBarMorePercentage,
model: moreButtons[7],
),
),
Align(
alignment: Alignment.bottomRight,
child: MoreButtons(
currentBottomBarMorePercentage:
currentBottomBarMorePercentage,
model: moreButtons[8],
),
),
],
),
),
),
);
}
Widget _buildOtherButtons(BuildContext context) {
return Align(
alignment: Alignment.bottomCenter,
child: Container(
padding: EdgeInsets.only(left: 0, right: 0),
height: bottomBarVisibleHeight +
(bottomBarExpandedHeight - 0) * currentBottomBarMorePercentage,
decoration: BoxDecoration(
color: Theme.of(context).canvasColor,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[_buildMoreButton(), _buildSearchButton()],
),
),
);
}
Widget _buildSearchButton() {
return Expanded(
child: Container(
height: bottomBarVisibleHeight,
child: GestureDetector(
onPanDown: (_) => onSearchPanDown,
onVerticalDragUpdate: onSearchVerticalDragUpdate,
onVerticalDragEnd: (_) {
_dispatchBottomBarSearchOffset();
},
onVerticalDragCancel: () {
_dispatchBottomBarSearchOffset();
},
child: FloatingActionButton(
backgroundColor: navBarColor,
elevation: 0,
heroTag: 'sdansiux',
// padding: EdgeInsets.only(left: 35),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(20),
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(
EvaIcons.search,
size: 30,
color: navBarIconColor,
),
Text(
'搜索',
style: ktitleStyle.copyWith(
fontSize: 13,
color: navBarIconColor,
),
)
],
),
onPressed: () {
if (onTap != null) onTap(2);
animateBottomBarSearch(!isBottomBarSearchOpen);
},
),
),
),
);
}
Widget _buildMoreButton() {
return Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
height: bottomBarVisibleHeight,
child: GestureDetector(
onPanDown: (_) => onMorePanDown,
onVerticalDragUpdate: onMoreVerticalDragUpdate,
onVerticalDragEnd: (_) {
_dispatchBottomBarMoreOffset();
},
onVerticalDragCancel: () {
_dispatchBottomBarMoreOffset();
},
child: FloatingActionButton(
backgroundColor: navBarColor,
heroTag: 'dsc',
// padding: EdgeInsets.only(right: 35),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(20),
),
),
onPressed: () {
if (onTap != null) onTap(0);
animateBottomBarMore(!isBottomBarMoreOpen);
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Transform(
alignment: FractionalOffset.center,
transform: new Matrix4.identity()
..rotateZ(180 *
currentBottomBarMorePercentage *
3.1415927 /
180),
child: Icon(
EvaIcons.arrowCircleUpOutline,
size: 30,
color: navBarIconColor,
),
),
Text(
'更多',
style: ktitleStyle.copyWith(
fontSize: 13,
color: navBarIconColor,
),
),
],
),
),
),
),
SizedBox(
height: (bottomBarExpandedHeight - bottomBarVisibleHeight) *
currentBottomBarMorePercentage,
// child: Container(
// color: Colors.red,
// ),
)
],
),
);
}
Widget _buildBackgroundForParallexCard(BuildContext context) {
return Positioned(
top: 25,
bottom: 55,
left: 0,
right: 0,
child: Container(
height: (bottomBarExpandedHeight - bottomBarOriginalHeight) *
currentBottomBarParallexPercentage,
color: Theme.of(context).canvasColor,
),
);
}
Widget _buildCenterButton(BuildContext context) {
return Positioned(
left: currentBottomBarParallexPercentage > 0.0
? 0
: (MediaQuery.of(context).size.width / 2) - 50,
right: currentBottomBarParallexPercentage > 0.0
? 0
: (MediaQuery.of(context).size.width / 2) - 50,
// top: 0 +
// (28 + bottomBarExpandedHeight) * currentBottomBarMorePercentage +
// (28 + 350) * currentBottomBarSearchPercentage,
bottom: 30 +
(bottomBarExpandedHeight - bottomBarOriginalHeight) *
currentBottomBarParallexPercentage -
28 * currentBottomBarMorePercentage -
28 * currentBottomBarSearchPercentage,
// alignment: Alignment.topCenter,
child: Container(
height: 50,
// color: Colors.red,
child: Column(
children: <Widget>[
SizedBox(
height: 50,
width: 50,
child: EaseInWidget(
onTap: () {
if (onTap != null) onTap(1);
animateBottomBarParallex(!isBottomBarParallexOpen);
},
child: GestureDetector(
onPanDown: (_) => onParallexPanDown,
onVerticalDragUpdate: onParallexVerticalDragUpdate,
onVerticalDragEnd: (_) {
_dispatchBottomBarParallexOffset();
},
onVerticalDragCancel: () {
_dispatchBottomBarParallexOffset();
},
child: FloatingActionButton(
backgroundColor: Colors.black,
heroTag: 'adaojd',
elevation: 0,
onPressed: null,
child: Icon(
Icons.view_column,
color: Theme.of(context).canvasColor,
),
),
),
),
),
// SizedBox(
// height: 300 * currentBottomBarParallexPercentage,
// // child: Container(
// // color: Colors.red,
// // ),
// )
],
),
),
);
}
Widget _buildParallexCards(BuildContext context) {
return Positioned(
bottom: 30, // * currentBottomBarParallexPercentage,
left: 0,
right: 0,
child: Container(
// height: (bottomBarExpandedHeight - bottomBarVisibleHeight - 10) *
// currentBottomBarParallexPercentage,
child: Padding(
padding: EdgeInsets.only(bottom: 25.0),
child: SizedBox.fromSize(
size: Size.fromHeight(210.0 * currentBottomBarParallexPercentage),
child: parallexCardPageTransformer,
),
),
),
);
}
void _dispatchBottomBarParallexOffset() {
if (!isBottomBarParallexOpen) {
if (currentBottomBarParallexPercentage < 0.3) {
animateBottomBarParallex(false);
} else {
animateBottomBarParallex(true);
}
} else {
if (currentBottomBarParallexPercentage > 0.6) {
animateBottomBarParallex(true);
} else {
animateBottomBarParallex(false);
}
}
}
void _dispatchBottomBarMoreOffset() {
if (!isBottomBarMoreOpen) {
if (currentBottomBarMorePercentage < 0.3) {
animateBottomBarMore(false);
} else {
animateBottomBarMore(true);
}
} else {
if (currentBottomBarMorePercentage > 0.6) {
animateBottomBarMore(true);
} else {
animateBottomBarMore(false);
}
}
}
void _dispatchBottomBarSearchOffset() {
if (!isBottomBarSearchOpen) {
if (currentBottomBarSearchPercentage < 0.2) {
animateBottomBarSearch(false);
} else {
animateBottomBarSearch(true);
}
} else {
if (currentBottomBarSearchPercentage > 0.6) {
animateBottomBarSearch(true);
} else {
animateBottomBarSearch(false);
}
}
}
}
class MoreButtons extends StatelessWidget {
const MoreButtons({
Key key,
@required this.currentBottomBarMorePercentage,
@required this.model,
}) : super(key: key);
final double currentBottomBarMorePercentage;
final MoreButtonModel model;
@override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width * 0.3,
height: (bottomBarExpandedHeight - bottomBarVisibleHeight) * 0.3,
// color: Colors.red,
child: model == null
? SizedBox()
: FlatButton(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(
model.icon,
size: MediaQuery.of(context).size.width *
0.33 *
currentBottomBarMorePercentage /
3,
// size: 45,
),
// SizedBox(
// height: 5,
// ),
Text(
model.label,
textAlign: TextAlign.center,
style: ktitleStyle.copyWith(
// fontSize: 14,
fontSize: MediaQuery.of(context).size.width *
0.1 *
currentBottomBarMorePercentage /
3,
),
)
],
),
onPressed: model.onTap,
),
);
}
}
page_transformer.dart代码如下
part of navbar_scaffold;
//函数别名,给某一种特定的函数类型起了一个名字,可以认为是一个类型的别名,
typedef PageView PageViewBuilder(BuildContext context, PageVisibilityResolver visibilityResolver);
class PageVisibilityResolver {
PageVisibilityResolver({
ScrollMetrics metrics,
double viewPortFraction,
}) : this._pageMetrics = metrics,
this._viewPortFraction = viewPortFraction;
final ScrollMetrics _pageMetrics;
final double _viewPortFraction;
PageVisibility resolvePageVisibility(int pageIndex) {
final double pagePosition = _calculatePagePosition(pageIndex);
final double visiblePageFraction =
_calculateVisiblePageFraction(pageIndex, pagePosition);
return PageVisibility(
visibleFraction: visiblePageFraction,
pagePosition: pagePosition,
);
}
double _calculateVisiblePageFraction(int index, double pagePosition) {
if (pagePosition > -1.0 && pagePosition <= 1.0) {
return 1.0 - pagePosition.abs();
}
return 0.0;
}
double _calculatePagePosition(int index) {
final double viewPortFraction = _viewPortFraction ?? 1.0;
final double pageViewWidth =
(_pageMetrics?.viewportDimension ?? 1.0) * viewPortFraction;
final double pageX = pageViewWidth * index;
final double scrollX = (_pageMetrics?.pixels ?? 0.0);
final double pagePosition = (pageX - scrollX) / pageViewWidth;
final double safePagePosition = !pagePosition.isNaN ? pagePosition : 0.0;
if (safePagePosition > 1.0) {
return 1.0;
} else if (safePagePosition < -1.0) {
return -1.0;
}
return safePagePosition;
}
}
class PageVisibility {
PageVisibility({
@required this.visibleFraction,
@required this.pagePosition,
});
final double visibleFraction;
final double pagePosition;
}
class PageTransformer extends StatefulWidget {
PageTransformer({
@required this.pageViewBuilder,
});
final PageViewBuilder pageViewBuilder;
@override
_PageTransformerState createState() => _PageTransformerState();
}
class _PageTransformerState extends State<PageTransformer> {
PageVisibilityResolver _visibilityResolver;
@override
Widget build(BuildContext context) {
final pageView = widget.pageViewBuilder(
context, _visibilityResolver ?? PageVisibilityResolver());
final controller = pageView.controller;
final viewPortFraction = controller.viewportFraction;
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
setState(() {
_visibilityResolver = PageVisibilityResolver(
metrics: notification.metrics,
viewPortFraction: viewPortFraction,
);
});
},
child: pageView,
);
}
}
ParallexCardWidet.dart代码如下
part of navbar_scaffold;
class ParallaxCardItem {
ParallaxCardItem({
this.title,
this.body,
this.background,
this.data,
});
final String title;
final String body;
final Widget background;
final dynamic data;
}
class ParallaxCardsWidget extends StatelessWidget {
ParallaxCardsWidget({
@required this.item,
@required this.pageVisibility,
});
final ParallaxCardItem item;
final PageVisibility pageVisibility;
Widget _applyTextEffects({
@required double translationFactor,
@required Widget child,
}) {
final double xTranslation = pageVisibility.pagePosition * translationFactor;
return Opacity(
opacity: pageVisibility.visibleFraction,
child: Transform(
alignment: FractionalOffset.topLeft,
transform: Matrix4.translationValues(
xTranslation,
0.0,
0.0,
),
child: child,
),
);
}
_buildTextContainer(BuildContext context) {
var categoryText = _applyTextEffects(
translationFactor: 300.0,
child: Padding(
padding: EdgeInsets.all(3.0),
child: Text(
item.body,
style: ktitleStyle.copyWith(
color: Colors.white,
// fontWeight: FontWeight.w600,
fontSize: 22.0,
),
textAlign: TextAlign.left,
),
),
);
var titleText = _applyTextEffects(
translationFactor: 200.0,
child: Padding(
padding: EdgeInsets.all(3.0),
child: Text(
item.title,
style: ksubtitleStyle.copyWith(
color: Colors.white,
// fontWeight: FontWeight.w700,
fontSize: 20.0,
),
textAlign: TextAlign.left,
),
),
);
return Positioned(
// top: 5,
bottom: 5.0,
left: 10.0,
// right: 10.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
categoryText,
titleText,
],
),
);
}
@override
Widget build(BuildContext context) {
var imageOverlayGradient = DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black12,
// Colors.transparent,
// Colors.black12,
// Colors.black26,
// Colors.black38,
Colors.black87,
// Colors.black,
],
),
),
);
return Padding(
padding: EdgeInsets.symmetric(
vertical: 20.0,
horizontal: 5.0,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: Material(
shadowColor: Theme.of(context).accentColor,
elevation: 10,
type: MaterialType.card,
child: Stack(
fit: StackFit.expand,
children: [
item.background,
// centerMarker,
imageOverlayGradient,
_buildTextContainer(context),
],
),
),
),
);
}
}
最后pubspec.yaml文件如下
name: navbar_scaffold
description: A new Flutter application.
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
eva_icons_flutter: ^2.0.0
#使用自定义图标
flutter_vector_icons: ^0.2.1
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
总结