如何在flutter頁面中應(yīng)用模板方法設(shè)計(jì)模式瞭吃?

在該設(shè)計(jì)模式下頁面的路由跳轉(zhuǎn)

image

這里沒用到 context 也沒使用任何路由插件嗜憔。

模板方法模式的定義和組成

模板方法模式是一種只需使用繼承就可以實(shí)現(xiàn)的非常簡單的模式木缝。

模板方法模式由兩部分結(jié)構(gòu)組成便锨,第一部分是抽象父類,第二部分是具體的實(shí)現(xiàn)子類我碟。通常在抽象父類中封裝了子類的算法框架,包括實(shí)現(xiàn)一些公共方法以及封裝子類中所有方法的執(zhí)行順序放案。子類通過繼承這個(gè)抽象類,也繼承了整個(gè)算法結(jié)構(gòu)矫俺,并且可以選擇重寫父類的方法吱殉。

假如我們有一些平行的子類,各個(gè)子類之間有一些相同的行為厘托,也有一些不同的行為友雳。如果相同和不同的行為都混合在各個(gè)子類的實(shí)現(xiàn)中,說明這些相同的行為會(huì)在各個(gè)子類中重復(fù)出現(xiàn)铅匹。

但實(shí)際上押赊,相同的行為可以被搬移到另外一個(gè)單一的地方,模板方法模式就是為解決這個(gè)問題而生的包斑。在模板方法模式中流礁,子類實(shí)現(xiàn)中的相同部分被上移到父類中,而將不同的部分留待子類來實(shí)現(xiàn)罗丰。這也很好地體現(xiàn)了泛化的思想神帅。

---- 摘抄自《Javascript設(shè)計(jì)模式與開發(fā)實(shí)踐》

如何實(shí)現(xiàn)?

首先進(jìn)行抽象丸卷,每個(gè)頁面都有一個(gè)路由跳轉(zhuǎn)方法枕稀,跳轉(zhuǎn)的時(shí)候都要設(shè)置路由名稱。同時(shí)他們可能還要執(zhí)行 pop 來把當(dāng)前頁面移除谜嫉。然后還要對(duì)每個(gè)頁面用戶的進(jìn)入離開進(jìn)行埋點(diǎn)上報(bào)萎坷。

相同的地方地方在于路由的跳轉(zhuǎn)以及 pop 方法,還有用戶行為埋點(diǎn)沐兰。不同的地方在于設(shè)置路由名稱哆档,這一點(diǎn)要子類去實(shí)現(xiàn)。

基于此住闯,我們就開始創(chuàng)建一個(gè)抽象類 BasePage

/// BasePage 是抽象的頁面瓜浸,所有頁面都應(yīng)該繼承于它
/// [ResultType] 是頁面 pop 時(shí)返回的數(shù)據(jù)類型。
abstract class BasePage<ResultType> extends StatefulWidget {
  /// 無需context跳轉(zhuǎn)路由的關(guān)鍵比原,全局導(dǎo)航key
  static final GlobalKey<NavigatorState> navigatorKey = GlobalKey();

  /// 需要子類實(shí)現(xiàn)的設(shè)置頁面名稱
  String get pageName;

  /// PageRoute 的 builder
  Widget _pageBuilder(BuildContext context) {
    return this;
  }

  /// 創(chuàng)建一個(gè) PageRoute
  Route<ResultType> createPageRoute(WidgetBuilder builder, RouteSettings settings) {
    return MaterialPageRoute(
      settings: settings,
      builder: builder,
    );
  }

  /// 獲取路由設(shè)置
  RouteSettings get settings => RouteSettings(name: pageName);

  /// 跳轉(zhuǎn)路由
  Future<ResultType> active({bool replace}) {
    var page = createPageRoute(_pageBuilder, settings);
    assert(page.settings != null); // createPageRoute 函數(shù)必須設(shè)置 settings
    assert(page.settings.name != null); // settings 中必須包含name插佛。
    if (replace == true) {
      return navigatorKey.currentState.pushReplacement(page);
    } else {
      return navigatorKey.currentState.push(page);
    }
  }
}

