Android開(kāi)發(fā)者的Flutter入門(二)

前言

上篇文章Android開(kāi)發(fā)者的Flutter入門(一)講解了用Flutter開(kāi)發(fā)一個(gè)簡(jiǎn)單的新聞app的大體流程以及主要功能的實(shí)現(xiàn)。其中略過(guò)了一些功能的實(shí)現(xiàn)細(xì)節(jié)。這篇文章會(huì)對(duì)這些細(xì)節(jié)做一些闡述。涉及到的有以下這些點(diǎn):

閃屏頁(yè)
自定義布局
下拉刷新
上拉加載更多
使用Assets
路由(頁(yè)面跳轉(zhuǎn))
內(nèi)嵌WebView

閃屏頁(yè)

由于啟動(dòng)Flutter app的時(shí)候需要初始化Flutter。這個(gè)時(shí)間是比較長(zhǎng)的。所以開(kāi)發(fā)Flutter app的時(shí)候都需要加一個(gè)閃屏頁(yè)。給Android平臺(tái)上跑的Flutter app加閃屏頁(yè)其實(shí)是和給一個(gè)正常的Android app加閃屏頁(yè)是一樣的。

首先在AndroidManifest.xml中,

AndroidManifest.xml

在第一個(gè)紅框中,給MainActivity設(shè)置了一個(gè)Theme; 另外注意一下第二個(gè)紅框中的meta-data標(biāo)簽。那段注釋的大概意思是說(shuō)這個(gè)標(biāo)簽是用來(lái)表示讓Flutter在啟動(dòng)過(guò)程中保持閃屏頁(yè)直到第一幀畫面被繪制出來(lái)。也就是說(shuō),閃屏頁(yè)的隱藏不需要我們來(lái)處理了。

接下來(lái)看看這個(gè)LaunchTheme:

LaunchTheme

可見(jiàn)就定義了一個(gè)窗口的背景了,也就是我們的閃屏頁(yè)本尊了,這里你可以把這個(gè)drawable改成你自己的閃屏頁(yè)圖片也OK。

至于ios平臺(tái)的閃屏頁(yè)怎么弄,可以參考這里

自定義布局

我們都知道,在Android中,如果系統(tǒng)提供的布局控件不能滿足我們的需求,我們會(huì)自定義布局控件來(lái)實(shí)現(xiàn)。Flutter同樣的也提供自定義布局控件的功能。在這個(gè)新聞app中,首頁(yè)的列表項(xiàng)顯示效果如下圖,這就是用自定義的布局控件來(lái)實(shí)現(xiàn)的。


列表項(xiàng)

這個(gè)列表項(xiàng)整個(gè)背景是新聞圖片,然后在下方疊加標(biāo)題和來(lái)源,文字部分會(huì)有個(gè)半透明的背景。

代碼在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),
               //這個(gè)是自定義Layout
              child: CustomMultiChildLayout(
                // 這個(gè)Delegate用來(lái)做實(shí)際的布局
                delegate: ItemLayoutDelegate(),
                //用來(lái)做布局的子控件們
                children: children,
              ))),
    );
}
}

CustomMultiChildLayout就是來(lái)讓你做自定義布局的控件,需要一個(gè)Delegate做參數(shù),這個(gè)Delegate需要我們自己實(shí)現(xiàn)。另一個(gè)參數(shù)children是需要布局的子控件。自定義布局控件的子控件們都需要用一個(gè)LayoutId的控件包起來(lái)。這也是Flutter一個(gè)比較有意思的地方,很多在Android中我們當(dāng)做屬性來(lái)用的東西,F(xiàn)lutter都會(huì)做成一個(gè)類來(lái)包裹,這也是造成UI代碼比較難看的一個(gè)原因。

這里的id一般用枚舉來(lái)表示,例如

enum _Block {
  bg,
  text,
}

bg代表新聞圖片,text代表新聞標(biāo)題。那么你傳給CustomMultiChildLayout子控件列表需要是這樣的,每一個(gè)都要用LayoutId包起來(lái):

final List<Widget> children = <Widget>[];
children.add(LayoutId(
     //頭圖的id
      id: _Block.bg,
      child: FadeInImage.assetNetwork(),
    ));
children.add(LayoutId(
     // 標(biāo)題的id
      id: _Block.text,
      child: Container()
     ));

最后我們?cè)诳纯磳?shí)際布局是怎么做的,來(lái)看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這個(gè)函數(shù)中進(jìn)行的。入?yún)⑹莻€(gè)Size,也就是父控件的寬高。函數(shù)體就是根據(jù)id來(lái)取子控件,不同的子控件先調(diào)用layoutChild給約束,再調(diào)用positionChild擺位置,自定義布局就完成了,是不是很簡(jiǎn)單?

下拉刷新

添加一個(gè)Material design風(fēng)格的下拉刷新比較簡(jiǎn)單,直接給列表包一個(gè)RefreshIndicator就可以了

 return RefreshIndicator(
            //觸發(fā)的回調(diào)
            onRefresh: _onRefresh,
            child: ListView.builder()
)

下拉刷新觸發(fā)的回調(diào)通過(guò)onRefresh參數(shù)設(shè)置。在_onRefesh里實(shí)現(xiàn)刷新數(shù)據(jù)的邏輯,需要注意的是函數(shù)_onRefresh需要返回Null類型的Future。在這個(gè)Future complete之后。刷新的圖標(biāo)會(huì)自己消失。效果如圖:

