马耕得最多,但集体农庄董事长并没有成为



最近,在移动社区中,人们经常可以听到有关Flutter,React Native的信息。 我很想了解这些作品的收益。 在开发应用程序时,它们将真正改变生活。 结果,创建了4个(从执行的功能来看完全相同)应用程序:本机Android,本机iOS,Flutter,React本机。 在本文中,我描述了我从经验中学到的知识,以及如何在所考虑的解决方案中实现应用程序的相似元素。

评论:本文的作者不是专业的跨平台开发人员。 而所写的只是这些平台的新手开发人员的外观。 但是我认为,对于那些正在使用正在考虑的解决方案之一并且希望为两个平台编写应用程序或改善iOS和Android之间的交互过程的人员而言,这篇评论将是有用的。

作为开发的应用程序,决定制作一个“运动计时器”,该计时器将帮助从事体育运动的人进行间歇训练。

该应用程序包含3个屏幕。


计时器操作画面


锻炼历史记录屏幕


计时器设置屏幕

作为开发人员,此应用程序对我来说很有趣,因为我感兴趣的以下组件将受到其创建的影响:
-布局
-自定义视图
-使用UI列表
-多线程
-数据库
-网络
-键值存储

重要的是要注意,对于Flutter和React Native,我们可以创建到应用程序本机部分的桥(通道),并使用它来实现操作系统提供的所有内容。 但是我想知道开箱即用的框架是什么。

选择开发工具


对于iOS的本机应用程序-我选择了Xcode开发环境和Swift编程语言。 对于本机Android-Android Studio和Kotlin。 用JS编程语言WebStorm开发的React Native。 Flutter-Android Studio和Dart。

在Flutter上进行开发时,一个有趣的事实对我来说似乎是,从Android Studio(用于Android开发的主要IDE)开始,您就可以在iOS设备上运行该应用程序。



项目结构


本机iOS和Android项目的结构非常相似。 这是扩展名为.storyboard(iOS)和.xml(Android)的布局文件,依赖性管理器Podfile(iOS)和Gradle(Android),扩展名为.swift(iOS)和.kt(Android)的源代码文件。


Android项目结构


iOS项目结构

Flutter和React Native结构包含Android和iOS文件夹,其中包含Android和iOS的常规本机项目。 Flutter和React Native作为库连接到本机项目。 实际上,当您在iOS设备上启动Flutter时,通常的本机iOS应用程序会在连接Flutter库的情况下启动。 对于React Native和Android,一切都相同。

Flutter和React Native还包含package.json(React Native)和pubspec.yaml(Flutter)依赖项管理器以及带有.js(React Native)和.dart(Flutter)扩展名的源文件,这些扩展名还包含布局。


Flutter项目结构


反应本机项目结构

布局图


对于本机iOS和Android,有视觉编辑器。 这大大简化了屏幕的创建。


适用于本机Android的可视编辑器


适用于本机iOS的可视编辑器

没有用于React Native和Flutter的可视编辑器,但是支持热重载功能,这至少在某种程度上简化了UI的工作。


在Flutter中进行热重启


在React Native中热重启

在Android和iOS中,布局存储在扩展名为.xml和.storybord的单独文件中。 在React Native和Flutter中,布局是直接从代码派生的。 在描述ui速度时应注意的重要一点是,Flutter具有自己的呈现机制,框架的创建者承诺使用60 fps。 React Native使用使用js构建的本机ui元素,这导致它们过多的嵌套。

在Android和iOS中,要更改View属性,请使用代码中的链接,例如,要更改背景色,请直接对对象进行更改。 对于React Native和Flutter,还有另一种理念:我们更改setState调用内的属性,并根据更改后的状态重新绘制视图本身。

为每个选定解决方案创建计时器屏幕的示例:


Android计时器屏幕的布局


iOS上的计时器屏幕布局

Flutter计时器屏幕的布局