很簡單,目前只能單純的跳轉(zhuǎn)量窘,但是后期可以很方便的擴(kuò)展 active() 跳轉(zhuǎn)函數(shù)雇寇,使跳轉(zhuǎn)更靈活。繼承于它的子頁面就獲得了相應(yīng)的能力蚌铜。這里的 active 函數(shù)就是模板方法了锨侯,因?yàn)樗庋b了調(diào)用順序及跳轉(zhuǎn)邏輯。

好了冬殃,現(xiàn)在要把 navigatorKey 注冊(cè)到 MaterialApp 中去囚痴。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: BasePage.navigatorKey,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: '模板方法設(shè)計(jì)模式',),
    );
  }
}

oh! 還有一個(gè) BasePageState ,StatefulWidget 和 State 一直是成對(duì)出現(xiàn)的不是嗎审葬?我們有了繼承于 StatefulWidget 的抽象類 BasePage深滚,但是它并不能提供 initState 和 dispose 用于用戶行為埋點(diǎn)。那么我們?cè)賱?chuàng)建一個(gè)抽象類 BasePageState

/// 持有 BasePage 實(shí)例的 State
abstract class BasePageState<T extends BasePage> extends State<T> {
  
  @mustCallSuper
  @override
  void initState() {
    print('[BasePageState]: 記錄用戶打開 ${widget.pageName} 頁面');
    super.initState();
  }

  @mustCallSuper
  @override
  void dispose() {
    print('[BasePageState]: 記錄用戶離開 ${widget.pageName} 頁面');
    super.dispose();
  }

  /// 封裝的 pop涣觉。
  bool pop<T extends Object>([T result]) {
    return Navigator.pop<T>(context, result);
  }

}

到這里痴荐,我們已經(jīng)寫好了一個(gè)最基礎(chǔ)的頁面模板。

那么就創(chuàng)建第一個(gè)頁面并且讓它繼承自模板吧旨枯。

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:page_route/base_page.dart';

class PageOne extends BasePage<String> {
  final String title;

  PageOne(this.title);

  @override
  _PageOneState createState() => _PageOneState();

  @override
  String get pageName => 'PageOne';
}

class _PageOneState extends BasePageState<PageOne> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Container(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('當(dāng)前頁面是 ${widget.title}'),
              RaisedButton(
                onPressed: () => pop('page one 已結(jié)束'),
                child: Text('返回結(jié)果'),
              )
            ],
          ),
        ),
      ),
    );
  }

  @override
  void initState() {
    print('${widget.pageName}開始獲取數(shù)據(jù)');
    super.initState();
  }

  @override
  void dispose() {
    print('銷毀用不到的對(duì)象');
    super.dispose();
  }
}

看蹬昌,這樣的話,創(chuàng)建一個(gè)頁面跟平時(shí)創(chuàng)建一個(gè) StatefulWidget 并沒有多大區(qū)別攀隔,而且還獲得了路由啟動(dòng)能力皂贩。使得我們可以隨時(shí)隨地啟動(dòng)一個(gè)頁面。pop 當(dāng)前頁面的時(shí)候也不需要寫一長串的 Navigator.pop<T>(context, result)昆汹。

個(gè)性化路由跳轉(zhuǎn)動(dòng)畫

假使我們要對(duì)某個(gè)頁面加上一些跳轉(zhuǎn)動(dòng)畫明刷,只需要在頁面中 override 重寫模板的 createPageRoute 方法即可。

先創(chuàng)建第二個(gè)頁面 PageTwo满粗,然后實(shí)現(xiàn)漸隱過度

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:page_route/base_page.dart';

class PageTwo extends BasePage {
  @override
  _PageTwoState createState() => _PageTwoState();

