精华内容
下载资源
问答
  • dart 如何优雅的避空

    2019-03-18 15:00:30
    本篇通过对比一般非空判断和 dart 特有的语法糖告诉你如何使用 dart 进行优雅的避空。 目录 1. dart 在线编辑器 一般一些简单的 dart 测试我们可以直接用在线编辑器来做测试和验证。 下面给大家介绍的两个都...

    前言

    对于每一个程序员来说,空指针异常应该是基本都会遇到过的异常,而且这个异常出现的概率还比较大。

    但是,空指针异常又是最容易解决的异常,因为只要加个非空判断就可以避免了。

    本篇通过对比一般非空判断和 dart 特有的语法糖告诉你如何使用 dart 进行优雅的避空。

    目录

    1. dart 在线编辑器

    一般一些简单的 dart 测试我们可以直接用在线编辑器来做测试和验证。

    下面给大家介绍的两个都是官网的。

    **dart 在线运行器主页版:**?
    https://www.dartlang.org/guides/get-started

    **dart 在线运行器全屏版:**?
    https://dartpad.dartlang.org/null

    其中全屏版就是在主页版里面点击全屏按钮就打开了。

    所以可以认为是一样的。

    但是笔者使用起来的不同如下,大家可以根据自己的感受选择。

    主页版:
    优点:运行输出结果较全屏版快。
    缺点:输出结果区域较小,超出需要滑动查看。

    全屏版:
    优点:输出结果区域大。可以直观看到结果。
    缺点:运行输出结果较主页版慢。

    2. dart ?.

    dart 语法糖 ?.

    它的意思是左边如果为空返回 null,否则返回右边的值。

    A?.B
    如果 A 等于 null,那么 A?.B 为 null
    如果 A 不等于 null,那么 A?.B 等价于 A.B

    Sample:

    void main() {
      Animal animal = new Animal('cat');
      Animal empty = null;
      
      //animal 非空,返回 animal.name 的值 cat
      print(animal?.name);
      //empty 为空,返回 null
      print(empty?.name);
      
      //animal 非空,可以直接访问 animal.name 的值 cat
      print(animal.name);
      //empty 为空,抛出异常
      print(empty.name);
    }
    
    class Animal {
      final String name;
      Animal(this.name);
    }
    

    大家拷贝代码然后替换在线编辑器的内容,运行后会看到如下输出:

    cat
    null
    cat
    Uncaught exception:
    Cannot read property 'get$name' of null
    

    可以看到假设左边不为空,不管是使用**?.还是直接用我们熟悉的.访问变量都是没问题的。
    但是如果左边为空,使用
    ?.会返回null**。但是直接使用**.**会直接抛出异常。

    3. dart ??

    dart 语法糖 ??

    它的意思是左边如果为空返回右边的值,否则不处理。

    A??B
    如果 A 等于 null,那么 A??B 为 B
    如果 A 不等于 null,那么 A??B 为 A

    以上面为例子,假设我们上面要求当 empty 为空时,默认值输出 unknown。

    那么可以修改如下:

    //empty 为空,返回 null
    print(empty?.name);
    

    改为

    //empty 为空,本来要返回 null,由于有 ??,返回 unknown
    print(empty?.name??'unknown');
    

    这样就不会返回 null 而是返回 unknown。

    同样的大家可以试下返回 cat 的语句如果加上这个会怎样,可以预见是不会改变的。

    4. dart ?. ?? 优雅所在

    这边举例说明下使用 ?. ?? 语法糖和不使用的对比。

    void main() {
      C c = new C('Case 1');
      B b = new B(c);
      A a = new A(b);
      
    //   C c = new C(null);
    //   B b = new B(c);
    //   A a = new A(b);
      
    //   C c = new C('Case 2');
    //   B b = null;
    //   A a = new A(b);
      
      //直接使用.来最终获取 c 的变量 value
      if (a != null && a.bMember != null && a.bMember.cMember != null) {
        print(a.bMember.cMember.value);
      } else {
        print(null);
      }
      
      //直接使用.来最终获取 c 的变量 value,为空时返回 unknown
      if (a != null && a.bMember != null && a.bMember.cMember != null) {
        String value = a.bMember.cMember.value;
        if (value == null) {
          value = 'unknown';
        }
        print(value);
      } else {
        print('unknown');
      }
      
      //dart 使用?.来最终获取 c 的变量 value
      print(a?.bMember?.cMember?.value);
      //dart 使用?.来最终获取 c 的变量 value,为空时使用 ?? 返回 unknown
      print(a?.bMember?.cMember?.value??'unknown');
    }
    
    class A {
      final B bMember;
      A(this.bMember);
    }
    
    class B {
      final C cMember;
      B(this.cMember);
    }
    
    class C {
      final String value;
      C(this.value);
    }
    

    这里面有三个 case,另外两个 case 暂时注释掉。

    这三个 case 的结果分别为:

    Case 1
    Case 1
    Case 1
    Case 1
    
    null
    unknown
    null
    unknown
    
    null
    unknown
    null
    unknown
    

    可以看到 dart 的语法糖很优雅,一行全搞定。

    5. print 方法遇到 null

    下面这个例子:

    void main() {
        String a = null;
        print('exception='+a);
    }
    

    你觉得结果是 exception=null 吗?

    结果是

    Uncaught exception:
    Invalid argument: null
    

    原因是因为 print 里面连接的必须是字符串。

    因为这里 a 确实是字符串,所以编辑器没有报错。

    假设这里 a 为一个对象 A 的变量,会报如下提示:

    The argument type 'A' can't be assigned to the parameter type 'String'.
    

    那我们怎么处理?

    有两种方法。

    方法一:

    void main() {
        String a = null;
        print('exception='+'$a');
    }
    

    方法二:

    void main() {
        String a = null??'null';
        print('exception='+a);
    }
    

    注意下面的写法是不行的,原因是 ?? 优先级没有 + 高。需要加小括号。

    void main() {
        String a = null;
        print('exception='+a??'null');
    }
    

    6. 牛刀小试

    知识学以致用才能够巩固。

    因此这边出了小题目给大家测试是否完全掌握本篇内容。

    答案组成了支付宝口令红包哦~

    微信公众号回复「牛刀小试」获取题目。

    或者直接点击菜单栏目录->牛刀小试获取。

    温馨提示:
    如果你输入 3 次还是提示错误(错误过多口令红包会暂时不可用哦),有两种情况。

    第一种就是答案错了。

    第二种就是领取完了。

    答案会在红包领取完之后或一天之后将题目替换为题目+答案。

    因为是异步的,所以不一定实时更新哦~

    更多阅读:
    Flutter 即学即用系列博客——01 环境搭建
    Flutter 即学即用系列博客——02 一个纯 Flutter Demo 说明
    Flutter 即学即用系列博客——03 在旧有项目引入 Flutter
    Flutter 即学即用系列博客——04 Flutter UI 初窥
    Flutter 即学即用系列博客——05 StatelessWidget vs StatefulWidget

    展开全文
  • Dart 如何优雅的避空

    2019-11-18 11:05:37
    class Animal { final String name;...#3 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:172:12) [Done] exited with code=255 in 0.214 seconds    
    class Animal {
    
      final String name;
    
      Animal(this.name);
    
    }
    
    void main() {
    
      Animal animal = new Animal('cat');
    
      Animal empty = null;
    
      //1-----------animal 非空,?.作用返回 animal.name 的值 cat
    
      print(animal?.name);
    
      //2-----------empty 为空,?.作用返回 null
    
      print(empty?.name);
    
      //3-----------animal 非空,可以直接访问 animal.name 的值 cat
    
      print(animal.name);
    
      //4-----------animal 非空,??作用假如为空 则返回右面的数值
    
      print(empty?.name??'un---------known');
    
      //5-----------animal 非空,$ 可以避免 为空 报错
    
       print('$empty.name');
    
      //6-----------empty 为空,抛出异常
    
      print(empty.name);
    
    }

    结果如下

    
    cat
    null
    cat
    un---------known
    null.name
    Unhandled exception:
    NoSuchMethodError: The getter 'name' was called on null.
    Receiver: null
    Tried calling: name
    #0      Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5)
    #1      main (file:///e:/Documents/darttest/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1.dart:17:15)
    #2      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:305:19)
    #3      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:172:12)
    
    [Done] exited with code=255 in 0.214 seconds
    
    


     

     

    展开全文
  • 所以参考lottie-android库实现了一个功能完备,性能优异的纯Dart Package来提供Flutter上的Lottie动画支持。 fish-lottie项目架构图 如上图所示,整个项目由基础模块,接口层和控件层构成,支持矢量图形,填充描边等...


    背景


    Lottie 是一个由 Airbnb 开源的横跨 Android,iOS,Web 等多端的一个动画方案,它以 JSON 的方式解决了开发者对复杂动画实现的开发成本问题。

    众所周知,闲鱼团队是比较早在客户端侧选择Flutter方案的技术团队,当前的闲鱼工程里也包含很多的Flutter界面。  而官方却一直没有提供Lottie-Flutter方案,当前也有一些第三方开发者提供了相关实现方案,基本上分为两种:

    • 在Native端进行数据解析和渲染,再使用桥接的方式把渲染数据传输到Flutter端进行显示。

    • 在Flutter直接进行数据解析和使用Flutter绘图能力进行渲染显示。

    不过当前已经开源的方案都存在一些问题,前者会在性能和显示存在一些问题,例如显示闪烁白屏。后者在一些能力支持上存在一些功能缺陷,例如不支持文本动画等。所以这一直是闲鱼团队乃至整个Flutter开发者团体的一个痛点。

    项目架构


    闲鱼团队在调研了官方开源的lottie-android库之后,发现不管是数据解析能力,还是图形绘制能力。Flutter都提供了媲美Android的实现方案。所以参考lottie-android库实现了一个功能完备,性能优异的纯Dart Package来提供Flutter上的Lottie动画支持。

    fish-lottie项目架构图

    如上图所示,整个项目由基础模块,接口层和控件层构成,支持矢量图形,填充描边等能力,详情可见Lottie支持能力,支持的能力也和lottie-android大致相同。

     基础模块

    基础模块是与 FlutterSDK 提供的各种能力直接交互的地方,主要分为数据模型模块,动画绘制模块,数据解析模块和工具模块。

    首先对于整个框架来说,我们首先可以拿到包含整个动画信息的JSON文件,所以需要先经过我们的数据解析模块,把JSON文件里面包含的数据和信息解析并传递给数据模型模块,动画绘制模块负责拿到数据模型模块里的对象之后,调用Flutter提供的绘图能力来进行图形的绘制,而工具模块就主要负责获取屏幕信息,字符串处理,日志打印等工具类能力。

     接口层

    接口层主要负责JSON数据的输入和动画绘制控制和调用,JSON信息经过数据解析模块最终会生成一个LottieComposition对象,这个对象里承载着整个JSON的动画信息。

    然后将这个对象传递给LottieDrawable,LottieDrawable会把对象传递传递给动画绘制模块,这样动画绘制模块就可以拿到动画信息,LottieDrawable再调用动画绘制模块来进行动画的绘制和刷新。

     组件层

    组件层,这里主要是我们继承Flutter的Widget实现的自定义组件,也是框架暴露给开发者的接口。

    开发者只需要新建一个LottieAnimationView,并把JSON文件的路径传递给它,支持Asset,Url,File三种形式,然后再把LottieAnimationView像一个普通Widget放到FlutterUI里,就可以完成一个简单的Lottie动画播放器了,当然也会暴露动画的控制接口以及控件的布局接口,只需要在新建LottieAnimationView的时候传入AnimationController,width,height,alignment等属性就可以完成对动画的进一步定制。

    工作流程


     整体思路

    设计师在使用AE制作一段动画时,这个动画其实是由不同的图层组成的,AE提供了多个图层供设计师选择,例如纯色层(通常当做背景)、形状层(绘制各种矢量图形)、文本层、图片层等,每一个图层都可以设置平移、旋转、放缩等变换。

    每个图层可能又包含多个元素,例如形状图层可能由多个基本矢量图形和钢笔路径图形组合成为一个具有设计感的图案,每个元素也可能包含自己的变换,除了基础变换之外,还可以设置颜色、形状这样的变换。以上图层和元素的动画就组成了一个完整的动画。

    如上图所示,我们在AE中新建了一个纯色图层并填充上蓝色,然后新建了一个形状图层,并给这个形状图层添加了一个位移动画(即给形状图层1变换中的位置设置两个关键帧,并在关键帧上设置初始值和最终值)。

    然后在形状图层中添加一个矩形路径和一个黄色的填充,再以同样的方法给矩形的大小和圆度设置动画,不过大小的关键帧为0秒到3秒,圆度的关键帧为3秒到5秒。所以就完成了一个矩形从左到右的同时,先变大然后变为圆形的动画。我们通过Lottie提供的BodyMovin插件将以上的动画导出为JSON格式的文件,这个JSON文件里就包含了刚刚我们的所有绘制和关键帧信息。

    如上图所示,拿到这个JSON文件之后,我们首先通过了数据解析把设计师在AE中制作的各种图层信息和动画信息都解析传递给一个LottieComposition对象,然后LottieDrawable获取到这个LottieComposition对象并调用底层的Canvas来进行图形的绘制,通过AnimationBuilder来进行进度的控制,进度发生变化时通知Drawable进行重绘,绘制模块会获取到处于该进度时的各项属性值,然后就完成了动画的播放。

     数据加载和显示

    我们的组件层提供三种方式来进行JSON文件的获取,分别为asset(程序内置资源),url(网络资源),file(文件资源)。整个数据的加载和显示的流程图大致如下所示,省略了底层绘制的细节:

    这里以fromAsset方式举例,其他两种的加载方式和这种相同,都统一由LottieCompositionFactory进行处理。这里我们根据构造函数的不同将将加载方式分为三种,即asset,file和url。然后根据类型的不同调用LottieCompositionFactory里的不同加载方法将对应的内置资源、网络资源和文件资源加载进来并进行JSON文件的解析,然后最终的产物是一个LottieComposition对象,这个对象经过异步加载解析,在解析完成之后会通知LottieAnimationView进行调用。我们将加载完成的LottieComposition对象传递给我们的绘制类,LottieDrawable会根据composition里的内容建立图层组,图层组里包含如形状,文本层等图层,和设计师在AE制作动画时创建的图层一一对应。每个图层有不同的绘制规则和方法,然后在LottieAnimationView里获取到系统的Canvas传递给LottieDrawable并调用draw方法。这样就可以使用系统画布绘制我们自己的动画内容了。

     动画绘制与播放

    完成了动画的加载与显示,我们还需要让画面动起来。我们通过AnimationBuilder的方式将AnimationController的value设置为LottieDrawable的progress,然后触发重绘使我们的底层通过progress去获取当前进度的各项动画属性,这样就可以实现动画的效果了。时序图大致如下所示:

    我们在LottieAnimationView里通过Flutter内置的AnimationController来控制动画,其中forward方法可以让Animation的progress从零开始增加,这也是我们动画播放的开始。

    我们不断调用setProgress函数将动画的进度设置到各层,最终到达KeyframeAnimation层,更新当前进度。进度改变之后我们需要通知上层进行界面的重绘,最终将LottieDrawable里的一个isDirty的变量设为true。

    我们在setProgress函数里,在完成进度设置之后我们获取lottieDrawable的isDirty变量,如果这个变量为true,证明进度已经更新,此时我们调用重写的方法markNeedPaint(),这时候系统会标记当前组件为需要更新的组件,Flutter会调用我们重写的paint函数,对整个画面进行重绘。我们和显示的流程一样,一层层进行绘制,在底层我们会根据当前进度拿到KeyframeAnimation中对应的属性值,然后绘制出来的画面就会产生变化。通过这样不断的更新进度,然后重新获取当前进度对应的属性进行重绘,这样就可以实现动画的播放效果。

    实现差异


     安卓端组件层

    对于lottie-android来说,AnimationView和Drawable组成了整个组件层。AnimationView继承于ImageView,LottieDrawable继承于Drawable。整个工作的流程和上面所说的基本相同,开发者在xml文件中写入LottieAnimationView并设置JSON文件资源路径。然后AnimationView会发起数据获取和解析,解析完成之后把Composition对象传递给LottieDrawable,然后调用重写的draw方法来进行动画展示。

    然后整个动画的播放,暂停,进度等控制都是通过开发者在代码中获取AnimationView的引用然后调用各种方法来完成的,但是其实真正的动画控制是由LottieDrawable里的ValueAnimator来控制的。在初始化LottieDrawable的同时也会创建ValueAnimator,它会产生一个0~1的插值,根据不同的插值来设置当前动画进度。LottieAnimationView里的暂停,播放等动画控制方法其实就是调用了这个ValueAnimator自身的对应方法来实现动画的控制。

     Flutter组件层

    对于Flutter来说,并没有提供类似于ImageView和Drawable这样的组件让我们继承和重写,我们需要自定义一个Widget,自定义组件一般有三种方式:

    • 原生组件的组合

    此处我们显然不能使用这个方法,因为我们需要获取系统提供的画布来进行绘制。

    • 实现CustomPainter

    在Flutter中,提供了一个自绘UI的接口CustomPainter,这个接口会提供一块2D画布Canvas,Canvas内部封装了一些基本绘制的API,开发者可以通过Canvas绘制各种自定义图形。

    我们可以在重写的paint方法中获取到系统的canvas,把这个canvas传递给我们的LottieDrawable就可以完成动画的绘制了,然后在属性变化时导致画面需要刷新时在shouldRepaint返回true。

    但是这个方案会有一些问题无法解决,我们都知道整个LottieAnimationView是作为一个Widget嵌入到FlutterUI当中的,我们往往需要自定义动画播放区域(即LottieAnimationView)的大小,但是当开发者没有设定这个宽高值的时候或者是设定的尺寸大于父布局的尺寸的时候,我们也要根据父布局对子布局的约束来进行尺寸的适配和转换。

    但是在Flutter提供的这个CustomPainter中,没有暴露相应的接口让我们获取到这个Widget所对应的RenderObject的constraint属性,也就无法在开发者没有设置LottieAnimationView自身的width和height时根据父布局的约束进行尺寸适配,所以放弃了这个实现方案。

    • 自定义RenderObject

    我们都知道Flutter中的Widget只是一些轻量的样式配置信息,真正进行图形渲染的类是RenderObject。

    所以我们自然也可以重写这个RenderObject类中的paint方法来获取系统画布来进行绘制。这个方案会比上一个方案复杂一些,我们需要先定义一个继承于RenderBox的RenderLottie类,然后重写paint方法来把系统的canvas传递给LottieDrawable,在需要进行刷新的地方调用markNeedPaint方法,就可以完成界面重绘。

    对于RenderObject来说,我们可以获取到当前组件的constraint属性,也就是在开发者没有设置LottieAnimationView的尺寸或者是设置的尺寸超出复布局的时候我们也可以自适应父布局的尺寸了。

    接下来需要定义一个继承于LeafRenderObjectWidget的组件LeafRenderLottie并重写createRenderObject方法并返回RenderLottie对象,重写updateRenderObject方法更新RenderLottie的进度等各项属性。这就完成了一个LottieWidget的实现。

    那我们如何来进行动画的播放控制呢?我们的LottieAnimationView是作为一个Widget嵌入到FlutterUI当中的,一般不会去获取它的引用来调用方法,那我们就传入一个Flutter提供的AnimationController,然后在LottieAnimationView的build方法中返回一个AnimationBuilder并把AnimationController的进度值传给LeafRenderLottie,如果开发者没有传入AnimationController,我们就提供一个默认的controller来进行简单的动画播放就可以了。

    关键代码如下所示:

    1. @override
      void paint(PaintingContext context, Offset offset) {
      if(_drawable == null) return;
          _drawable.draw(context.canvas, offset & size,
              fit: _fit, alignment: _alignment);
      }
      
      
      
      
      //RenderLottie的paint方法
      

     安卓端文本绘制

    Android SDK里的Canvas提供了drawText的方法,可以使用画布直接绘制文本。Android实现方案如下:

    1. privatevoid drawCharacter(String character, Paint paint, Canvas canvas) {
      if(paint.getColor() == Color.TRANSPARENT) {
      return;
      }
      if(paint.getStyle() == Paint.Style.STROKE && paint.getStrokeWidth() == 0) {
      return;
      }
          canvas.drawText(character, 0, character.length(), 0, 0, paint);
      }
      

     Flutter文本绘制

    但是在Flutter的Canvas里却没有这种方法,通过调研之后我们发现Flutter提供了一个专门的TextPainter来进行文本的绘制。Flutter实现方案如下:

    1. void _drawCharacter(
      String character, TextStyle textStyle, Paint paint, Canvas canvas) {
      if(paint.color.alpha == 0) {
      return;
      }
      if(paint.style == PaintingStyle.stroke && paint.strokeWidth == 0) {
      return;
      }
      
      
      
      
      if(paint.style == PaintingStyle.fill) {
            textStyle = textStyle.copyWith(foreground: paint);
      } elseif(paint.style == PaintingStyle.stroke) {
            textStyle = textStyle.copyWith(background: paint);
      }
      var painter = TextPainter(
            text: TextSpan(text: character, style: textStyle),
            textDirection: _textDirection,
      );
          painter.layout();
          painter.paint(canvas, Offset(0, -textStyle.fontSize));
      }
      

     安卓端贝塞尔曲线

    我们在背景中提到过,贝塞尔曲线是组成动画的三元素之一。

    我们的动画往往不是线性播放的,如果需要实现先快后慢这样的效果。我们就需要在通过进度获取属性值的时候,使用贝塞尔曲线才能进行从进度到属性值的映射。

    Android SDK里提供了PathInterpolator来实现,我们的JSON文件里使用两个控制点来描述贝塞尔曲线,我们将这两个控制点的坐标传给PathInterpolator,然后在属性值获取的时候,调用插值器的getInterpolation就可以拿到映射后的值了。以下是关键方法实现:

    1. interpolator = PathInterpolatorCompat.create(cp1.x, cp1.y, cp2.x, cp2.y);
      
      
      
      
      public static Interpolator create(float controlX1, float controlY1,
      float controlX2, float controlY2) {
      if(Build.VERSION.SDK_INT >= 21) {
      return new PathInterpolator(controlX1, controlY1, controlX2, controlY2);
      }
      return new PathInterpolatorApi14(controlX1, controlY1, controlX2, controlY2);
      }
      
      
      
      
      public PathInterpolator(float controlX1, float controlY1, float controlX2, float 
                              controlY2) {
              initCubic(controlX1, controlY1, controlX2, controlY2);
      }
      
      
      
      
      private void initCubic(float x1, float y1, float x2, float y2) {
      Path path = newPath();
              path.moveTo(0, 0);
              path.cubicTo(x1, y1, x2, y2, 1f, 1f);
              initPath(path);
      }
      
      
      
      
      //Andorid内置贝塞尔曲线生成关键方法
      

     FLutter贝塞尔曲线

    而Flutter里没有提供这样现成的路径插值器,我们只有根据源码来自行实现。查看Android相关源码之后,我发现我们只需要将JSON里两个控制点的坐标传入Flutter path中的cubicTo方法就可以生成该贝塞尔曲线,然后再自行实现一个入参为时间t,结果为映射后进度p的方法就可以,而具体的实现参考PathInterpolator中的getInterpolation就可以完成。以下是关键方法实现:

    1. interpolator = PathInterpolator.cubic(cp1.dx, cp1.dy, cp2.dx, cp2.dy);
      
      
      
      
      factory PathInterpolator.cubic(
      double controlX1, double controlY1, double controlX2, double controlY2) {
      return PathInterpolator(
              _initCubic(controlX1, controlY1, controlX2, controlY2));
      }
      
      
      
      
      staticPath _initCubic(
      double controlX1, double controlY1, double controlX2, double controlY2) {
      final path = Path();
          path.moveTo(0.0, 0.0);
          path.cubicTo(controlX1, controlY1, controlX2, controlY2, 1.0, 1.0);
      return path;
      }
      
      
      
      
      自定义Flutter贝塞尔曲线生成关键方法
      

    效果对比



    我们当前已经使用fish-lottie实现了一个闭环Demo工程,在里面也同样选取了lottie-android工程里的lottie json文件来进行测试,发现在release包无论是从流畅度,还是动画还原度上,都达到了官方示例App的水准,下面我会用一些动图来对比进行说明:

         

    上述中,前者是使用fish-lottie在flutter页面播放的动画,后者是lottie-android在native页面播放的动画,不难看出fish-lottie无论是从渲染还是播放,都可以达到和lottie-android媲美的程度。

          

    上述中,前者是使用fish-lottie的动态文本动画,后者是lottie-android的动态文本动画,可以看出fish-lottie在动态的属性和文本实时渲染方面也可以提供不输于lottie-android的效果。

    而且因为我们的文本绘制实现方案与原生有一定的差异,我们可以更好的将字体样式接口暴露出来,让开发者不止可以对文本进行定制,在样式方面也可以进行实时动态定制,这是目前lottie-android没有提供的功能。

    后续展望——从静态到交互


    当前Lottie的使用场景都仅仅是一段动画的静态播放。例如点赞之后会出现大拇指的动画,收藏之后会出现心形的动画,最多通过进度来控制一些整个动画的播放。但是在实现整个框架的过程中,我发现lottie-android其实已经具备一些可交互的能力,使用方法如下:

    1. val shirt = KeyPath("Shirt", "Group 5", "Fill 1")
       animationView.addValueCallback(shirt, LottieProperty.COLOR) { Colors.XXX } //需定制的颜色
      

    以上代码实现的效果如下图所示:


     lottie-android实现方案

    从以上的代码我们可以看出,要想实现动态属性控制,我们需要传入三个参数,第一个参数类似于一个定位符,需要通过路径的形式来定位到我们想进行属性控制的矢量图形内容,第二个参数是一个属性枚举变量,它表明了我们控制的属性类型,最后一个参数是一个回调函数,需要返回我们动态改变的目标值。因为上层组件层和lottie-android有比较大的差异,所以fish-lottie当前只完成了动画播放的能力支持,可交互能力正在开发当中。

     fish-lottie实现思路

    因为上层组件的双端实现的差异性和UI构建特性,Flutter中我们一般不会获取Widget的引用来调用它的方法。所以不能像lottie-android一样直接使用lottieAnimationView.addValueCallback()来进行动态属性控制,我们在实现动画的进度控制的时候其实也遇到过一样的问题。

    所以我们的实现思路这其实和AnimationCtroller一样,我们也实现一个PropertiesController(属性控制器),把我们需要修改的一系列的目标图形,目标属性和回调函数传递给这个控制器,再把这个控制器作为LottieAnimationView构造函数的一个参数传递给LottieDrawable,然后由这个属性控制器来发起目标图形绘制类的匹配和回调函数设置。底层的绘制类和帧动画类中的方法和lottie-android保持一致。基本的思路和lottie-android保持一致,只是LottieAnimationView不再承担属性控制的责任,而是由PropertiesController来承担。

     落地方向

    有了交互能力,我们不再只能控制动画的播放了。我们可以通过获取用户的点击触摸事件来进行动画上的反馈,以此来实现一些比较复杂的交互动画。

    如上图所示,这个搜索框背景的动画效果如果开发者直接进行开发是很难实现的。

    而通过lottie我们就有比较清晰的思路,制作一个流动的果冻背景动画,两个内容动画,一个黑夜星月动画,一个白天云彩动画,我们可以通过点击事件来控制果冻背景动画背景在黑色和蓝紫渐变色之间进行切换,以及改变一下它的局部形状,还有两个内容动画的显示和隐藏。

    在点击第一个Pillow按钮时把果冻背景动画颜色切换为蓝紫渐变色,然后显示云彩动画。

    点击第二个Baby按钮时把果冻背景动画的背景色切换为黑色,然后显示星月动画。

    对于云彩动画的3D效果,我们可以通过手机设备的陀螺仪传感器来获取手机的侧偏移角度,然后根据角度来改变云彩动画各个元素的位置。这样之前开发成本过高甚至无法实现的复杂交互动画效果,就可以通过lottie很轻松的实现出来了。

    ✿  拓展阅读

    作者|陈昱(岑彧)

    编辑|橙子君

    出品|阿里巴巴新零售淘系技术

    展开全文
  • dart语言如何学习 Dart 2.5是Google开发的最新版本,可编译为机器代码或JavaScript ,它包括机器学习支持的代码完成的技术预览以及用于调用C代码的外部函数接口。 [更深入: 如何开始使用WebAssembly 。 • ...

    dart语言如何学习

    Dart 2.5是Google开发的最新版本,可编译为机器代码或JavaScript ,它包括机器学习支持的代码完成的技术预览以及用于调用C代码的外部函数接口。

    [更深入: 如何开始使用WebAssembly WebAssembly的下一步 8个使WebAssembly崭露头角的项目 •那么, WebAssembly的下一步究竟是什么? | 通过InfoWorld的App Dev Report新闻通讯了解编程方面的热门话题。 ]

    Google Dart团队已经发布了Dart 2.5 SDK,它具有以下beta功能:

    • ML Complete,使用机器学习来解决由于要探索的API数量越来越多而可能完成的列表变得更长的情况。 基于给定上下文训练可能成员出现的基于TensorFlow Lite的模型。 ML Complete内置在Dart分析仪中。 在启用Dart的编辑器(例如Android Studio和Visual Studio Code )中可用。
    • dart:ffi Dart-C互操作的外部函数接口,允许调用主机OS上基于C的系统API或调用基于C的系统库。 外部函数接口是为了响应开发人员要求更好地支持从Dart调用C代码而做出的响应。 到目前为止,这种支持仅限于通过本机扩展将其深度集成到Dart VM中。

    Dart 2.5还具有对定义常量表达式的扩展支持,包括使用强制转换以及控制Dart 2.3中可用的流和收集功能的功能。 Dart的未来计划要求在默认情况下实现健全的非空跟踪类型的系统,这可能会带来性能上的好处。 还可以添加并发改进,以更好地利用智能手机上的多核处理器。

    一旦被视为JavaScript的竞争对手 ,Dart现已被定位为针对任何平台上的快速应用程序的客户端优化语言。 它可以用于编写功能齐全的移动,Web和服务器端应用程序以及命令行脚本。

    在哪里下载Dart

    可以在dart.dev网站上找到有关访问Dart SDK的说明。

    翻译自: https://www.infoworld.com/article/3439339/dart-language-taps-machine-learning-for-code-completion.html

    dart语言如何学习

    展开全文
  • Flutter中在禁用了dart:mirror,无法使用反射情况下如何得到类相关信息? Dart的文件不限制是class,可以是function、class,因而在注解扫描的范围不同的情况下如何拿到层层信息而不仅仅是toplevel信息? 提取到注解...
  • dart异步编程by Mohammed Salman ... 如何通过期货将一些异步编程引入Dart (How to bring a little asynchronous programming to Dart with futures) Asynchronous programming is a form of parallel programm...
  • 如何判断dart数据类型

    万次阅读 2019-01-10 11:17:47
    import 'dart:mirrors'; getTypeName(dynamic obj) { return reflect(obj).type.reflectedType.toString(); } void main() { var val = "\"Dart is dynamically typed (with optional type ...
  • 学习Dart,可以使用VScode作为编辑器 首先去这个网址下载http://www.gekorm.com/dart-windows/ 然后配置环境变量 使用vscode安装Dart插件 Dart和Flutter插件配合是为Flutter准备的 Atom One Dark Theme 是一...
  • 文章目录概述Dart的异常处理系统实践总结 概述 不知不觉使用Flutter已经5个月了,项目已经基本上告一段落了,所以是到了回头总结的时候了。任何事情只有不断学习,不断总结才能领先于你的同行… 今天准备总结一下...
  • Dart List如何扩容的

    2020-09-03 12:41:13
    谁来解释一下,Dart里面List数组扩容机制[face]qq:13.gif[/face]
  • Dart如何实现库(library),library的可见性。如何使用Dart中的库(library),Dart如何只导入库的一部分,Dart如何懒加载一个库,Dart如何为库指定前缀。
  • 如何创建DaRT 10恢复映像

    千次阅读 2019-12-25 20:01:22
    如何创建DaRT 10恢复映像 ERD Repair Disk是不受挑战的管理员助手,可以恢复Windows出于多种原因而拒绝启动的情况。在本文中,我们将介绍如何使用一组工具来恢复Windows 10或Windows Server 2016来创建自己的启动...
  • 如何理解dart的mixin

    2019-10-02 18:13:43
    mixin和implements有着本质的区别,implements是实现,dart中任何类都有一个隐形的接口,都可以被其他类实现 但是混入却有着限制,首先一个类如果定义了构造函数,哪怕是无参的,也是无法被别的类混入的,这在语...
  • 如何Dart中构建这个模式? A: 在Dart中构建一个单例很容易,以工厂构造方法为例: class Singleton { static final Singleton _singleton = Singleton._internal(); factory Singleton() { return _...
  • Flutter中在禁用了dart:mirror,无法使用反射情况下如何得到类相关信息? Dart的文件不限制是class,可以是function、class,因而在注解扫描的范围不同的情况下如何拿到层层信息而不仅仅是toplevel信息? 提取到注解...
  • 用Android Studio如何创建纯Dart工程???这个问题目前网上没有答案。 尝试之后,总结如下: 首先,先确定Android Studio 已经安装了Dart插件; 然后: 第一步,创建一个文件夹 mkdir dart_learn 第二步,创建一...
  • polymer-dart-patterns, 说明如何执行 Polymer.dart 方法的小有用Fragment/示例 聚合物 Fragment说明如何做聚合物方法的小,有用,Fragment/样品。https://github.com/PolymerLabs/polymer-snippets 中 polymer.js ...
  • 如何使用Dart的Stream(一)

    千次阅读 2019-10-28 09:49:29
    2018年,谷歌推出了Dart中最重要的一个特性-- Stream。官方对其介绍是: Widgets + Streams = Reactive Flutter App 简单来说就是,Stream被创建的初衷就是为了实现Flutter的响应式编程。 Stream 简介 Stream(流) ...
  • dart 07.1Dart执行模型

    2020-06-19 18:52:45
    Dart 是一种单线程语言 首先我们需要记住 ...那么,Dart如何管理事件执行呢 看下dart的 事件循环机制 当启动一个Flutter/Dart 程序时 将创建启动一个新的线程或者进程Dart中叫Isolate这个Iolate 将作
  • Dart2基础--异步支持

    2019-06-02 22:20:42
    Dart如何处理异步操作;Dart声明异步函数;Dart中Future的用法; Dart中流(Streams)的使用。Dart中await和async的使用;Dart中的Isolates的使用;
  • 环境:Win 10 +Flutter SDK(已配置环境变量) win + R 进入运行输入CMD...输入:where flutter dart 或者which flutter dart 回车,即可看到,如下图; 注意:where 命令获取的路径一般都是在环境变量配置了; ...
  • [Dart]Dart概述

    2018-03-08 06:50:02
    关于Dart,下面两个资源在...一组指南,向你展示如何编写最好的Dart代码。是有关于Dart风格、文档、使用和设计的指南。 其他资源 Strong Mode Dart How and why to write sound Dart code, and how to use stro...
  • 如何Dart调用RESTful Web Service

    千次阅读 2017-01-10 17:07:24
    Dart是这个新操作系统的主要编程语言。最近发现Google的移动开发框架Flutter也使用Dart,于是就想看下。 开发环境 安装Dart SDK。 安装IDE IntelliJ IDEA。Community版本免费。运行IDE,通过File > Settings >...
  • In this short tutorial we will implement the Delegation design pattern in two languages: Dart and Swift. During the process, we will learn what a mixin in Dart is and compare the final implementation ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 962
精华内容 384
关键字:

dart如何