Flutter推出來已經有一段時間了,前一陣Google IO大會後發佈了Beta3。基於Flutter的 app能夠一次編寫,同時在Android和iOS平臺上跑,而且能給用戶帶來徹底原生的體驗。咱們都知道跨平臺開發還有Hybrid,React Native以及Weex等方案,這些解決方案都是從Web開發的角度向Native開發演進,其技術基礎都是HTML、CSS和Javascript等Web技術,對於沒有接觸過Web開發的Native app程序員來說,門檻是比較高的。而Flutter給個人感受是從Native開發向Web開發演進,Native app程序員應該能比較舒服的入門。java
做爲一名Android開發者,我始終認爲跨平臺是移動端開發的發展趨勢,可是哪種技術方案會最終勝出,還有待時間的檢驗。Flutter對Native開發者友好,而且吸納了React等Web開發的前沿技術,能夠做爲Native程序員學習跨平臺開發的很好的路徑。android
爲了學習Flutter, 我試着開發了一個簡單的新聞app,涵蓋了一些移動端app比較基礎的功能。接下來我會對照這個app來給你們介紹一下Flutter開發的一些知識。整個工程源碼你們能夠從Github獲取。若有任何問題或建議,歡迎你們提issue。ios
本文是Android開發者的Flutter入門的第一部分,有一些技術細節放在了第二部分介紹,戳這裏查看 Android開發者的Flutter入門(二)。git
Flutter是用Dart語言開發的。因此在開發Flutter app以前,須要咱們對Dart語言有必定的掌握。對於Android程序員來說,學習Dart是比較快的一個過程,和Java同樣,Dart也是面向對象的語言。不少地方都是相通的。須要注意的是對於Dart裏的類(各類構造函數,getter
,setter
),函數(函數也是對象,函數內部能夠定義函數,函數能夠做爲參數和返回值, 閉包),以及異步(Future
,async
和await
)等地方要反覆揣摩,仔細體會。程序員
有了Dart的基礎,那麼咱們就能夠開始嘗試開發個Flutter app了。github
首先你要配置Flutter的開發環境。對於咱們Android程序員來說,那就是再熟悉不過的Android Studio了。整個配置過程是比較簡單的,你們照文檔走就是了。不過要注意一點,若是你沒有穿牆的的話,須要看一下這裏。web
好了,環境已經弄好了,可能你已經把Hello World
也跑起來了。那麼咱們就用Flutter來開發一個稍微像樣點的app吧。json
咱們開發的是一個簡單新聞app。主要包含兩個頁面,一個首頁,顯示一個頭條新聞的列表,點擊裏面的某個頭條,就跳轉到那條新聞的詳情頁面。這個簡單的app包含了一些比較基礎的功能:api
如何經過網絡從服務器請求數據? Android程序員:我用OkHttp。bash
如何解析返回數據? Android程序員:我用Gson。
返回的數據如何在界面上顯示出來? Android程序員:我用RecylerView。
如何顯示網絡圖片? Android程序員:我用Glide。
頁面之間如何跳轉? Android程序員:我用Intent。
如何加入下拉刷新? Android程序員:我用SwipeRefreshLayout。
接下來咱們就說說以上這些功能如何在Flutter裏實現,先來兩張截圖感覺一下:
新聞源咱們使用的是newsapi.org。你只要申請一個apiKey就能從他家獲取json格式的頭條新聞數據。至於詳情的話須要用webview直接打開對應的新聞url。
網絡返回的JSON數據格式如圖所示:
這裏面"articles"字段的值是個jsonArray,內容是頭條新聞的列表。在Android中咱們能夠用Gson來把json數據反序列化爲對象。那再Flutter中如何來作反序列化呢?
首先咱們引入必要的庫: 在pubspec.yaml加入如下內容
dependencies:
json_annotation: ^0.2.3
dev_dependencies:
build_runner: ^0.8.0
json_serializable: ^0.5.0
複製代碼
而後在終端中運行flutter packages get
(或者點擊"Packages Get"的提示,相似你更改.gradle文件之後Android Studio顯示的同步提示)
接下來就是model類了
import 'package:json_annotation/json_annotation.dart';
part "news.g.dart";
@JsonSerializable()
class News extends Object with _$NewsSerializerMixin {
final String author;
final String title;
final String description;
final String url;
final String urlToImage;
final String publishedAt;
final Source source;
News(this.author,
this.title,
this.description,
this.url,
this.urlToImage,
this.publishedAt,
this.source);
factory News.fromJson(Map<String, dynamic> json) => _$NewsFromJson(json);
}
@JsonSerializable()
class Source extends Object with _$SourceSerializerMixin {
final String id;
final String name;
Source(this.id, this.name);
factory Source.fromJson(Map<String, dynamic> json) => _$SourceFromJson(json);
}
@JsonSerializable()
class NewsList extends Object with _$NewsListSerializerMixin {
final String status;
final int totalResults;
final List<News> articles;
final code;
final message;
NewsList(this.status, this.totalResults, this.articles, this.code, this.message);
factory NewsList.fromJson(Map<String, dynamic> json) => _$NewsListFromJson(json);
}
複製代碼
看起來既有熟悉的字段,又有陌生的註解和代碼?不要緊,只要你按照這裏的要求來作就好了。能夠看出反序列化是在_$NewsListFromJson(json);
裏完成的。那麼這個函數從何而來呢?這須要咱們運行命令flutter packages pub run build_runner build
來生成對應的代碼。生成的代碼存放在news.g.dart中。
至此model類以及反序列化咱們就已經作完了,那麼下面就看看網絡請求怎麼來實現。
對應於Android中的OkHttp, Flutter中的網絡請求庫是http.dart。以下所示,代碼比較簡單
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_news/model/news.dart';
class NewsApi {
static Future<NewsList> getHeadLines({String category: "general", int page: 0}) async {
final response = await http.get(
"https://newsapi.org/v2/top-headlines?country=us&apiKey=efaf5fb66d104385ad40c73d4fd4acb1&page=$page&category=$category");
return compute(parseResult, response.body);
}
static NewsList parseResult(String respond) {
return NewsList.fromJson(json.decode(respond));
}
}
複製代碼
咱們都知道在Android中網絡請求須要在子線程來作,不然會阻塞主線程;請求的結果經過callback來返回給主線程。 而在Flutter中則更加簡潔,經過async
和await
,避免了難看的callback代碼嵌套。 函數getHeadLines
用來作http請求,在走到await
的時候會"等待"後面的http.get
函數執行完畢,返回值賦給response
,以後繼續執行函數體中的後續代碼。注意,這裏的"等待"並非阻塞在那裏,而只是告訴系統,後續的代碼須要在await
後面的表達式結束以後執行。你能夠把await
那一行如下的代碼理解爲Android網絡調用中的callback。實際的運行機制實際上是比較複雜的,須要另寫文章詳細說明。
在請求獲得返回值response
之後就要作json反序列化了。由於反序列化也有多是個耗時任務,有可能會阻塞ui. 這裏咱們用過Flutter提供的compute
函數把反序列化放在另外的isolate
去完成。這裏你能夠先把isolate
當成是Java裏的線程。compute
函數的第一個參數parseResult
是真正進行反序列化操做的函數。你們能夠感覺一下,函數做爲參數仍是比較方便的。
Model層咱們已經有了,那麼接下來就看下View層怎麼來搭建吧。
在作Android原生開發的時候。咱們通常會用XML來搭建界面,裏面是一個一個的View。而在Flutter中,和View等同的是Widget。Flutter app的界面就是由一個個Widget拼接起來的。並且Widget都是寫在代碼中的,目前沒有用xml等其餘搭建UI的方式,這也是目前Flutter開發被吐槽的點,代碼中各類嵌套的Widget仍是比較使人酸爽的。
Widget分爲StatelessWidget
(無狀態的)和StatefulWidget
(有狀態的)。無狀態是指這個Widget的狀態會發生改變,類好比Android中顯示固定字符串的TextView或者顯示固定圖標的ImageView。反之有狀態則是指這個Widget在顯示期間內狀態會發生改變,就好比咱們在作網絡請求的時候會顯示一個Progress圖標,請求回來數據之後會顯示一個列表。這就是狀態發生了變化。當須要變動狀態的時候,只要調用setState
。StatefulWidget的build
函數會被調用,根據新的state來重建UI,是否是聽起來和Android中的notifyDataSetChanged有點像?
讓咱們自上而下的看一下main.dart的代碼吧
// 我是入口,相似於java中的 static main()
void main() => runApp(new MyApp());
// 我是最外層的容器,我不關內心面內容的變化,因此是無狀態的。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//返回給你一個MaterialApp,至於內部還有啥,看參數
return MaterialApp(
title: 'Headlines',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// 這個Widget是咱們自定義的
home: HeadLinePage(title: 'Headlines'),
);
}
複製代碼
入口的這些代碼都是常規操做。不細說了。
這裏順便說一句,一個.dart文件中是能夠包含多個在最外層的類的,這點和Java是不同的,須要習慣一下。
接下來咱們再實現自定義的Widget: HeadLineList
。由於其狀態會發生改變(有網絡請求),因此這是個StatefulWidget。
class HeadLineList extends StatefulWidget {
@override
_HeadLineListState createState() => new _HeadLineListState();
}
複製代碼
Emmm....... 自定義一個Widget只須要一行代碼嗎?答案是否認的,乾貨都在_HeadLineListState
裏......
class _HeadLineListState extends State<HeadLineList> {
List<News> _articles;
Future _getNews() async {
NewsList news = await NewsApi.getHeadLines();
_articles = news?.articles;
//有數據了 觸發ui更新
setState(() {
});
}
@override
void initState() {
super.initState();
//初始化 開始加載
_getNews();
}
@override
Widget build(BuildContext context) {
switch (_status) {
case IDLE:
//有數據了,返回列表
return ListView.builder(
itemCount: _articles.length,
itemBuilder: (context, index) {return NewsItem(news: _articles[index])};
case LOADING:
//加載中,返回個加載框
return Center(child: CircularProgressIndicator());
}
}
}
複製代碼
這裏HeadLineList是包含加載進度框和新聞列表的容器Widget。而_HeadLineListState
是和其關聯的狀態。真正建立Widget是在build
函數內。這裏會根據不一樣的狀態返回不一樣的Widget。List<News> _articles;
存儲出來的新聞列表,在initState
初始化的時候開始調用網絡請求。
在狀態變爲加載完成時,build
函數內會用ListView.builder
來建立顯示列表。這裏不須要像Android裏的ListView那樣須要一個Adapter,給itemBuilder傳個函數參數就好了,這個函數參數返回咱們自定義的無狀態Widget, NewsItem
, 做爲列表顯示項。
自定義的NewsItem
會有一個充滿控件的背景圖片,這個圖片須要從網絡加載。有一個placeHolder而且加載完有淡入淡出的效果,在Android中咱們可能會用Glide來實現,而在Flutter中,僅需幾行代碼也能夠作到
FadeInImage.assetNetwork(
//圖片url
image: '${news.urlToImage}',
// 圖片scale方式
fit: BoxFit.fitWidth,
// 佔位圖,從assets 中獲取
placeholder: 'images/news_cover.png',
)
複製代碼
整體流程基本上走完了,未涉及到的下拉刷新,最底加載,WebView等技術點 能夠戳這裏Android開發者的Flutter入門(二)查看,或者你們能夠參考源代碼自行理解。
最後咱們再看一下整個工程的目錄結構:
android
,
ios
和
lib
。
android
,
ios
目錄分別是存放兩個平臺的相關代碼。全部的Flutter代碼都存放在
lib
目錄下。
pubspec.yaml
文件項目的配置文件,相似於Android工程中的build.gradle。咱們再看看Android開發者比較關心的
android
目錄,這裏只有一個MainActivity, 代碼以下:
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}
複製代碼
可見這惟一的一個Activity就是個空殼,只是用來給Flutter app提供一個容器。
打apk只須要一條命令: flutter build apk
固然,這之可能須要作一些配置,具體可參考這個文檔
移動端跨平臺開發是大勢所趨,Flutter是一個比較強大的跨平臺解決方案,雖然如今仍是在Beta階段,並無徹底成熟。可是相對於其餘跨平臺解決方案,其對Native app開發者友好,同時又吸取了一些先進的Web開發技術理念,是一個比較順一些的學習跨平臺開發的路徑。另外對於一些未涉及的技術細節你們能夠到這裏查看Android開發者的Flutter入門(二)。