@override Widget build(BuildContext context) { return Scaffold( body: Stack( children: <Widget>[ new Container( color: color, child: new Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text( " ${getTextByType(trainingModel.type)}", style: new TextStyle( fontWeight: FontWeight.bold, fontSize: 24.0), ), new Text( "${trainingModel.timeSec}", style: TextStyle( fontWeight: FontWeight.bold, fontSize: 56.0), ), new Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text( " ${trainingModel.setCount}", style: TextStyle( fontWeight: FontWeight.bold, fontSize: 24.0), ), Text( " ${trainingModel.cycleCount}", style: TextStyle( fontWeight: FontWeight.bold, fontSize: 24.0), ), ], ), ], ), padding: const EdgeInsets.all(20.0), ), new Center( child: CustomPaint( painter: MyCustomPainter( //0.0 trainingModel.getPercent()), size: Size.infinite, ), ) ], )); } 

React Native计时器屏幕布局

  render() { return ( <View style={{ flex: 20, flexDirection: 'column', justifyContent: 'space-between', alignItems: 'stretch', }}> <View style={{height: 100}}> <Text style={{textAlign: 'center', fontSize: 24}}> {this.state.value.type} </Text> </View> <View style={styles.container}> <CanvasTest data={this.state.value} style={styles.center}/> </View> <View style={{height: 120}}> <View style={{flex: 1}}> <View style={{flex: 1, padding: 20,}}> <Text style={{fontSize: 24}}>  {this.state.value.setCount} </Text> </View> <View style={{flex: 1, padding: 20,}}> <Text style={{textAlign: 'right', fontSize: 24}}>  {this.state.value.cycleCount} </Text> </View> </View> </View> </View> ); } 

自订检视


熟悉解决方案后,对我来说很重要的一点是,可以创建任何视觉组件。 也就是说,在正方形,圆形和路径的水平上绘制ui。 例如,计时器指示器就是这样的视图。



对于本机iOS来说没有问题,因为可以访问“图层”,您可以在其上绘制所需的任何内容。

  let shapeLayer = CAShapeLayer() var angle = (-Double.pi / 2 - 0.000001 + (Double.pi * 2) * percent) let circlePath = UIBezierPath(arcCenter: CGPoint(x: 100, y: 100), radius: CGFloat(95), startAngle: CGFloat(-Double.pi / 2), endAngle: CGFloat(angle), clockwise: true) shapeLayer.path = circlePath.cgPa 

对于本机Android,您可以创建一个从View继承的类。 然后重新定义onDraw(Canvas canvas)方法,在该方法的参数上绘制Canvas对象。

  @Override protected void onDraw(Canvas canvas) { pathCircleOne = new Path(); pathCircleOne.addArc(rectForCircle, -90, value * 3.6F); canvas.drawPath(pathCircleBackground, paintCircleBackground); } 

对于Flutter,您可以创建一个从CustomPainter继承的类。 并覆盖paint(Canvas canvas,Size size)方法,该方法在参数中传递Canvas对象-即与Android中非常相似的实现。

  @override void paint(Canvas canvas, Size size) { Path path = Path() ..addArc( Rect.fromCircle( radius: size.width / 3.0, center: Offset(size.width / 2, size.height / 2), ), -pi * 2 / 4, pi * 2 * _percent / 100); canvas.drawPath(path, paint); } 

对于React Native,找不到开箱即用的解决方案。 我认为这是因为视图仅在js上描述,并且由本机ui元素构建而成。 但是您可以使用react-native-canvas库,该库提供对画布的访问。

  handleCanvas = (canvas) => { if (canvas) { var modelTimer = this.state.value; const context = canvas.getContext('2d'); context.arc(75, 75, 70, -Math.PI / 2, -Math.PI / 2 - 0.000001 - (Math.PI * 2) * (modelTimer.timeSec / modelTimer.maxValue), false); } } 

使用UI列表




适用于Android,iOS,Flutter的算法-解决方案非常相似。 我们需要指出列表中有多少个元素。 并通过元素编号指定要绘制的单元格。
IOS使用UITableView绘制列表,您需要在其中实现DataSource方法。

  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return countCell } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return cell } 

对于Android,他们使用RecyclerView,在其适配器中我们实现了类似的iOS方法。

 class MyAdapter(private val myDataset: Array<String>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() { override fun onBindViewHolder(holder: MyViewHolder, position: Int) { holder.textView.text = myDataset[position] } override fun getItemCount() = myDataset.size } 

对于抖动,他们使用ListView,在构建器中实现了类似的方法。

 new ListView.builder( itemCount: getCount() * 2, itemBuilder: (BuildContext context, int i) { return new HistoryWidget( Key("a ${models[index].workTime}"), models[index]); }, ) 

React Native使用ListView。 该实现类似于先前的解决方案。 但是没有引用列表中元素的数量和数量,在DataSource中我们设置了元素列表。 并且在renderRow中,我们根据出现的元素实现单元的创建。

 <ListView dataSource={this.state.dataSource} renderRow={(data) => <HistoryItem data={data}/>} /> 

多线程,异步


当我开始处理多线程异步时,各种各样的解决方案使我感到震惊。 在iOS中-这是GCD,在Android中是Operation-在AsyncTask,Loader,协程中,在React Native中是Promise,异步/等待,在Flutter-Future中是Stream。 一些解决方案的原理相似,但实现方式仍不同。
心爱的Rx营救了您。 如果您还没有爱上他,我建议您学习。 在我考虑的所有解决方案中,形式都是:RxDart,RxJava,RxJs,RxSwift。

Rxjava的

  Observable.interval(1, TimeUnit.SECONDS) .subscribe(object : Subscriber<Long>() { fun onCompleted() { println("onCompleted") } fun onError(e: Throwable) { println("onError -> " + e.message) } fun onNext(l: Long?) { println("onNext -> " + l!!) } }) 

Rxswift

 Observable<Int>.interval(1.0, scheduler: MainScheduler.instance) .subscribe(onNext: { print($0) }) 

Rxdart

  Stream.fromIterable([1, 2, 3]) .transform(new IntervalStreamTransformer(seconds: 1)) .listen((i) => print("$i sec"); 

Rxjs

 Rx.Observable .interval(500 /* ms */) .timeInterval() .take(3) .subscribe( function (x) { console.log('Next: ' + x); }, function (err) { console.log('Error: ' + err); }, function () { console.log('Completed'); }) 

如您所见,代码在所有四种语言中看起来都非常相似。 将来,如有必要,它将帮助您从一种用于移动开发的解决方案过渡到另一种。

资料库


在移动应用程序中,标准是SQLite数据库。 在每个考虑的解决方案中,都会编写一个包装程序来使用它。 Android通常使用ORM Room。

在iOS上,核心数据。 Flutter可以使用sqflite插件。

在React Native中-react-native-sqlite-storage。 所有这些解决方案的设计都不同。 为了使应用程序看起来像,您必须手动编写Sqlite查询,而无需使用包装器。

最好使用Realm数据存储库,该库使用其自己的内核进行数据存储。 在iOS,Android和React Native上受支持。 Flutter当前没有支持,但是Realm工程师朝着这个方向努力。

Android上的领域

 RealmResults<Item> item = realm.where(Item.class) .lessThan("id", 2) .findAll(); 

iOS上的领域

 let item = realm.objects(Item.self).filter("id < 2") 

React Native领域

 let item = realm.objects('Item').filtered('id < 2'); 

键值存储


本机iOS使用UserDefaults。 在本机Android中,偏好设置。 在React Native和Flutter中,您可以使用作为本机键值存储(SharedPreference(Android)和UserDefaults(iOS))包装的库。

安卓系统

 SharedPreferences sPref = getPreferences(MODE_PRIVATE); Editor ed = sPref.edit(); ed.putString("my key'", myValue); ed.commit(); 

的iOS

 let defaults = UserDefaults.standard defaults.integer(forKey: "my key'") defaults.set(myValue, forKey: "my key") 

颤动

 SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.getInt(my key') prefs.setInt(my key', myValue) 

反应本机

 DefaultPreference.get('my key').then(function(value) {console.log(value)}); DefaultPreference.set('my key', myValue).then(function() {console.log('done')}); 

联播网


为了在本机iOS和Android中使用网络,有很多解决方案。 最受欢迎的是Alamofire(iOS)和Retrofit(Android)。 React Native和Flutter具有自己的独立于平台的客户端,可以上网。 所有客户的设计都非常相似。

安卓系统

 Retrofit.Builder() .baseUrl("https://timerble-8665b.firebaseio.com") .build() @GET("/messages.json") fun getData(): Observable<Map<String,RealtimeModel>> 

的iOS

 let url = URL(string: "https://timerble-8665b.firebaseio.com/messages.json") Alamofire.request(url, method: .get) .responseJSON { response in … 

颤动

 http.Response response = await http.get('https://timerble-8665b.firebaseio.com/messages.json') 

反应本机

 fetch('https://timerble-8665b.firebaseio.com/messages.json') .then((response) => response.json()) 

开发时间




由于我是一名Android开发人员,因此根据我的开发时间得出任何结论可能是不正确的。 但是我认为对于iOS开发人员来说,进入Flutter和Android技术似乎比在React Native中容易。

结论


开始写一篇文章,我知道我会在结论中写些什么。 我会告诉您我更喜欢哪种解决方案,不应该使用哪种解决方案。 但是,然后,在与在生产中使用这些解决方案的人员交谈之后,我意识到我的结论是错误的,因为我从经验的角度看待了一切。 最主要的是,我意识到对于所考虑的每个解决方案,都有一些最适合的项目。 有时,企业跨平台应用程序的利润确实要比在两个本地应用程序的开发上投入更多的利润。 而且,如果某些解决方案不适合您的项目,请不要认为它在原则上是不好的。 希望本文对您有所帮助。 感谢您的结束。

关于文章编辑,请写一封个人电子邮件,我会很乐意解决所有问题。

Source: https://habr.com/ru/post/zh-CN430566/


All Articles