NOW直播Flutter動態搜尋列表頁實現

NOW終端技術團隊發表於2018-08-24

作者:騰訊NOW直播 - narutosun (孫帥)

前言

Flutter是Google使用Dart語言開發的移動應用開發框架,使用一套Dart程式碼就能構建高效能、高保真的iOS和Android應用程式,並且在排版、圖示、滾動、點選等方面實現零差異。騰訊Now直播App中使用Flutter實現了動態搜尋列表頁。本文主要介紹動態搜尋列表頁實現相關步驟,總體來看主要分為UI,資料解析和資料通訊三個部分。

1. 動態搜尋列表頁UI

Now直播動態搜尋列表頁在Native程式碼實現的UI如下圖所示。

NOW直播Flutter動態搜尋列表頁實現

從iOS開發的角度看,可以將這個頁面元素進行拆解。頁面的父檢視就是普通的一個UITableview。每一行的元素就是一個cell,cell內部頭像是一個UIImageview,暱稱是UILabel,時間也是UILabel,動態的內容通過UILabel展示,動態圖片通過UIImageview顯示,右上角更多的按鈕是一個UIButton。

NOW直播Flutter動態搜尋列表頁實現
NOW直播Flutter動態搜尋列表頁實現

從Flutter開發的角度看,我們可以將介面元素拆解成Row,Column,ListView,itemWidget這些UI元素。下面看下具體這些元素如何定義及使用。

NOW直播Flutter動態搜尋列表頁實現
NOW直播Flutter動態搜尋列表頁實現

  1. Row (行佈局)
    行佈局,顧名思義,佈局就是以行為基準,將子檢視,以行的形式去排列,它可以擁有多個子widget,在這裡也說一下,Flutter秉承的原則就是“一切皆為控制元件”,每個元素都是一個widget。那這裡的子widget可以理解為子檢視,也就是說Row可以包含多個子檢視。由於Row的屬性網上有好多介紹的,這裡就不一一去介紹了,主要介紹下在Now動態搜尋列表頁裡用到Row幾個屬性:
  • mainAxisAlignment: 在水平方向上,子widget的對齊方式(有這幾種方式spaceEvenly,center,left,right)。
  • verticalDirection: 表示垂直方向上,從哪個方向為起始位置(比如從上向下佈局還是從下向上佈局)
  • textDirector:表示在橫軸上,佈局從哪個方向開始(比如從左右向還是從右向左佈局)
  1. Column (列布局)
    和行佈局不一樣的,列布局就是在縱軸上對子widget進行排列。同樣也是可以包含多個子檢視。同樣介紹下Now動態搜尋列表頁裡涉及到的簡單的屬性:
  • mainAxisAlignment:和Row不一樣的是,這裡指的變成了在垂直方向上,子widget的對齊方式(有這幾種方式spaceEvenly,center,left,right)。
  • verticalDirection: 同樣和Row有相同的效果
  1. Container (容器)
    Container在Flutter中太常見了。官方給出的簡介,是一個結合了繪製(painting)、定位(positioning)以及尺寸(sizing)widget的widget。可以得出幾個資訊,它是一個組合的widget,內部有繪製widget、定位widget、尺寸widget。後續看到的不少widget,都是通過一些更基礎的widget組合而成的。只包含一個子widget,container的組成可以由下面這張官方的圖體現出來。

NOW直播Flutter動態搜尋列表頁實現

由於Container屬性有很多,下面同樣只展示下Now動態搜尋列表頁用到的屬性。

  • padding: decoration內部的空白區域,如果有child的話,child位於padding內部。可以通過設定padding,來改變content在container的位置,同樣也能改變container的大小。
  • alignment: 設定child在Container的對齊方式(有topLeft,topCenter,topRight,centerLeft,center,centerRight,bottomLeft,bottomCenter,bottomRight這幾種系統提供的型別)。可以通過Alignment的方法來自定義child的位置
  • margin:從上圖能夠看出,margin其實表示的是decoration外面的區域,不是Container的內容區域。可以設定與父widget的間隔
  1. ListView
    ListView控制元件是APP開發中最為常見的控制元件之一,類似iOS中的UITableView,可以用來展示列表式的資訊。它的內容對於其渲染框太長時會自動提供滾動。可以擁有多個child,可以水平或者垂直放置。在APP中扮演著重要的角色,listview屬性詳細介紹可以看下listView文件,Now這裡用的是Flutter第三庫的元件。Now動態搜尋列表頁用到的基本屬性簡單介紹一下:
  • scrollDirection:預設child是垂直方向上進行佈局(Axis.vertical),可滑動方向對應的也是垂直方向上,horizontal表示的是水平方向。
  • reverse:是個bool型別,預設是false,在水平方向則child是從左向右排列,垂直方向上child是從上向下排列。反之ture的話,水平方向的child是從右向左排列,垂直方向是從下向上排列。

