Android開發者的Flutter入門(二)

ad6623發表於2019-09-20

前言

上篇文章Android開發者的Flutter入門(一)講解了用Flutter開發一個簡單的新聞app的大體流程以及主要功能的實現。其中略過了一些功能的實現細節。這篇文章會對這些細節做一些闡述。涉及到的有以下這些點:

閃屏頁

自定義佈局

下拉重新整理

上拉載入更多

使用Assets

路由(頁面跳轉)

內嵌WebView

閃屏頁

由於啟動Flutter app的時候需要初始化Flutter。這個時間是比較長的。所以開發Flutter app的時候都需要加一個閃屏頁。給Android平臺上跑的Flutter app加閃屏頁其實是和給一個正常的Android app加閃屏頁是一樣的。

首先在AndroidManifest.xml中,

AndroidManifest.xml
在第一個紅框中,給MainActivity設定了一個Theme; 另外注意一下第二個紅框中的meta-data標籤。那段註釋的大概意思是說這個標籤是用來表示讓Flutter在啟動過程中保持閃屏頁直到第一幀畫面被繪製出來。也就是說,閃屏頁的隱藏不需要我們來處理了。

接下來看看這個LaunchTheme:

LaunchTheme
可見就定義了一個視窗的背景了,也就是我們的閃屏頁本尊了,這裡你可以把這個drawable改成你自己的閃屏頁圖片也OK。

至於ios平臺的閃屏頁怎麼弄,可以參考這裡

自定義佈局

我們都知道,在Android中,如果系統提供的佈局控制元件不能滿足我們的需求,我們會自定義佈局控制元件來實現。Flutter同樣的也提供自定義佈局控制元件的功能。在這個新聞app中,首頁的列表項顯示效果如下圖,這就是用自定義的佈局控制元件來實現的。

列表項
這個列表項整個背景是新聞圖片,然後在下方疊加標題和來源,文字部分會有個半透明的背景。

程式碼在news_item.dart中。

class NewsItem extends StatelessWidget {
 ...
  @override
  Widget build(BuildContext context) {
   ...
  return new InkWell(
      onTap: enabled ? onTap : null,
      onLongPress: enabled ? onLongPress : null,
      child: Semantics(
          selected: selected,
          enabled: enabled,
          child: ConstrainedBox(
              constraints: BoxConstraints(minHeight: 200.0, maxHeight: 200.0),
               //這個是自定義Layout
              child: CustomMultiChildLayout(
                // 這個Delegate用來做實際的佈局
                delegate: ItemLayoutDelegate(),
                //用來做佈局的子控制元件們
                children: children,
              ))),
    );
}
}
複製程式碼

CustomMultiChildLayout就是來讓你做自定義佈局的控制元件,需要一個Delegate做引數,這個Delegate需要我們自己實現。另一個引數children是需要佈局的子控制元件。自定義佈局控制元件的子控制元件們都需要用一個LayoutId的控制元件包起來。這也是Flutter一個比較有意思的地方,很多在Android中我們當做屬性來用的東西,Flutter都會做成一個類來包裹,這也是造成UI程式碼比較難看的一個原因。

這裡的id一般用列舉來表示,例如

enum _Block {
  bg,
  text,
}
複製程式碼

bg代表新聞圖片,text代表新聞標題。那麼你傳給CustomMultiChildLayout子控制元件列表需要是這樣的,每一個都要用LayoutId包起來:

final List<Widget> children = <Widget>[];
children.add(LayoutId(
     //頭圖的id
      id: _Block.bg,
      child: FadeInImage.assetNetwork(),
    ));
children.add(LayoutId(
     // 標題的id
      id: _Block.text,
      child: Container()
     ));
複製程式碼

最後我們在看看實際佈局是怎麼做的,來看ItemLayoutDelegate的程式碼:

class ItemLayoutDelegate extends MultiChildLayoutDelegate {
  @override
  void performLayout(Size size) {
    if (hasChild(_Block.bg)) {
      layoutChild(_Block.bg, new BoxConstraints.tight(size));
      positionChild(_Block.bg, Offset.zero);
    }

    if (hasChild(_Block.text)) {
      layoutChild(_Block.text,
          new BoxConstraints.tight(Size(size.width, size.height * 0.4)));
      positionChild(
          _Block.text, new Offset(0.0, size.height - size.height * 0.4));
    }
  }
  @override
  bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) => false;
}
複製程式碼

