新沂网站制作,vue反向代理天地图地址,如何做网站优化 纯外链,网上购物商城开发目录 1.动画简介2.动画实现和监听3. 自定义路由切换动画4. Hero动画5.交织动画6.动画切换7.Flutter预置的动画过渡组件自定义组件1.简介2.组合组件3.CustomPaint 和 RenderObject 1.动画简介
Animation、Curve、Controller、Tween这四个角色#xff0c;它们一起配合来完成一个… 目录 1.动画简介2.动画实现和监听3. 自定义路由切换动画4. Hero动画5.交织动画6.动画切换7.Flutter预置的动画过渡组件自定义组件1.简介2.组合组件3.CustomPaint 和 RenderObject 1.动画简介
Animation、Curve、Controller、Tween这四个角色它们一起配合来完成一个完整动画
Animation Animation是抽象类和UI渲染没有关系功能是保存动画的插值和状态比较常用的是Animation addListener帧监听器中最常见的行为是改变状态后调用setState()来触发UI重建 addStatusListener动画开始、结束、正向或反向见AnimationStatus定义时会调用状态改变的监听器。Curve 动画过程可以是匀速的、匀加速的或者先加速后减速等。Flutter中通过Curve曲线来描述动画过程我们把匀速动画称为线性的(Curves.linear)而非匀速动画称为非线性的。
final CurvedAnimation curve CurvedAnimation(parent: controller, curve: Curves.easeIn);Curves曲线 动画过程 linear 匀速的 decelerate 匀减速 ease 开始加速后面减速 easeIn 开始慢后面快 easeOut 开始快后面慢 easeInOut 开始慢然后加速最后再减速
也可以自定义一个正弦曲线
class ShakeCurve extends Curve {overridedouble transform(double t) {return math.sin(t * math.PI * 2);}
}AnimationController AnimationController用于控制动画它包含动画的启动forward()、停止stop() 、反向播放 reverse()等
final AnimationController controller AnimationController( duration: const Duration(milliseconds: 2000), lowerBound: 10.0,upperBound: 20.0,vsync: this
);Tween 默认AnimationController对象值的范围是[0.01.0]但可以使用Tween来改变范围 例如像下面示例Tween生成[-200.00.0]的值
final Tween doubleTween Tweendouble(begin: -200.0, end: 0.0);
完整示例 以下示例构建了一个控制器、一条曲线和一个 Tween
final AnimationController controller AnimationController(duration: const Duration(milliseconds: 500), vsync: this,
);
final Animation curve CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animationint alpha IntTween(begin: 0, end: 255).animate(curve);线性插值lerp函数
//a 为起始颜色b为终止颜色t为当前动画的进度[0,1]
Color.lerp(a, b, t);2.动画实现和监听
AnimatedBuilder可以封装常见的过渡效果来复用动画
class GrowTransition extends StatelessWidget {const GrowTransition({Key? key,required this.animation,this.child,}) : super(key: key);final Widget? child;final Animationdouble animation;overrideWidget build(BuildContext context) {return Center(child: AnimatedBuilder(animation: animation,builder: (BuildContext context, child) {return SizedBox(height: animation.value,width: animation.value,child: child,);},child: child,),);}
}...
Widget build(BuildContext context) {return GrowTransition(child: Image.asset(images/avatar.png), animation: animation,);
}Flutter中正是通过这种方式封装了很多动画如FadeTransition、ScaleTransition、SizeTransition等很多时候都可以复用这些预置的过渡类
Animation的addStatusListener()方法来添加动画状态改变监听器。Flutter中有四种动画状态在AnimationStatus枚举类中定义 dismissed 动画在起始点停止 forward 动画正在正向执行 reverse 动画正在反向执行 completed 动画在终点停止
3. 自定义路由切换动画
无论是MaterialPageRoute、CupertinoPageRoute还是PageRouteBuilder它们都继承自PageRoute
MaterialPageRoute组件它可以使用和平台风格一致的路由切换动画如在iOS上会左右滑动切换而在Android上会上下滑动切换
CupertinoPageRoute是Cupertino组件库提供的iOS风格的路由切换组件它实现的就是左右滑动切换。
自定义切换动画优先考虑使用PageRouteBuilder
Navigator.push(context,PageRouteBuilder(transitionDuration: Duration(milliseconds: 500), //动画时间为500毫秒pageBuilder: (BuildContext context, Animation animation,Animation secondaryAnimation) {return FadeTransition(//使用渐隐渐入过渡,opacity: animation,child: PageB(), //路由B);},),
);但是有些时候PageRouteBuilder是不能满足需求的例如在应用过渡动画时我们需要读取当前路由的一些属性这时就只能通过继承PageRoute的方式了
override
Widget buildTransitions(BuildContext context, Animationdouble animation,Animationdouble secondaryAnimation, Widget child) {//当前路由被激活是打开新路由if(isActive) {return FadeTransition(opacity: animation,child: builder(context),);}else{//是返回则不应用过渡动画return Padding(padding: EdgeInsets.zero);}
}4. Hero动画
在Flutter中将图片从一个路由“飞”到另一个路由称为hero动画
例如A路由有一个圆形用户头像点击后跳到B路由可以查看大图
class HeroAnimationRouteA extends StatelessWidget {const HeroAnimationRouteA({Key? key}) : super(key: key);overrideWidget build(BuildContext context) {return Container(alignment: Alignment.topCenter,child: Column(children: Widget[InkWell(child: Hero(tag: avatar, //唯一标记前后两个路由页Hero的tag必须相同child: ClipOval(child: Image.asset(imgs/avatar.png,width: 50.0,),),),onTap: () {//打开B路由Navigator.push(context, PageRouteBuilder(pageBuilder: (BuildContext context,animation,secondaryAnimation,) {return FadeTransition(opacity: animation,child: Scaffold(appBar: AppBar(title: const Text(原图),),body: const HeroAnimationRouteB(),),);},));},),const Padding(padding: EdgeInsets.only(top: 8.0),child: Text(点击头像),)],),);}
}class HeroAnimationRouteB extends StatelessWidget {overrideWidget build(BuildContext context) {return Center(child: Hero(tag: avatar, //唯一标记前后两个路由页Hero的tag必须相同child: Image.asset(imgs/avatar.png),),);}
}实现 Hero 动画只需要用Hero组件将要共享的 widget 包装起来并提供一个相同的 tag 即可
5.交织动画
比如有一个柱状图需要在高度增长的同时改变颜色等到增长到最大高度后我们需要在X轴上平移一段距离。可以发现上述场景在不同阶段包含了多种动画要实现这种效果使用交织动画Stagger Animation会非常简单 实现步骤 1.要创建交织动画需要使用多个动画对象Animation。 2.一个AnimationController控制所有的动画对象。 3.给每一个动画对象指定时间间隔Interval
class StaggerAnimation extends StatelessWidget {StaggerAnimation({Key? key,required this.controller,}) : super(key: key) {//高度动画height Tweendouble(begin: .0,end: 300.0,).animate(CurvedAnimation(parent: controller,curve: const Interval(0.0, 0.6, //间隔前60%的动画时间curve: Curves.ease,),),);color ColorTween(begin: Colors.green,end: Colors.red,).animate(CurvedAnimation(parent: controller,curve: const Interval(0.0, 0.6, //间隔前60%的动画时间curve: Curves.ease,),),);padding TweenEdgeInsets(begin: const EdgeInsets.only(left: .0),end: const EdgeInsets.only(left: 100.0),).animate(CurvedAnimation(parent: controller,curve: const Interval(0.6, 1.0, //间隔后40%的动画时间curve: Curves.ease,),),);}late final Animationdouble controller;late final Animationdouble height;late final AnimationEdgeInsets padding;late final AnimationColor? color;Widget _buildAnimation(BuildContext context, child) {return Container(alignment: Alignment.bottomCenter,padding: padding.value,child: Container(color: color.value,width: 50.0,height: height.value,),);}overrideWidget build(BuildContext context) {return AnimatedBuilder(builder: _buildAnimation,animation: controller,);}
}StaggerAnimation中定义了三个动画分别是对Container的height、color、padding属性设置的动画然后通过Interval来为每个动画指定在整个动画过程中的起始点和终点
使用
class StaggerRoute extends StatefulWidget {override_StaggerRouteState createState() _StaggerRouteState();
}class _StaggerRouteState extends StateStaggerRoutewith TickerProviderStateMixin {late AnimationController _controller;overridevoid initState() {super.initState();_controller AnimationController(duration: const Duration(milliseconds: 2000),vsync: this,);}_playAnimation() async {try {//先正向执行动画await _controller.forward().orCancel;//再反向执行动画await _controller.reverse().orCancel;} on TickerCanceled {//捕获异常。可能发生在组件销毁时计时器会被取消。}}overrideWidget build(BuildContext context) {return Center(child: Column(children: [ElevatedButton(onPressed: () _playAnimation(),child: Text(start animation),),Container(width: 300.0,height: 300.0,decoration: BoxDecoration(color: Colors.black.withOpacity(0.1),border: Border.all(color: Colors.black.withOpacity(0.5),),),//调用我们定义的交错动画Widgetchild: StaggerAnimation(controller: _controller),),],),);}
}6.动画切换
AnimatedSwitcher组件它定义了一种通用的UI切换抽象
const AnimatedSwitcher({Key? key,this.child,required this.duration, // 新child显示动画时长this.reverseDuration,// 旧child隐藏的动画时长this.switchInCurve Curves.linear, // 新child显示的动画曲线this.switchOutCurve Curves.linear,// 旧child隐藏的动画曲线this.transitionBuilder AnimatedSwitcher.defaultTransitionBuilder, // 动画构建器this.layoutBuilder AnimatedSwitcher.defaultLayoutBuilder, //布局构建器
})当AnimatedSwitcher的 child 发生变化时类型或 Key 不同旧 child 会执行隐藏动画新 child 会执行执行显示动画。究竟执行何种动画效果则由transitionBuilder参数决定该参数接受一个AnimatedSwitcherTransitionBuilder类型的 builder
typedef AnimatedSwitcherTransitionBuilder Widget Function(Widget child, Animationdouble animation);defaultTransitionBuilder 默认AnimatedSwitcher会对新旧child执行“渐隐”和“渐显”动画
现一个计数器然后在每一次自增的过程中旧数字执行缩小动画隐藏新数字执行放大动画显示
import package:flutter/material.dart;class AnimatedSwitcherCounterRoute extends StatefulWidget {const AnimatedSwitcherCounterRoute({Key key}) : super(key: key);override_AnimatedSwitcherCounterRouteState createState() _AnimatedSwitcherCounterRouteState();}class _AnimatedSwitcherCounterRouteState extends StateAnimatedSwitcherCounterRoute {int _count 0;overrideWidget build(BuildContext context) {return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: Widget[AnimatedSwitcher(duration: const Duration(milliseconds: 500),transitionBuilder: (Widget child, Animationdouble animation) {//执行缩放动画return ScaleTransition(child: child, scale: animation);},child: Text($_count,//显示指定key不同的key会被认为是不同的Text这样才能执行动画key: ValueKeyint(_count),style: Theme.of(context).textTheme.headline4,),),ElevatedButton(child: const Text(1,),onPressed: () {setState(() {_count 1;});},),],),);}}Flutter SDK中还提供了一个AnimatedCrossFade组件它也可以切换两个子元素切换过程执行渐隐渐显的动画和AnimatedSwitcher不同的是AnimatedCrossFade是针对两个子元素而AnimatedSwitcher是在一个子元素的新旧值之间切换
示例实现各种“滑动出入动画”便非常容易只需给direction传递不同的方向值即可
class SlideTransitionX extends AnimatedWidget {SlideTransitionX({Key? key,required Animationdouble position,this.transformHitTests true,this.direction AxisDirection.down,required this.child,}) : super(key: key, listenable: position) {switch (direction) {case AxisDirection.up:_tween Tween(begin: const Offset(0, 1), end: const Offset(0, 0));break;case AxisDirection.right:_tween Tween(begin: const Offset(-1, 0), end: const Offset(0, 0));break;case AxisDirection.down:_tween Tween(begin: const Offset(0, -1), end: const Offset(0, 0));break;case AxisDirection.left:_tween Tween(begin: const Offset(1, 0), end: const Offset(0, 0));break;}}final bool transformHitTests;final Widget child;final AxisDirection direction;late final TweenOffset _tween;overrideWidget build(BuildContext context) {final position listenable as Animationdouble;Offset offset _tween.evaluate(position);if (position.status AnimationStatus.reverse) {switch (direction) {case AxisDirection.up:offset Offset(offset.dx, -offset.dy);break;case AxisDirection.right:offset Offset(-offset.dx, offset.dy);break;case AxisDirection.down:offset Offset(offset.dx, -offset.dy);break;case AxisDirection.left:offset Offset(-offset.dx, offset.dy);break;}}return FractionalTranslation(translation: offset,transformHitTests: transformHitTests,child: child,);}
}AnimatedSwitcher(duration: Duration(milliseconds: 200),transitionBuilder: (Widget child, Animationdouble animation) {var tweenTweenOffset(begin: Offset(1, 0), end: Offset(0, 0))return SlideTransitionX(child: child,direction: AxisDirection.down, //上入下出position: animation,);},...//省略其余代码
)7.Flutter预置的动画过渡组件
AnimatedPadding 在padding发生变化时会执行过渡动画到新状态 AnimatedPositioned 配合Stack一起使用当定位状态发生变化时会执行过渡动画到新的状态。 AnimatedOpacity 在透明度opacity发生变化时执行过渡动画到新状态 AnimatedAlign 当alignment发生变化时会执行过渡动画到新的状态。 AnimatedContainer 当Container属性发生变化时会执行过渡动画到新的状态。 AnimatedDefaultTextStyle 当字体样式发生变化时子组件中继承了该样式的文本组件会动态过渡到新样式。
import package:flutter/material.dart;class AnimatedWidgetsTest extends StatefulWidget {const AnimatedWidgetsTest({Key? key}) : super(key: key);override_AnimatedWidgetsTestState createState() _AnimatedWidgetsTestState();
}class _AnimatedWidgetsTestState extends StateAnimatedWidgetsTest {double _padding 10;var _align Alignment.topRight;double _height 100;double _left 0;Color _color Colors.red;TextStyle _style const TextStyle(color: Colors.black);Color _decorationColor Colors.blue;double _opacity 1;overrideWidget build(BuildContext context) {var duration const Duration(milliseconds: 400);return SingleChildScrollView(child: Column(children: Widget[ElevatedButton(onPressed: () {setState(() {_padding 20;});},child: AnimatedPadding(duration: duration,padding: EdgeInsets.all(_padding),child: const Text(AnimatedPadding),),),SizedBox(height: 50,child: Stack(children: Widget[AnimatedPositioned(duration: duration,left: _left,child: ElevatedButton(onPressed: () {setState(() {_left 100;});},child: const Text(AnimatedPositioned),),)],),),Container(height: 100,color: Colors.grey,child: AnimatedAlign(duration: duration,alignment: _align,child: ElevatedButton(onPressed: () {setState(() {_align Alignment.center;});},child: const Text(AnimatedAlign),),),),AnimatedContainer(duration: duration,height: _height,color: _color,child: TextButton(onPressed: () {setState(() {_height 150;_color Colors.blue;});},child: const Text(AnimatedContainer,style: TextStyle(color: Colors.white),),),),AnimatedDefaultTextStyle(child: GestureDetector(child: const Text(hello world),onTap: () {setState(() {_style const TextStyle(color: Colors.blue,decorationStyle: TextDecorationStyle.solid,decorationColor: Colors.blue,);});},),style: _style,duration: duration,),AnimatedOpacity(opacity: _opacity,duration: duration,child: TextButton(style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Colors.blue)),onPressed: () {setState(() {_opacity 0.2;});},child: const Text(AnimatedOpacity,style: TextStyle(color: Colors.white),),),),AnimatedDecoratedBox1(duration: Duration(milliseconds: _decorationColor Colors.red ? 400 : 2000),decoration: BoxDecoration(color: _decorationColor),child: Builder(builder: (context) {return TextButton(onPressed: () {setState(() {_decorationColor _decorationColor Colors.blue? Colors.red: Colors.blue;});},child: const Text(AnimatedDecoratedBox toggle,style: TextStyle(color: Colors.white),),);}),)].map((e) {return Padding(padding: const EdgeInsets.symmetric(vertical: 16),child: e,);}).toList(),),);}
}自定义组件
1.简介
“组合”是自定义组件最简单的方法在任何需要自定义组件的场景下都应该优先考虑是否能够通过组合来实现。 而通过CustomPaint和RenderObject自绘的方式本质上是一样的都需要开发者调用Canvas API手动去绘制UI
2.组合组件
自定义渐变背景按钮 DecoratedBox可以支持背景色渐变和圆角InkWell在手指按下有涟漪效果所以我们可以通过组合DecoratedBox和InkWell来实现GradientButton
import package:flutter/material.dart;class GradientButton extends StatelessWidget {const GradientButton({Key? key, this.colors,this.width,this.height,this.onPressed,this.borderRadius,required this.child,}) : super(key: key);// 渐变色数组final ListColor? colors;// 按钮宽高final double? width;final double? height;final BorderRadius? borderRadius;//点击回调final GestureTapCallback? onPressed;final Widget child;overrideWidget build(BuildContext context) {ThemeData theme Theme.of(context);//确保colors数组不空ListColor _colors colors ?? [theme.primaryColor, theme.primaryColorDark];return DecoratedBox(decoration: BoxDecoration(gradient: LinearGradient(colors: _colors),borderRadius: borderRadius,//border: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),),child: Material(type: MaterialType.transparency,child: InkWell(splashColor: _colors.last,highlightColor: Colors.transparent,borderRadius: borderRadius,onTap: onPressed,child: ConstrainedBox(constraints: BoxConstraints.tightFor(height: height, width: width),child: Center(child: Padding(padding: const EdgeInsets.all(8.0),child: DefaultTextStyle(style: const TextStyle(fontWeight: FontWeight.bold),child: child,),),),),),),);}
}GradientButton是由DecoratedBox、Padding、Center、InkWell等组件组合而成 flukit组件库已收录GradientButton 使用 children: Widget[GradientButton(colors: const [Colors.orange, Colors.red],height: 50.0,child: const Text(Submit),onPressed: onTap,),3.CustomPaint 和 RenderObject
painter: 背景画笔会显示在子节点后面;
foregroundPainter: 前景画笔会显示在子节点前面
size当child为null时代表默认绘制区域大小如果有child则忽略此参数画布尺寸则为child尺寸。如果有child但是想指定画布为特定大小可以使用SizeBox包裹CustomPaint实现。
isComplex是否复杂的绘制如果是Flutter会应用一些缓存策略来减少重复渲染的开销。
willChange和isComplex配合使用当启用缓存时该属性代表在下一帧中绘制是否会改变CustomPaint({Key key,this.painter, this.foregroundPainter,this.size Size.zero, this.isComplex false, this.willChange false, Widget child, //子节点可以为空
})
//自定义
class MyPainter extends CustomPainter class CustomCheckbox extends LeafRenderObjectWidget Canvas常用 drawLine 画线 drawPoint 画点 drawPath 画路径 drawImage 画图像 drawRect 画矩形 drawCircle 画圆 drawOval 画椭圆 drawArc 画圆弧