從上面來看,我們能分析出,通過Flutter實現這個UI,用的無非就是Row,Column,Container,listView這些基礎widget。動態搜尋頁基礎UI我們就已經搭建出來了。

2. 資料格式及解析

Now App在請求資料與解析資料,都是通過谷歌的protobuf來實現的,所以Flutter頁面的資料依舊通過protobuf來實現。Flutter怎麼整合進來protobuf的外掛呢,這裡我使用的是AndroidStudio的IDE,安裝了Dart環境後,開啟Flutter工程,會有一個pubspec.yaml的配置檔案,在這裡可以像Xcode的cocopods一樣,將protobuf的版本號輸入這裡,然後update一下Dart,就會自動將protobuf整合進來。具體的protobuf是什麼以及如何使用,可以參考這篇文章

NOW直播Flutter動態搜尋列表頁實現
NOW直播Flutter動態搜尋列表頁實現

在上面我們也看到了搜尋頁有很多的檢視元素,還有一些點選跳轉的事件也需要傳參,所以這裡設計資料格式的話,給當前類建立了這些屬性。

class AnchorInfo {
  String anchorHeadUrl;        //頭像
  String anchorName;           //暱稱
  String uin;                  //uin
  String content;              //內容
  FEED_TYPE feedType;          //Feed型別
  String feedsId;              //FeedID
  String coverImageUrl;        //封面url
  double imgWidth = 200.0;     //圖片寬度
  double imgHeight = 200.0;    //圖片高度
  String jumpUrl;              //跳轉url
}
複製程式碼

從這個資料格式上來看,已經包含了動態搜尋列表頁面Cell的基本資料。下面就是如何給這些資料初始化,以及怎麼拿到這些資料。

3. 資料通訊

資料通訊主要有兩種情況,一種是有Flutter頁面主動觸發的,另一種是由Native主動觸發呼叫到Flutter的。我們分別來看這兩種情況在動態搜尋頁中的應用。

3.1 Flutter呼叫Native

這種情況應用於動態搜尋列表頁feeds拉取的場景。由於Now工程專案的特殊性,客戶端在發包的時候使用的是WNS平臺,所以這塊發包不能通過Flutter頁面來實現,只能通過客戶端來發包,客戶端回包會取到一個二進位制流的資料,會將這個二進位制流的資料塞給Flutter,因為發包同樣是遵照protobuf的格式。所以這裡Flutter拿到二進位制流的資料後,就可以通過protobuf來解包,實現了客戶端發包,Flutter解包的一個過程。具體流程如下圖所示。

NOW直播Flutter動態搜尋列表頁實現

針對這個場景,Flutter提供了MethodChannel來實現。具體步驟如下:

1)Flutter內部註冊MethodChannel

class DynamicListViewState extends State<DynamicState> {
  ...
  static platform = const MethodChannel('now.qq.com/flutter');
  ...
}
複製程式碼

在類初始化的時候,就將platform賦值給了一個MethodChannel,我們看到傳了一個字串的引數進去。那這個引數其實就是一個呼叫iOS的標識。

2)Native程式碼註冊相同名稱的MethodChannel iOS客戶端同樣會註冊一個帶有相同標識的channel。

self.methodChannel = [FlutterMethodChannel methodChannelWithName:
                        @"now.qq.com/flutter" binaryMessenger:self];
複製程式碼

3)Flutter呼叫MethodChannel 上面完成的步驟,可以理解為在iOS上已經注入了一個外掛,那接下來就需要通過外掛來呼叫iOS的相關的API,如何來呼叫具體的iOS的API呢。看下我們這邊載入動態資料的程式碼塊。

void moreAnchorInfoDataRequest() async {
    ...
    List<int> data = await platform.invokeMethod("anchorInfoDataRequest", pageIndex);
    ...
}
複製程式碼

