跟我學(xué)企業(yè)級flutter項目:手把手教你制作一款低耦合空頁面widget

前言

如何開發(fā)一款易用的,并且可以擴展的空頁面呢?那么今天我將帶領(lǐng)大家手把手開發(fā)一款可擴展的空頁面。

開發(fā)前注意事項

1、定義好空頁面狀態(tài)
2、可擴展思想(用抽象或基類替代實體)
3、抽離出空頁面的結(jié)構(gòu)

空頁面展示

在這里插入圖片描述

開始搭建

一、頁面分析

空頁面需要元素有:

  1. 展示圖片
  2. 展示文案
  3. 展示刷新按鈕

頁面功能點:

  1. 文案可自定義
  2. 圖片可自定義
  3. 按鈕可隱藏

wiget作用范圍:

  1. 可包裹其他widget
  2. 不包裹其他widget

二、定義狀態(tài)

2.1 幾種狀態(tài)

enum EmptyStatus {
  fail, //失敗視圖
  loading, //加載視圖
  nodata, //沒有數(shù)據(jù)視圖
  none //沒有狀態(tài)
}

沒有狀態(tài)該空頁面就隱藏掉

2.2 空頁面刷新回調(diào)

abstract class IEmptyRefresh{

  void pressedReload();

}

2.3 定義copy類(復(fù)用做準(zhǔn)備)&定義空接口(抽離要擴展的方法)

abstract class Copyable<T> {
  T copy();
}
abstract class IEmpty implements Copyable<IEmpty>{
  IEmptyRefresh? iEmptyRefresh;
  Widget? diyImage; // 自定義圖片替換
  Widget? diyText;// 自定義文案替換
  Widget? image();

  Widget? text();

  Widget? refresh();
}

2.4 空頁面實現(xiàn)類

默認加載中頁面

class DefaultLodingPage extends IEmpty{

  @override
  Widget? text() {
    return diyText??Text(
      LibEmptyManager.instance.libEmptyPageLoding,
      style: TextStyle(fontSize: LibEmptyManager.instance.textSize, color: AppTheme.instance.textColor()),
    );
  }

  @override
  Widget? image() {
    return null;
  }

  @override
  Widget? refresh() => null;

  @override
  IEmpty copy() {
    return DefaultLodingPage()
      ..diyImage = diyImage
      ..diyText = diyText
      ..iEmptyRefresh=iEmptyRefresh;
  }


}

默認空頁面

class DefaultEmptyPage extends IEmpty{

  @override
  Widget? text() {
    return diyText??Text(
      LibEmptyManager.instance.libEmptyPageNoData,
      style: TextStyle(fontSize: LibEmptyManager.instance.textSize, color: AppTheme.instance.textColor()),
    );
  }

  @override
  Widget? image() {
    return Padding(
      padding: const EdgeInsets.only(bottom: 20),
      child: diyImage??Icon(LibEmptyManager.instance.imageNoData,color: AppTheme.instance.imageColor(),size: LibEmptyManager.instance.imageSize,),
    );
  }

  @override
  Widget? refresh() => null;

  @override
  IEmpty copy() {
    return DefaultEmptyPage()
      ..diyImage = diyImage
      ..diyText = diyText
      ..iEmptyRefresh=iEmptyRefresh;;
  }


}
默認網(wǎng)絡(luò)失效頁

class DefaultNetWorkError extends IEmpty {
  @override
  Widget? text() {
    return diyText??Text(
      LibEmptyManager.instance.libEmptyPageNetError,
      style: TextStyle(fontSize: LibEmptyManager.instance.textSize, color: AppTheme.instance.textColor()),
    );
  }

  @override
  Widget? image() {
    return Padding(
      padding: const EdgeInsets.only(bottom: 20),
      child: diyImage??Icon(LibEmptyManager.instance.imageNetWork,color: AppTheme.instance.imageColor(),size: LibEmptyManager.instance.imageSize,),
    );
  }