自定義的佈局是在performLayout這個函式中進行的。入參是個Size,也就是父控制元件的寬高。函式體就是根據id來取子控制元件,不同的子控制元件先呼叫layoutChild給約束,再呼叫positionChild擺位置,自定義佈局就完成了,是不是很簡單?

下拉重新整理

新增一個Material design風格的下拉重新整理比較簡單,直接給列表包一個RefreshIndicator就可以了

 return RefreshIndicator(
            //觸發的回撥
            onRefresh: _onRefresh,
            child: ListView.builder()
)
複製程式碼

下拉重新整理觸發的回撥通過onRefresh引數設定。在_onRefesh裡實現重新整理資料的邏輯,需要注意的是函式_onRefresh需要返回Null型別的Future。在這個Future complete之後。重新整理的圖示會自己消失。效果如圖:

下拉重新整理

上拉載入更多

Flutter沒有系統提供的載入更過控制元件,這裡我們想辦法做一個比較粗糙的實現。思路是在列表的末尾新增一個載入控制元件,當滑動到列表底部的時候觸發載入的操作。

ListView.builder(
                //列表長度加1
                itemCount: _articles.length + 1,
                itemBuilder: (context, index) {
                  if (index == _articles.length) {
                    //如果是最後一個,返回載入更過控制元件
                    return LoadingFooter(
                        retry: () {
                          loadMore();
                        },
                        state: _footerStatus);
                  } else {
                   //返回正常列表項
                    return NewsItem();
                  }
                },
                //檢測列表滾動狀態
                controller: _controller));
複製程式碼

在建立列表的時候我們給列表長度加1,當要獲取最後一項時返回載入更多的控制元件,同時還要通過controller監測列表滾動狀態。這樣我們就給列表加了個上拉載入更多的功能。效果如圖:

上拉載入更多

使用Assets

新增 Assets

在Flutter中如果你有圖片等檔案需要引入到app中,都需要使用Assets, 這個Assets的概念不同於Android中Assets的概念,某種意義上講, Flutter的Assets更像是Android中Resource。Flutter中新增的asset都需要在pubspec.yaml 中宣告。例如,我需要新增一張圖片作為載入網路圖片時候的佔點陣圖,只需要做如下宣告就可以了。

flutter:
  assets:
  - images/news_cover.png
複製程式碼

Android中的Resources我們可以給資原始檔夾按照一定規律來命名,這樣系統可以挑選最適合的資源,同樣的Flutter的Asset也可以。下面的宣告就提供了3種不同解析度的圖示。

.../my_icon.png
.../2.0x/my_icon.png
.../3.0x/my_icon.png
複製程式碼

訪問 Assets

Flutter中訪問Assets很靈活,最基本的可以用以下方式來訪問Assets:

import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;

Future<String> loadAsset() async {
  return await rootBundle.loadString('assets/config.json');
}
複製程式碼

但是很多控制元件也會提供更方便的方式,具體可參考這裡

路由(頁面跳轉)

Android中我們都是用startActivity或者第三方路由庫來做頁面跳轉,在Flutter中,使用內建的Navigator來做跳轉的。Navigator是一個棧,當需要開啟新頁面的時候就呼叫Navigator.push,需要返回的時候就呼叫Navigator.pop,本文中的app當點選新聞項的時候要跳轉另外一個頁面開啟新聞詳情。程式碼如下:

Navigator.push(
              context,
              MaterialPageRoute(
                 builder: (context) => WebviewScaffold(),
             )));
複製程式碼

內嵌WebView

Flutter本身沒有支援內嵌WebView。我們可以用第三方外掛庫flutter_webview_plugin來實現。

首先在pubspec.yaml裡引入這個庫:

dependencies:
     flutter_webview_plugin: "^0.1.5"
複製程式碼

使用的時候直接傳入url和appBar就可以了

WebviewScaffold(
      url: '${_articles[index].url}',
      appBar:
              AppBar(title: Text("News Detail")),
      )
複製程式碼

總結

至此對於我的第一個Flutter app講解已經完畢,相信大家看了之後就會對開發Flutter app的一些基本的技術點都有了瞭解。我也是剛開始學習,文中可能會有錯漏之處,歡迎大家指正。總體感覺來講,用Flutter開發app可以體會到很多不同於Android 原生app開發的理念。對於我們開闊自己的技術思想還是有很有價值的。要深入理解Flutter開發的方方面面還是要多讀程式碼多實踐,後面的路還很長,但是會很有趣。

相關文章