还在写iOS?是时候学一下Flutter了

8,615 阅读7分钟

文章概述

本人之前主要从事iOS开发工作,刚好Flutter文档中有一篇Flutter for iOS developers的文档,之前两篇文章,我们大致上体验了Flutter,这篇文中我将从iOS开发者的角度来学习Flutter,与官方文档不同的是,这篇文章会更注重实践。由于文档很长,我将用两篇文章讲解。这是第一篇。通过阅读本篇文章,你讲学习到如下内容:

  • Widget与UIView的区别。
  • 导航,如何在页面间跳转

Widget与UIView

对于我们iOS开发者来讲,UIView再熟悉不过,它是我们构建界面的必备元素。而在Flutter中,我们可以将Widget看做是UIView,但它与UIView并不是完全等价的。

对于Widget而言,它是不可变的,当Widget所描述的界面需要改变的时候,Flutter是重新构建一个Widget实现的。对于UIView而言,界面改变的时候并不是去重新绘制(除非调用setNeedsDisplay()方法),而是属性的改变。

更确切的说,Widget更轻量级,因为它是不可变的。它只是UI的描述,是对下层真实视图对象的描述,而不是视图本身,也不会去绘制任何东西。

Flutter包含 Material Components组件库。这是一个遵循 Material Design guidelines 规范实现的控件,这是一个非常灵活的设计系统,并针对所有的系统进行了优化,包含iOS系统。

我们可以使用 Cupertino widgets 组件来实现遵循 Apple’s iOS design language 的界面。

如何更新Widgets

Flutter中更新widgets需要去操作它的state,不向UIView一样,直接修改对象的属性即可。Flutter中包含有状态Widgets和无状态Widgets,分别用StatefulWidgetStatelessWidget 表示。

举例来说:如果你只需要展示一个图标,并且它不会被改变,这时使用StatelessWidget 即可完成,但如果你需要根据网络请求返回的结果来动态的设置图片就需要使用StatefulWidget来完成了。

StatefulWidgetStatelessWidget的最大区别是,StatefulWidget拥有一个存储状态数据的State对象,并且在整个Widget Tree构建的过程中一直携带者它,永不丢失。

有一个简单的记法:如果一个Widget在它的build方法之外改变,它就是有状态的,如果在第一次构建之后永不改变,它就是无状态的。一个有状态的Widget的父控件可以是无状态的,只要父控件不受子控件状态的影响即可。如Text就是一个无状态的Widget组件。

// class Text extends StatelessWidget
Text(
  'I like Flutter!',
  style: TextStyle(fontWeight: FontWeight.bold),
);

不难发现Text没有携带任何状态信息,只有通过构造函数传递的信息。

如果要实现通过点击事件来改变Text的文本信息,需要通过将Text用StatefulWidget包裹一下来实现,

代码如下:

flutter布局.png

效果如下:

改变文本内容.gif

如何对Widget布局

In iOS, you might use a Storyboard file to organize your views and set constraints, or you might set your constraints programmatically in your view controllers. In Flutter, declare your layout in code by composing a widget tree.

The following example shows how to display a simple widget with padding:

在编写iOS代码的时候,你可以用Storyboard来构建视图和设置约束,或者在viewContoller中编写约束代码。在Flutter中,我们将布局代码写在Widget树种。

下面的例子展示了一个使用padding的Widget:

flutter布局.png

代码效果如下:

