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

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

前言

Flutter是Google使用Dart語言開發的移動應用開發框架,使用一套Dart代碼就能構建高性能、高保真的iOS和Android應用程序,而且在排版、圖標、滾動、點擊等方面實現零差別。騰訊Now直播App中使用Flutter實現了動態搜索列表頁。本文主要介紹動態搜索列表頁實現相關步驟,整體來看主要分爲UI,數據解析和數據通訊三個部分。bash

1. 動態搜索列表頁UI

Now直播動態搜索列表頁在Native代碼實現的UI以下圖所示。閉包

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

從Flutter開發的角度看,咱們能夠將界面元素拆解成Row,Column,ListView,itemWidget這些UI元素。下面看下具體這些元素如何定義及使用。async

  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的組成能夠由下面這張官方的圖體現出來。

因爲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是什麼以及如何使用,能夠參考這篇文章佈局

在上面咱們也看到了搜索頁有不少的視圖元素,還有一些點擊跳轉的事件也須要傳參,因此這裏設計數據格式的話,給當前類建立了這些屬性。性能

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的基本數據。下面就是如何給這些數據初始化,以及怎麼拿到這些數據。ui

3. 數據通訊

數據通訊主要有兩種狀況,一種是有Flutter頁面主動觸發的,另外一種是由Native主動觸發調用到Flutter的。咱們分別來看這兩種狀況在動態搜索頁中的應用。

3.1 Flutter調用Native

這種狀況應用於動態搜索列表頁feeds拉取的場景。因爲Now工程項目的特殊性,客戶端在發包的時候使用的是WNS平臺,因此這塊發包不能經過Flutter頁面來實現,只能經過客戶端來發包,客戶端回包會取到一個二進制流的數據,會將這個二進制流的數據塞給Flutter,由於發包一樣是遵守protobuf的格式。因此這裏Flutter拿到二進制流的數據後,就能夠經過protobuf來解包,實現了客戶端發包,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觸發的動做。具體流程以下圖所示:

針對這個場景,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愈來愈好!

相關文章
相關標籤/搜索