  @override
  Widget? refresh() {
    return Padding(
      padding: const EdgeInsets.only(top: 20),
      child: Padding(
        padding: const EdgeInsets.only(left: 20,right: 20),
        child: ElevatedButton(onPressed: () => iEmptyRefresh?.pressedReload(),
          style:  ButtonStyle(
            backgroundColor: MaterialStateProperty.all(AppTheme.instance.btnBackColor()),
            shape: MaterialStateProperty.all(const StadiumBorder()),
          )
          , child: Text(LibEmptyManager.instance.libRefresh,style: TextStyle(fontSize: LibEmptyManager.instance.libRefreshSize,color: AppTheme.instance.btnTextColor())),),
      ),
    );
  }

  @override
  IEmpty copy() {
    return DefaultNetWorkError()
      ..diyImage = diyImage
      ..diyText = diyText
      ..iEmptyRefresh=iEmptyRefresh;;
  }
}

2.5 空頁面管理類

可進行外部配置


class LibEmptyManager{
  IEmpty emptyPage = DefaultEmptyPage();
  IEmpty loadingPage = DefaultLodingPage();
  IEmpty netWorkError = DefaultNetWorkError();

  late LibEmptyConfig libEmptyConfig;

  LibEmptyManager._();

  static final LibEmptyManager _instance = LibEmptyManager._();

  static LibEmptyManager get instance {
    return _instance;
  }

2.6 核心邏輯

判斷狀態(tài),并進行類型拷貝,并增加自定義參數(shù)

switch(widget.layoutType){
      case EmptyStatus.none:
        visable = true;
        break;
    // return widget.child;
      case EmptyStatus.fail:
        iEmpty = LibEmptyManager.instance.netWorkError.copy()
          ..diyText = widget.networkText
          ..diyImage = widget.networkImage
        ;
        break;
      case EmptyStatus.nodata:
        iEmpty = LibEmptyManager.instance.emptyPage.copy()
          ..diyText = widget.emptyText
          ..diyImage = widget.emptyImage
        ;
        break;
      case EmptyStatus.loading:
        iEmpty = LibEmptyManager.instance.loadingPage;
        break;
      default:
        iEmpty = LibEmptyManager.instance.emptyPage.copy()
          ..diyText = widget.emptyText
          ..diyImage = widget.emptyImage
        ;
    }

如果是包裹類型需要stack進行包裝

return Stack(
      children: [
        Offstage(
          offstage: !visable,
          child: widget.child,
        ),
        Offstage(
          offstage: visable,
          child: Container(
            width: double.infinity,
            color: AppTheme.instance.backgroundColor(),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: _listEmpty(iEmpty),
            ),
          ),),
      ],
    );

判斷是否有網(wǎng),有網(wǎng)的話,就刷新,沒網(wǎng)的話,就提示


  @override
  void pressedReload() async{
    bool isConnectNetWork = await isConnected();
    if(isConnectNetWork){
      widget.refresh.call();
    }else{
      TipToast.instance.tip(LibLocalizations.getLibString().libNetWorkNoConnect!,tipType: TipType.error);
    }
  }

  // 是否有網(wǎng)
  Future<bool> isConnected() async {
    var connectivityResult = await (Connectivity().checkConnectivity());
    return connectivityResult != ConnectivityResult.none;
  }

組裝empty


List<Widget> _listEmpty(IEmpty? iEmpty) {
  List<Widget> tempEmpty = [];
  if(iEmpty!=null){
    Widget? image = iEmpty.image();
    Widget? text = iEmpty.text();
    Widget? refresh = iEmpty.refresh();
    if(image!=null){
      tempEmpty.add(image);
    }
    if(text!=null){
      tempEmpty.add(text);
    }
    if(refresh!=null){
      tempEmpty.add(refresh);
    }

  }
  return tempEmpty;
}

三、空頁面widget實現(xiàn)完整代碼

class LibEmptyView extends StatefulWidget{
  Widget? child;
  EmptyStatus layoutType;
  VoidCallback refresh;


  Widget? networkImage;Widget? networkText;
  Widget? emptyImage;Widget? emptyText;

