玖叶教程网

前端编程开发入门

Flutter脚手架自定义 漂亮的动画实现 建议收藏 Android iOS 供用

今天文章来到有点迟,今天周五,一大早公司就开会,所以来晚了,今天到内容给大家放大招,一个漂亮到自定义 Scaffold到实现,还有漂亮到动画,建议收藏以后用。

本头条核心宗旨

欢迎来到技术刚刚好头条,本头条是个人维护,每天至少更新一篇Flutter技术文章,实时为大家播报Flutter最新消息。如果你刚好也在关注Flutter这门技术,那就跟我一起学习进步吧,你的赞,收藏,转发是对我个人最大的支持,维护不易,欢迎关注。

技术刚刚好经历

近几年,移动端跨平台开发技术层出不穷,从Facebook家的ReactNative,到阿里家WEEX,前端技术在移动端跨平台开发中大展身手,技术刚刚好作为一名Android开发,经历了从Reactjs到Vuejs的不断学习。而在2018年,我们的主角变成了Flutter,这是Goolge开源的一个移动端跨平台解决方案,可以快速开发精美的移动App。希望跟大家一起学习,一起进步!

本文核心要点

dart到封装思路,library的使用方法,part的功能介绍和使用。自定义图标到使用,Flutter当中PageView到实现,CurvedAnimation动画到使用,半圆角的矩形边框RoundedRectangleBorder学习。part of使用说明,关键字 with学习。

大概就是上面知识点学习,我相信远远不止这些,其实学习代码开发最重要到核心思想还是动手,本文会把所有到代码都复制进来,也欢迎大家自己动手敲一遍代码,你会有很大到收获,谢谢大家。

整个项目到运行效果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框架

library指令可以帮助创建一个模块化的,可共享的代码库。库不仅提供了API,还提供隐私单元:以下划线(_)开头的标识符只对内部库可见。每个Dartapp就是一个库,即使它不使用库指令。

//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个类到结构查看

这也算是核心实现类了,可以具体代码请看下面,我一一列出,自己copy到项目可以直接使用。

scaffoldlib5

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文件如下

pubspec文件只用到了#使用自定义图标flutter_vector_icons: ^0.2.1

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

总结

希望大家能够希望,本文耗时几个小时弄出来的,刚好写完去吃饭了。

谢谢观看技术刚刚好头条文章,本头条是个人维护,每天至少更新一篇Flutter技术文章,实时为大家播报Flutter最新消息。如果你刚好也在关注Flutter这门技术,那就跟我一起学习进步吧,你的赞,收藏,转发是对我个人最大的支持,维护不易,欢迎关注。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言