Says flutter---10动画、主题风格和屏幕适配

130 阅读5分钟

animation动画

动画API

  • Animation
    实现动画的核心类,本身是一个抽象类。其方法有:
    addListener:添加动画监听器,调用setState方法,更新数据;
    removeListener:移除动画监听器;
    addStatusListener:添加动画状态监听器,动画会从'dismissed'状态开始,'complete'状态结束;
    removeStatusListener:移除动画状态监听器;
    status:获取动画当前状态;
    value:获取动画当前值。
    
  • AnimationController
    Animation的一个子类,实现动画需要创建AnimationController对象,其有一个必传参数vsync,我们一般将this对象传进去,因为其要求类型是TickerProvider,故为了能使用this将使用混入把SingleTickerProviderStateMixin定义到State中。
    其默认生成的值是0-1之间,除了可以实现监听,获取动画的状态、值以外,还实现了方法forward()向前执行动画、reverse()反向播放动画、stop()停止动画。
    
  • CurvedAnimation
    Animation的一个实现类,是为了给AnimationController增加动画曲线。
    
  • Tween
    1.使用场景:
      默认情况次下,AnimationController动画生成的值在0-1之间,如果希望使用以外的值,要使用Tween。
    2.使用:
      其的子类ColorTween、BorderTween可以针对动画或者边框设置动画的值;
      要使用Tween对象,需要调用Tween的animate()方法,传入一个Aniamtion对象。
    

动画使用

  • AnimationController实现单项放大
    1.创建AnimationController对象,其默认动画效果为匀速:
      AnimationController _controller;
       @override
       void initState() {
       	_controller = AnimationController(
          	vsync: this, //this代表指向的是当前对象,类里面是没有,故要在initStat()方法里面实现
          	lowerBound: 50.0,
          	upperBound: 150.0,
          	duration: Duration(seconds: 2)
      	);
      	super.initState();
    	}
      其中该对应的State类要实现混入SingleTickerProviderStateMixin,因为vsync要求类型是TickerProvider
    2.修改需要变化的组件:
       body: Center(
          child: Icon(Icons.favorite,color:Colors.red,size: _controller.value,),
       ),
     3.执行动画:
       floatingActionButton: FloatingActionButton(
          child: Icon(Icons.play_arrow),
          onPressed: (){
            _controller.forward();
          },
        ),
    
  • 实现放大、缩小功能以及暂停恢复以后可以按照原先的状态继续动画
    1.创建AnimationController对象:
      AnimationController _controller;
      Animation _animation;
      Animation _animationSize;
      _controller = AnimationController(
          vsync: this,
          duration: Duration(seconds: 2)
      );
    2.自定义动画效果曲线:
     _animation = CurvedAnimation(parent: _controller,curve: Curves.easeInOut);
    3.自定义变化的数值:
      _animationSize = Tween(begin: 50.0,end: 150.0).animate(_animation); //传入的是Animation对象,也可以是_controller。
    4.监听动画的改变:
      _controller.addListener(() {
        setState(() {});
      });
    5.监听动画的状态改变,实现可以反转放大缩小的效果:
       _controller.addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          _controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          _controller.forward();
        }
      });
    6.实现暂停恢复之后可以回到原来的状态。
      body: Center(
        child: Icon(Icons.favorite,color:Colors.red,size: _animationSize.value,),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: (){
          if (_controller.isAnimating) {
            _controller.stop();
          } else if (_controller.status == AnimationStatus.forward) {
            _controller.forward();
         } else if (_controller.status == AnimationStatus.reverse) {
            _controller.reverse();
         } else {
            _controller.forward();
         }
       },
     ),
    
  • AnimationWidget优化每次刷新都会执行build方法,浪费性能
    //---其他操作与上面的一致
    1.去掉监听动画的操作_controller.addListener((){});
    2.新建类继承自AnimateWidgetclass HYAnimation extends AnimatedWidget {
        HYAnimation(Animation animation): super(listenable: animation);
        @override
        Widget build(BuildContext context) {
          Animation animation = listenable;
          return Icon(Icons.favorite,color:Colors.red,size: animation.value,);
        }
      }
    3.使用该类:
      body: Center(
         child:HYAnimation(_animationSize)
      ),
    
  • AnimationBuilder优化AnimationWidget创建类
    1.去掉新创建类操作;
    2.用AnimatedBuilder包裹有动画的组件:
      body: Center(
        child: AnimatedBuilder(
          animation: _controller,
          builder: (context,child){
            return Icon(Icons.favorite,color:Colors.red,size: _animationSize.value,);
          },
        ),
      ),
    
  • 复合动画包括大小、颜色、旋转效果
    1.创建多个Animation对象:
      AnimationController _controller;
      Animation _animation;
      Animation _animationSize;
      Animation _animationColor;
      Animation _animationOpacity;
      Animation _animationRadius;
      _controller = AnimationController(
          vsync: this,
          duration: Duration(seconds: 2)
      );
    2.CurvedAnimation自定义动画效果:
    3.Tween自定义变化的数值:
      _animationSize = Tween(begin: 10.0,end: 100.0).animate(_controller);
      _animationColor = ColorTween(begin:Colors.orange,end: Colors.red).animate(_controller);
      _animationOpacity = Tween(begin: 0.0,end: 1.0).animate(_controller);
      _animationRadius = Tween(begin: 0.0,end: pi).animate(_controller);  
      以上动画并不是都支持Curves的所有属性,故animate里面的parent属性只能是_controller,而不是_animation4.放大缩小和暂停恢复可以回到原来效果的操作和上面保持一致;
    5.发生动画的组件部分:
       body: Center(
         child: AnimatedBuilder(
           animation: _controller,
           builder: (context,child){
             return Opacity(
               opacity: _animationOpacity.value,
               child: Transform(
                 transform: Matrix4.rotationZ(_animationRadius.value),  
                  //Matrix4是矩阵的意思,transform要求传入的是一个矩阵
                 alignment: Alignment.center,  //设置旋转点为中心点
                 child: Container(
                   width: _animationSize.value,
                   height: _animationSize.value,
                   color: _animationColor.value,
                   alignment: Alignment.center,
                 ),
               ),
             );
           },
         ),
       ),
    

