flutter 动画_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展示

3e3ded97d72be9d66f67eefe5780e311.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 { @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( 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到项目可以直接使用。

b82a3b729321c0bb5c26fbdac6a316cc.png

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 createState() => _EaseInWidgetState();}//关键字 with 就是dart当中到 mixin,你需要使用with关键字,后跟一个或多个mixin的名//mixin 的中文意思是混入,就是在类中混入其他功能。//复用代码,mixin 最早的根源来自于Lisp//在面向对象的语言中,mixin 类是一个可以把自己的方法提供给其他类使用,但却不需要成为其他类的父类。//mixin 是要通过非继承的方式来复用类中的代码。class _EaseInWidgetState extends State with TickerProviderStateMixin { AnimationController controller; Animation 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 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 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 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 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 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.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 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: [ _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: [ 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: [_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: [ 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: [ 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: [ 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: [ 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: [ 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 { PageVisibilityResolver _visibilityResolver; @override Widget build(BuildContext context) { final pageView = widget.pageViewBuilder( context, _visibilityResolver ?? PageVisibilityResolver()); final controller = pageView.controller; final viewPortFraction = controller.viewportFraction; return NotificationListener( 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_scaffolddescription: A new Flutter application.version: 1.0.0+1environment: 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.1dev_dependencies: flutter_test: sdk: flutterflutter: uses-material-design: true

总结

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值