主要看platform.invokeMethod的方法,發現又傳入了一個字串,不同的是還傳入了一個int型的pageIndex。字串同樣是一個標識,是Flutter呼叫客戶端的一個約定。客戶端在取到這個標識後,會去呼叫相應的API。至於pageIndex,是客戶端在需要呼叫這個方法時,所需的引數。來看下客戶端的程式碼是如何響應並執行這個操作的。

4)Native接收到呼叫處理完成後回包

[self.methodChannel setMethodCallHandler:^(FlutterMethodCall* call,
                                               FlutterResult result) {
        ...
         if([call.method isEqualToString:@"anchorInfoDataRequest"]){
            wself.result = result;
            [wself anchorInfoRequest:((NSNumber*)call.arguments).unsignedIntegerValue];
        }
        ...
    }];
複製程式碼

從程式碼塊中可以看出,客戶端在閉包裡,可以取到call的例項,通過method的屬性來判斷出需要呼叫哪種API。這樣就已經完成了Flutter呼叫客戶端的步驟。那現在還有一步是怎麼通過客戶端呼叫到Flutter呢。

3.2 Native呼叫Flutter

這種情況應用於Native動態詳情頁進行刪除的場景。在Flutter頁面點選頭像->進入主人態個人中心->刪除了動態->告知Flutter動態頁資料更新。Native主動觸發事件去告訴Flutter,Flutter需要去響應Native觸發的動作。具體流程如下圖所示:

NOW直播Flutter動態搜尋列表頁實現
針對這個場景,Flutter提供了EventChannel來實現。具體步驟如下:

1)Flutter註冊EventChannel

 static const EventChannel eventChannel = const EventChannel("now.qq.com/event");
複製程式碼

重寫當前類的initState方法,在這個方法裡,註冊一個監聽。用來監聽從客戶端呼叫來的事件,在_onEvent方法裡執行監聽到之後所需要做的操作。

void initState() {
    super.initState();
    ...
    eventChannel.receiveBroadcastStream().listen(_onEvent,onError: _onEventError);
    ...
  }
複製程式碼

在Now裡這個事件是個刪除動態的操作,所以呼叫了刪除的操作後,告知Flutter頁面的資料改變,UI需要重新整理。

void _onEvent(Object event) {
    ...
    if(deleteData != null){
      setState(() {
        dynamicDataList.remove(deleteData);
        if(dynamicDataList.length > 0) {
          showType = show_normalView;
        }else{
          showType = show_notingView;
        }
      });
    }
    ...
  }
複製程式碼

2)Native程式碼註冊相同名稱的EventChannel

看下在Now工程中,這個方法是在什麼時機呼叫的。程式碼塊如下:

self.eventChannel = [FlutterEventChannel eventChannelWithName:@"now.qq.com/event" binaryMessenger:self];
[self.eventChannel setStreamHandler:self];
複製程式碼

同樣客戶端也建立了一個FlutterEventChannel的例項,通過類方法來建立的。setStreamHandler的方法就是注入一個工具類,作用將需要呼叫的事件流通知給Flutter。設定了這個方法後,我們需要實現它提供的代理方法,在實現代理方法前,我們還需要一個FlutterEventSink的閉包函式。我們把這個閉包函式宣告成了一個屬性。

@property (nonatomic, strong) FlutterEventSink eventSink;
複製程式碼

3)Native呼叫到Flutter

下面看怎麼使用這個屬性以及怎麼回撥到Flutter頁面上。首先需要實現FlutterStreamHandler的代理方法,在onListenWithArguments代理方法裡,我們儲存了FlutterEventSink型別的閉包函式。方便後續在呼叫的地方去使用。

- (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments
                                       eventSink:(FlutterEventSink)events {
    self.eventSink = events;
    return nil;
}
複製程式碼

呼叫的時機如下,在我們刪除動態成功後,我們會回撥給Flutter。傳入一個feedsId的引數

- (void)onDeleteFeedsRequestSucceed:(NSArray *)feedsArray {
    ...
    if(self.eventSink){
        self.eventSink(model.feedsId);
    }
    ...
}
複製程式碼

總結

以上就是Now直播使用Flutter實現動態搜尋列表頁的一些步驟細節,歡迎大家探討。Now直播終端團隊致力於為Flutter生態作出一點自己的貢獻,期待Flutter越來越好!

相關文章