关于动画补充

  • 页面跳转补间动画(没有视觉上push的效果)
    1.要跳转的页面
      Navigator.of(context).push(PageRouteBuilder(
        pageBuilder: (context,animation1,animation2){
          return FadeTransition(
            opacity: animation1,
            child: HYPage(),
          );
        }
      ));
    
  • Hero动画实现从原位置放大图片后恢复,且采用补间动画的效果
    1.图片页(点击图片放大)
    	final String imageUrl = "https://picsum.photos/500/500?random=$index";
    	return GestureDetector(
      	onTap: (){
      		Navigator.of(context).push(PageRouteBuilder(
      			pageBuilder: (context,animation1,animation2){
      				return FadeTransition(opacity: animation1,child: HYImagePage(imageUrl));
      			}
      		));
      	},
      	child: Hero(
      		tag: imageUrl,
      		child: Image.network(imageUrl,fit: BoxFit.cover,)
      	),
      );
    2.放大的图片
    	class HYImagePage extends StatelessWidget {
    		final String _imageUrl;
    		HYImagePage(this._imageUrl);
    
    		@override
    		Widget build(BuildContext context) {
      		return Scaffold(
        			backgroundColor: Colors.black,
        			body: Center(
          			child: GestureDetector(
            				onTap: (){
              			Navigator.of(context).pop();
            				},
            				child: Hero(
                				tag:_imageUrl,
                				child: Image.network(_imageUrl)
            				),
                      ),
                  ),
              );
          }
      }
    

主题风格

  • 全局主题
    materialApp设置属性theme,传入一个ThemeData(),其常用属性有:
    	// 1.亮度: light-dark
    	brightness: Brightness.light,
      // 2.primarySwatch: primaryColor/accentColor的结合体
      primarySwatch: Colors.red,
      // 3.主要颜色: 导航/底部TabBar
      primaryColor: Colors.pink,
      // 4.次要颜色: FloatingActionButton/按钮颜色
      accentColor: Colors.orange,
      // 5.卡片主题
      cardTheme: CardTheme(
      	color: Colors.greenAccent,
      	elevation: 10,
      	shape: Border.all(width: 3, color: Colors.red),
      	margin: EdgeInsets.all(10)
      ),
      // 6.按钮主题
      buttonTheme: ButtonThemeData(
      	minWidth: 0,
      	height: 25
      ),
      // 7.文本主题
      textTheme: TextTheme(
      	title: TextStyle(fontSize: 30, color: Colors.blue),
      	display1: TextStyle(fontSize: 10),
      )
    
  • 局部主题
    自定义主题,在父节点包裹一下主题Theme,设置data为一个新的ThemeDataTheme(
        data: ThemeData(),
        child: Scaffold(
        ),
      );
    

屏幕适配

  • 适配方案
    1.rem适配;
    2.vw、wh适配,分成100等份;
    3.rpx适配,类似于小程序;
    4.自定义rpx适配方案。
    
  • 具体实现
    无论什么屏幕,统一设置吃750份(或者自定义的份数)
    1.适配信息:
    	import 'dart:ui';
      class HYSizeFit {
    		// 1.基本信息
    		static double physicalWidth;
    		static double physicalHeight; //分辨率
    		static double screenWidth; //屏幕宽度
    		static double screenHeight;  //屏幕高度
    		static double dpr;
    		static double statusHeight;  //屏幕有刘海的高度
    		static double rpx;
    		static double px;
    		static void initialize({double standardSize = 750}) {
      		// 1.手机的物理分辨率
      		physicalWidth = window.physicalSize.width;
      		physicalHeight = window.physicalSize.height;
      		// 2.获取dpr
      		dpr = window.devicePixelRatio;
      		// 3.宽度和高度
      		screenWidth = physicalWidth / dpr;
      		screenHeight = physicalHeight / dpr;
      		// 4.状态栏高度
      		statusHeight = window.padding.top / dpr;
      		// 5.计算rpx的大小
      		rpx = screenWidth / standardSize;
      		px = screenWidth / standardSize * 2;
    		}
    		static double setRpx(double size) {
      		return rpx * size;
    		}
    		static double setPx(double size) {
      		return px * size;
    		}
      }
    2.使用:
     	child: Container(
            width: HYSizeFit.setRpx(400),
            height: HYSizeFit.setPx(200),
            color: Colors.red,
            alignment: Alignment.center,
            child: Text("Hello World", style: TextStyle(fontSize: 40 * HYSizeFit.rpx),),
      ),
    3.手机屏幕的大小还可以在build方法中这样获得:(在materialApp里面的build方法中不能获得)
      final width = MediaQuery.of(context).size.width;
      final height = MediaQuery.of(context).size.height;
      final statusHeight = MediaQuery.of(context).padding.top;