![布局效果图.jpg](https://test.demo-1s.com/images/2019/05/18/kqr78c99LPRXDpAz.jpg)

你可以对任何一个Widget使用padding属性,它模拟了iOS中的约束功能。

widget layout这篇文章详细介绍了Flutter所提供的布局功能。

如何从布局中添加或者删除一个组件

在iOS中,我们可以调用父视图的addSubview() 方法为父视图添加可以子视图,或者调用子视图的removeFromSuperview()方法将自身从其父视图中移除,通过以上两个方法可以动态的添加或者移除子视图。在Flutter中,由于widget是不可变的,没有与addSubvie()等价的功能函数。在flutter中可以使用一个bool型变量来控制子视图是否需要创建。

来看下面的例子:通过一个toggle变量来控制子视图显示的内容:

更新子视图.png

效果如下:

变更子视图.gif

如何设置Widget 动画

在iOS中我们可以使用animate(withDuration:animations:)方法为一个view设置动画,在Flutter中需要使用第三方库来包装widget而不是使用具体动画属性的widge。

在Flutter中,使用AnimationController 它是Animation<double> 类型,可以控制一个动画的终止,执行,暂停和反转。它需要一个Ticker ,当垂直同步信号时产生在它执行的每一帧都产生一个0到1之间的线性插值。你可以创建一个或者多个动画绑定到这个controller上。

举个例子:

你可能使用CurvedAnimation依据插值曲线来完成一个动画,在这种场景下,controller是动画执行的"主人",CurvedAnimation计算用来计算的曲线会代替controller默认线性模式。

当构建widget树你将Animation赋值给一个widget的动画属性,比如,FadeTransition的不透明度,然后告诉controller开始执行动画。

下面的例子展示了如何写一个淡出动画,当点点击了按钮之后,logo会淡出显示。

动画.png

效果如下:

动画.gif

更多动画相关的资料可以参考 Animation & Motion widgets, 和 Animations tutorial, 还有 Animations overview

如何绘制到屏幕上

在iOS中,我们可以使用CoreGraphics可以将线条或者图形绘制到屏幕上。Flutter中拥有一套不同的Api,它基于Canvas这个类,并结合 CustomPaintCustomPainter 两个类可以帮你实现屏幕绘制需求,CustomPainter 实现了绘制画布的算法。

在Flutter中,实现画笔程序 可以参考Collin 在 StackOverflow 上的答案,源码在kitttn‘s github

Flutter画笔.png
\

效果如下:

画笔.gif

widget的透明度在哪

在iOS中每个组件都有.opacity 或者 .alpha 属性表示透明度。在Flutter中,要是先透明需要使用透明的widget包装一下widget才能实现。

如何实现自定义widget

在iOS中,我们可以继承UIView,或者使用已经存在的视图,通过重新和实现方法来实现期望的行为。在Flutter中,构造一个自定的widget需要将系统提供的widget组合在一起。

举个例子:如何自定义一个CustomButton, 它的构造方法中携带一个label?可以通过将RaisedButton和label结合在一起,如下代码:

自定义widget.png

导航

如何在多个页面之间跳转

在iOS中,多个viewController之间的转化可以通过UINavigationController来实现,它管理着一组viewController的显示。

Flutter中有类似的实现,使用NavigatorRouter来实现,一个Router是一个app中screen或者page的抽象,Navigator是一个widget,它管理着多个router。router近似于iOS中的UIViewController,navigator的工作机制和iOS中的UINavigationController一致。所以它可以使用push()pop()来将页面导航到某个视图或者返回到某个视图。

在页面之间跳转,你可以使用下面的几个方式:

  • 指定一个由router名称构成的map。
  • 直接跳转到一个路由

页面跳转.png

如何跳转到另一个app

在 iOS 中,要跳转到其他 App,你需要一个特定的 URL Scheme。对系统级别的 App 来说,这个 scheme 取决于 App。为了在 Flutter 中实现这个功能,你可以创建一个原生平台的整合层,或者使用现有的 plugin,例如 url_launcher

如何跳转到iOS原生的viewController

调用SystemNavigator.pop()方法相当于调用下面iOS代码:

UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController;
  if ([viewController isKindOfClass:[UINavigationController class]]) {
    [((UINavigationController*)viewController) popViewControllerAnimated:NO];
  }

如果这样达不到你的期望,你可以写自己的跨平台方案来调用iOS代码。platform channel

小结

本文主要从iOS开发者的角度讲述了Flutter开发中的几个点,不知道你是否有所收获,本文还有第二篇文章,敬请期待。

本文主要参考Flutter官方文档Flutter中文网

由于排版原因,文中我使用了图片的形式展示代码,如果你需要源码,可以关注我的公众号,回复关键字"flutter"获取相关代码。

本文首发自微信公众号【RiverLi】,欢迎你的关注与投稿。