  LibEmptyView({Key? key, this.child,required this.refresh,required this.layoutType,this.networkImage,this.networkText, this.emptyImage,this.emptyText}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _LibEmptyViewState();

}

class _LibEmptyViewState extends State<LibEmptyView> implements IEmptyRefresh{
  //控制器

  @override
  Widget build(BuildContext context) {
    IEmpty? iEmpty;
    bool visable = false;
    switch(widget.layoutType){
      case EmptyStatus.none:
        visable = true;
        break;
      case EmptyStatus.fail:
        iEmpty = LibEmptyManager.instance.netWorkError.copy()
          ..diyText = widget.networkText
          ..diyImage = widget.networkImage
        ;
        break;
      case EmptyStatus.nodata:
        iEmpty = LibEmptyManager.instance.emptyPage.copy()
          ..diyText = widget.emptyText
          ..diyImage = widget.emptyImage
        ;
        break;
      case EmptyStatus.loading:
        iEmpty = LibEmptyManager.instance.loadingPage;
        break;
      default:
        iEmpty = LibEmptyManager.instance.emptyPage.copy()
          ..diyText = widget.emptyText
          ..diyImage = widget.emptyImage
        ;
    }
    iEmpty?.iEmptyRefresh = this;

    

    return Stack(
      children: [
        Offstage(
          offstage: !visable,
          child: widget.child,
        ),
        Offstage(
          offstage: visable,
          child: Container(
            width: double.infinity,
            color: AppTheme.instance.backgroundColor(),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: _listEmpty(iEmpty),
            ),
          ),),
      ],
    );
  }

  @override
  void pressedReload() async{
    bool isConnectNetWork = await isConnected();
    if(isConnectNetWork){
      widget.refresh.call();
    }else{
      TipToast.instance.tip(LibLocalizations.getLibString().libNetWorkNoConnect!,tipType: TipType.error);
    }
  }


  // 是否有網(wǎng)
  Future<bool> isConnected() async {
    var connectivityResult = await (Connectivity().checkConnectivity());
    return connectivityResult != ConnectivityResult.none;
  }
}

List<Widget> _listEmpty(IEmpty? iEmpty) {
  List<Widget> tempEmpty = [];
  if(iEmpty!=null){
    Widget? image = iEmpty.image();
    Widget? text = iEmpty.text();
    Widget? refresh = iEmpty.refresh();
    if(image!=null){
      tempEmpty.add(image);
    }
    if(text!=null){
      tempEmpty.add(text);
    }
    if(refresh!=null){
      tempEmpty.add(refresh);
    }

  }
  return tempEmpty;
}

四、空頁面widget使用代碼

包裹使用 (代碼中的webview封裝參見:跟我學(xué)企業(yè)級flutter項目:如何封裝一套易用,可擴展的Hybrid混合開發(fā)webview

LibEmptyView(
        layoutType: status,
        refresh: () {
         
          status = EmptyStatus.none;
          _innerWebPageController.reload();
         
        },
        
        child: InnerWebPage(widget.url,titleCallBack: (title){
          setState(() {
            urlTitle = title;
          });
        },javascriptChannels: widget._javascriptChannels,urlIntercept: widget._urlIntercept,onInnerWebPageCreated: (innerWebPageController){
          _innerWebPageController = innerWebPageController;
          widget._javascriptChannels?.webPageCallBack = webPageCallBack;
          widget._urlIntercept?.webPageCallBack = webPageCallBack;
        },onWebResourceError: (error){
          setState(() {
            status = EmptyStatus.fail;
          });
        },),
      ),
    ));

非包裹使用

if(_status == EmptyStatus.none){
      return _listViewUi.call(_allReportItems);
    }else{
      var empty = LibEmptyView(
        layoutType: _status,
        refresh: () {
          _status = EmptyStatus.loading;
          LibLoading.show();
          _refreshCenter.refreshData();
        },networkImage: networkImage,networkText: networkText,emptyImage: emptyImage,emptyText: emptyText,);
      if(builder!=null){
        return builder.call(context,empty);
      }else{
        return empty;
      }
    }

感謝大家閱讀我的文章

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內(nèi)容