  @override
  String get pageName => 'PageTwo';

  @override
  Route createPageRoute(builder, RouteSettings settings) {
    return PageRouteBuilder(
      transitionDuration: Duration(milliseconds: 500), //動(dòng)畫時(shí)間為500毫秒
      settings: settings, // 必須載入 settings
      pageBuilder: (BuildContext context, Animation animation,
          Animation secondaryAnimation) {
        return new FadeTransition(
          //使用漸隱漸入過渡,
          opacity: animation,
          child: builder(context), //路由B
        );
      },
    );
  }
}

class _PageTwoState extends BasePageState<PageTwo> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('重寫 createPageRoute'),
      ),
      body: Container(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('當(dāng)前頁面的路由動(dòng)畫是 重寫 createPageRoute 實(shí)現(xiàn)的'),
              RaisedButton(
                onPressed: () => pop('page two 已結(jié)束'),
                child: Text('返回結(jié)果'),
              )
            ],
          ),
        ),
      ),
    );
  }
}

可以看到辈末,只要我們遵循單一職責(zé)原則,一定程度上就可以使程序的可擴(kuò)展性得到提升。打開 PageTwo 頁面還是一如既往的簡單挤聘。

void goPageTwo() {
  PageTwo().active();
}

快速創(chuàng)建一個(gè)頁面

我們知道轰枝,ide提供了一些快捷模板(live template)。只要輸入 stful 就可以快速創(chuàng)建一個(gè) StatefulWidget组去。我們可以仿照 stful 寫一個(gè) page 快捷模板鞍陨。先打開 stful 看看。


image

好的从隆,你已經(jīng)學(xué)會(huì)創(chuàng)建快捷模板了诚撵,于是你創(chuàng)建了一個(gè) page。

image

創(chuàng)建新的快捷模板時(shí)键闺,一定要記得設(shè)置模板用的語言寿烟。

image

截圖里面代碼不全,下面是完全版:

import 'package:flutter/material.dart';
import 'package:page_route/base_page.dart';

class $PAGE_NAME$ extends BasePage {
  @override
  _$PAGE_NAME$State createState() => _$PAGE_NAME$State();

  @override
  String get pageName => '$PAGE_NAME$';
}

class _$PAGE_NAME$State extends BasePageState<$PAGE_NAME$> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('$END$'),
      ),
      body: Container(),
    );
  }

}

此時(shí)只需要像寫 stful 一樣寫 page 就可以快速創(chuàng)建一個(gè)頁面了辛燥。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末筛武,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子购桑,更是在濱河造成了極大的恐慌畅铭,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件勃蜘,死亡現(xiàn)場(chǎng)離奇詭異硕噩,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)缭贡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門炉擅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人阳惹,你說我怎么就攤上這事谍失。” “怎么了莹汤?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵快鱼,是天一觀的道長。 經(jīng)常有香客問我纲岭,道長抹竹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任止潮,我火速辦了婚禮窃判,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘喇闸。我一直安慰自己袄琳,他們只是感情好询件,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著唆樊,像睡著了一般宛琅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上窗轩,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天夯秃,我揣著相機(jī)與錄音座咆,去河邊找鬼痢艺。 笑死,一個(gè)胖子當(dāng)著我的面吹牛介陶,可吹牛的內(nèi)容都是我干的堤舒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼哺呜,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼舌缤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起某残,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤国撵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后玻墅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體介牙,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年澳厢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了环础。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡剩拢,死狀恐怖线得,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情徐伐,我是刑警寧澤贯钩,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站办素,受9級(jí)特大地震影響角雷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜摸屠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一谓罗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧季二,春花似錦檩咱、人聲如沸揭措。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绊含。三九已至,卻和暖如春炊汹,著一層夾襖步出監(jiān)牢的瞬間躬充,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工讨便, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留充甚,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓霸褒,卻偏偏與公主長得像伴找,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子废菱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355