下拉刷新

上拉加載更多

Flutter沒(méi)有系統(tǒng)提供的加載更過(guò)控件,這里我們想辦法做一個(gè)比較粗糙的實(shí)現(xiàn)。思路是在列表的末尾添加一個(gè)加載控件,當(dāng)滑動(dòng)到列表底部的時(shí)候觸發(fā)加載的操作。

ListView.builder(
                //列表長(zhǎng)度加1
                itemCount: _articles.length + 1,
                itemBuilder: (context, index) {
                  if (index == _articles.length) {
                    //如果是最后一個(gè),返回加載更過(guò)控件
                    return LoadingFooter(
                        retry: () {
                          loadMore();
                        },
                        state: _footerStatus);
                  } else {
                   //返回正常列表項(xiàng)
                    return NewsItem();
                  }
                },
                //檢測(cè)列表滾動(dòng)狀態(tài)
                controller: _controller));

在創(chuàng)建列表的時(shí)候我們給列表長(zhǎng)度加1,當(dāng)要獲取最后一項(xiàng)時(shí)返回加載更多的控件,同時(shí)還要通過(guò)controller監(jiān)測(cè)列表滾動(dòng)狀態(tài)。這樣我們就給列表加了個(gè)上拉加載更多的功能。效果如圖:

上拉加載更多

使用Assets

添加 Assets

在Flutter中如果你有圖片等文件需要引入到app中,都需要使用Assets, 這個(gè)Assets的概念不同于Android中Assets的概念,某種意義上講, Flutter的Assets更像是Android中Resource。Flutter中添加的asset都需要在pubspec.yaml
中聲明。例如,我需要添加一張圖片作為加載網(wǎng)絡(luò)圖片時(shí)候的占位圖,只需要做如下聲明就可以了。

flutter:
  assets:
  - images/news_cover.png

Android中的Resources我們可以給資源文件夾按照一定規(guī)律來(lái)命名,這樣系統(tǒng)可以挑選最適合的資源,同樣的Flutter的Asset也可以。下面的聲明就提供了3種不同分辨率的圖標(biāo)。

.../my_icon.png
.../2.0x/my_icon.png
.../3.0x/my_icon.png

訪問(wèn) Assets

Flutter中訪問(wèn)Assets很靈活,最基本的可以用以下方式來(lái)訪問(wèn)Assets:

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

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

但是很多控件也會(huì)提供更方便的方式,具體可參考這里。

路由(頁(yè)面跳轉(zhuǎn))

Android中我們都是用startActivity或者第三方路由庫(kù)來(lái)做頁(yè)面跳轉(zhuǎn),在Flutter中,使用內(nèi)置的Navigator來(lái)做跳轉(zhuǎn)的。Navigator是一個(gè)棧,當(dāng)需要打開(kāi)新頁(yè)面的時(shí)候就調(diào)用Navigator.push,需要返回的時(shí)候就調(diào)用Navigator.pop,本文中的app當(dāng)點(diǎn)擊新聞項(xiàng)的時(shí)候要跳轉(zhuǎn)另外一個(gè)頁(yè)面打開(kāi)新聞詳情。代碼如下:

Navigator.push(
              context,
              MaterialPageRoute(
                 builder: (context) => WebviewScaffold(),
             )));

內(nèi)嵌WebView

Flutter本身沒(méi)有支持內(nèi)嵌WebView。我們可以用第三方插件庫(kù)flutter_webview_plugin來(lái)實(shí)現(xiàn)。

首先在pubspec.yaml里引入這個(gè)庫(kù):

dependencies:
     flutter_webview_plugin: "^0.1.5"

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

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

總結(jié)

至此對(duì)于我的第一個(gè)Flutter app講解已經(jīng)完畢,相信大家看了之后就會(huì)對(duì)開(kāi)發(fā)Flutter app的一些基本的技術(shù)點(diǎn)都有了了解。我也是剛開(kāi)始學(xué)習(xí),文中可能會(huì)有錯(cuò)漏之處,歡迎大家指正??傮w感覺(jué)來(lái)講,用Flutter開(kāi)發(fā)app可以體會(huì)到很多不同于Android 原生app開(kāi)發(fā)的理念。對(duì)于我們開(kāi)闊自己的技術(shù)思想還是有很有價(jià)值的。要深入理解Flutter開(kāi)發(fā)的方方面面還是要多讀代碼多實(shí)踐,后面的路還很長(zhǎng),但是會(huì)很有趣。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,954評(píng)論 25 709
  • 原文鏈接:https://github.com/opendigg/awesome-github-android-u...
    IM魂影閱讀 33,154評(píng)論 6 472
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 47,147評(píng)論 22 665
  • 早早的到了單位。澆花、燒水、擦桌子、掃地。路上暈車,疲倦,早飯不想吃了。同事問(wèn)吃早飯沒(méi)?送來(lái)餅干。心里暖暖的。 整...
    曲明溪閱讀 204評(píng)論 0 0
  • 三姐離開(kāi)我們已經(jīng)多年了,她用一根繩子,結(jié)束了自己年輕的生命。 三姐是大姨的女兒,在家排行老三。 ...
    都市追夢(mèng)人閱讀 895評(píng)論 0 1

友情鏈接更多精彩內(nèi)容