Flutter竟然還有這種高端用法?

2022-01-05 郭霖
近日特斯拉發布了2020年第三季度財報。財報顯示,特斯拉第三季度營收為87.7億美元,與去年同期相比增長30%;歸屬於普通股東的淨利潤為3.31億美元,同比增長131%;歸屬於普通股東的每股收益為0.76美元,同比增長105%。財報發布後,特斯拉股價上漲超過3%。本篇文章來自小呆呆666的投稿,分享了Flutter中Bloc模式的應用,相信會對大家有所幫助!同時也感謝作者貢獻的精彩文章!
https://juejin.im/user/2840793776393847首先,有很多的文章在說flutter bloc模式的應用,但是百分之八九十的文章都是在說,使用StreamController+StreamBuilder搭建bloc,提升性能的會加上InheritedWidget,這些文章看了很多,真正寫使用bloc作者開發的flutter_bloc卻少之又少。沒辦法,只能去bloc的github上去找使用方式,最後去bloc官網翻文檔。蛋痛,各位叼毛,就不能好好說說flutter_bloc的使用嗎?非要各種抄bloc模式提出作者的那倆篇文章。現在,搞的雜家這個伸手黨要自己去翻文檔總結(手動滑稽)。http://cnad666.gitee.io/book_web_manage/#/https://cnad666.gitee.io/flutter_use/#/Flutter_Bloc起源:https://www.didierboelens.com/2018/08/reactive-programming-streams-bloc/Flutter_Bloc模式優化:https://www.didierboelens.com/2018/12/reactive-programming-streams-bloc-practical-use-cases/Flutter_Bloc誕生:https://medium.com/flutter-community/flutter-bloc-package-295b53e95c5cFlutter_Bloc官網文檔:https://bloclibrary.dev/#/前面三個,是bloc作者寫的bloc模式文檔,典型的觀察者模式的應用,最原始的就是java中CallBack形式。前倆篇文章就是咱們這些大抄子的主要「參考」的資料來源,這三篇文章在掘金上有翻譯版,搜下bloc就能找到。最後一篇文章就是我主要總結歸納的源泉,作者在官網上寫了好幾個demo:計時器,登錄,Todos,天氣等等,大家可以自己去看看。問題初次使用flutter_bloc框架,可能會有幾個疑問1. state裡面定義了太多變量,某個事件只需要更新其中一個變量,其它的變量賦相同值麻煩2. 進入某個模塊,進行初始化操作:複雜的邏輯運算,網絡請求等,入口在哪定義好了,嗶嗶了一堆,看下咱們要用flutter_bloc實現的效果。直接打開Chrome演示,大家在虛擬機上跑也一樣。先說明下,bloc給的api很多,不同的api針對與解決場景不同,我要是把官網那些api全抄過也沒啥意義;不,也有可能可以裝幣,我要是不說明,大家說不定以為是我自己總結的呢!哈哈。OK,大家要是想知道全場景的使用,可以去官網翻翻文檔,我覺得學習一個模式或者框架的時候,最主要的是把主流程跑通,起碼可以符合標準的堆頁面,這樣的話,就可以把這玩意用起來,再遇到想要的什麼細節,就可以自己去翻文檔,畢竟大體上已經懂了,寫過了幾個頁面,也有些體會,再去翻文檔就很快能理解了。

flutter_bloc: ^6.0.6 #狀態管理框架
equatable: ^1.2.3 #增強組件相等性判斷

看看flutter_bloc都推到6.0了,別再用StreamController手搭Bloc了!在Android Studio設置的Plugins裡,搜索:Bloc右擊相應的文件夾,選擇「Bloc Class」,我在main文件夾新建的,填入的名字:main,就自動生成下面三個文件;:main_bloc,main_event,main_state;main_view是我自己新建,用來寫頁面的。是不是覺得,還在手動新建這些bloc文件low爆了;就好像fish_redux,不用插件,讓我手動去創建那六個文件,寫那些模板代碼,真的要原地爆炸。初始化代碼來看下這三個生成的bloc文件:main_bloc,main_event,main_statemain_bloc:這裡就是咱們主要寫邏輯的頁面了mapEventToState方法只有一個參數,後面自動帶了一個逗號,格式化代碼就分三行了,建議刪掉逗號,格式化代碼。

class MainBloc extends Bloc<MainEvent, MainState> {
  MainBloc() : super(MainInitial());

  @override
  Stream<MainState> mapEventToState(
    MainEvent event,
  ) async* {
    // TODO: implement mapEventToState
  }
}

main_event:這裡是執行的各類事件,有點類似fish_redux的action層

@immutable
abstract class MainEvent {}

@immutable
abstract class MainState {}

class MainInitial extends MainState {}

實現這裡對於簡單的頁面,state的使用抽象狀態繼承實現的方式,未免有點麻煩,這裡我進行一點小改動,state的實現類別有很多,官網寫demo也有不用抽象類,直接class,類似實體類的方式開搞的。老夫在代碼關鍵點寫上"///"類型注釋,大家仔細看看,拷進Android Studio裡面,這些地方會變綠!大家好好體會下綠色代碼!state變量是框架內部定義的,會默認保存上一次同步的MainSate對象的值

class MainBloc extends Bloc<MainEvent, MainState> {
  MainBloc() : super(MainState(selectedIndex: 0, isExtended: false));

  @override
  Stream<MainState> mapEventToState(MainEvent event) async* {
    ///main_view中添加的事件,會在此處回調,此處處理完數據,將數據yield,BlocBuilder就會刷新組件
    if (event is SwitchTabEvent) {
      ///獲取到event事件傳遞過來的值,咱們拿到這值塞進MainState中
      ///直接在state上改變內部的值,然後yield,只能觸發一次BlocBuilder,它內部會比較上次MainState對象,如果相同,就不build
      yield MainState()
        ..selectedIndex = event.selectedIndex
        ..isExtended = state.isExtended;
    } else if (event is IsExtendEvent) {
      yield MainState()
        ..selectedIndex = state.selectedIndex
        ..isExtended = !state.isExtended;
    }
  }
}

在這裡就能看見,view觸發了那些事件了;維護起來也很爽,看看這裡,也很快能懂頁面在幹嘛了

@immutable
abstract class MainEvent extends Equatable{
  const MainEvent();
}
///切換NavigationRail的tab
class SwitchTabEvent extends MainEvent{
  final int selectedIndex;

  const SwitchTabEvent({@required this.selectedIndex});

  @override
  List<Object> get props => [selectedIndex];
}
///展開NavigationRail,這個邏輯比較簡單,就不用傳參數了
class IsExtendEvent extends MainEvent{
  const IsExtendEvent();

  @override
  List<Object> get props => [];
}


state有很多種寫法,在bloc官方文檔上,不同項目state的寫法也很多這邊變量名可以設置為私用,用get和set可選擇性的設置讀寫權限,因為我這邊設置的倆個變量全是必用的,讀寫均要,就設置公有類型,不用下劃線「_」去標記私有了。

class MainState{
   int selectedIndex;
   bool isExtended;

   MainState({this.selectedIndex, this.isExtended});
}

對於生成的模板代碼,我們在這:去掉@immutable註解,去掉abstract;這裡說下加上@immutable和abstract的作用,這邊是為了標定不同狀態,拿很典型的列表數據加載說明,列表加載的時候一般有三種狀態獲取數據前,列表的布局展示空樣式:LoadingBeforeState獲取數據失敗,顯示出加載失敗的布局,或提升重新加載的樣式提升:LoadingFailureState獲取數據成功,顯示出列表數據:LoadingSuccessState針對上面三種狀態,需要展示不同的布局,這樣我們就可以繼承抽象的狀態類:LoadingState,針對不同狀態實現上面三種不同的狀態類,不同的狀態可以定義不同的參數,然後在view中去判斷調用這種實現不同狀態,對不同狀態進行管理,有點設計模式中-狀態模式的味道下面代碼是對上述描述的一種代碼展示,可以瞧瞧;跑demo的時候,這下面的代碼就不用抄了,僅做演示

@immutable
abstract class LoadingState extends Equatable {}
class LoadingInitial extends LoadingState {
  @override
  List<Object> get props => [];
}
class LoadingBeforeSate extends LoadingState{
  ///實現相應的欄位信息
  @override
  List<Object> get props => [];
}
class LoadingFailureState extends LoadingState{
  ///實現相應的欄位信息
  @override
  List<Object> get props => [];
}
class LoadingSuccessState extends LoadingState{
  ///實現相應的欄位信息
  @override
  List<Object> get props => [];
}

///在View中使用,偽代碼
BlocBuilder<MainBloc, MainState>(builder: (context, state) {
  if(state is LoadingBeforeSate){
    return Beforewidget(state.XX,..);
  } else if(state is LoadingFailureState){
    return FailureWidget(state.XX,state.XX,...);
  } else if(state is LoadingSuccessState){
    return SuccessWidget(state.XX);
  } else {
    return ErrorWidget(...);
  }
})

這邊就是咱們的界面層了,很簡單,將需要刷新的組件,用BlocBuilder包裹起來,使用BlocBuilder:提供的state去賦值就ok了,context去添加執行的事件,context用StatelessWidget中提供的或者BlocBuilder提供的都行

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MainPage(),
    );
  }
}

class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    ///創建BlocProvider的,表明該Page,我們是用MainBloc,MainBloc是屬於該頁面的Bloc了
    return BlocProvider(
      create: (BuildContext context) => MainBloc(),
      child: BodyPage(),
    );
  }
}

class BodyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Bloc')),
      body: totalPage(),
    );
  }
}

Widget totalPage() {
  return Row(
    children: [
      navigationRailSide(),
      Expanded(child: Center(
        child: BlocBuilder<MainBloc, MainState>(builder: (context, state) {
          ///看這看這:刷新組件!
          return Text("selectedIndex:" + state.selectedIndex.toString());
        }),
      ))
    ],
  );
}

//增加NavigationRail組件為側邊欄
Widget navigationRailSide() {
  //頂部widget
  Widget topWidget = Center(
    child: Padding(
      padding: const EdgeInsets.all(8.0),
      child: Container(
          width: 80,
          height: 80,
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            image: DecorationImage(
                image: NetworkImage("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3383029432,2292503864&fm=26&gp=0.jpg"),
                fit: BoxFit.fill),
          )),
    ),
  );

  //底部widget
  Widget bottomWidget = Container(
    child: BlocBuilder<MainBloc, MainState>(
      builder: (context, state) {
        return FloatingActionButton(
          onPressed: () {
            ///添加NavigationRail展開,收縮事件
            context.bloc<MainBloc>().add(IsExtendEvent());
          },
          ///看這看這:刷新組件!
          child: Icon(state.isExtended ? Icons.send : Icons.navigation),
        );
      },
    ),
  );

  return BlocBuilder<MainBloc, MainState>(builder: (context, state) {
    return NavigationRail(
      backgroundColor: Colors.white12,
      elevation: 3,
      ///看這看這:刷新組件!
      extended: state.isExtended,
      labelType: state.isExtended ? NavigationRailLabelType.none : NavigationRailLabelType.selected,
      //側邊欄中的item
      destinations: [
        NavigationRailDestination(
            icon: Icon(Icons.add_to_queue),
            selectedIcon: Icon(Icons.add_to_photos),
            label: Text("測試一")),
        NavigationRailDestination(
            icon: Icon(Icons.add_circle_outline),
            selectedIcon: Icon(Icons.add_circle),
            label: Text("測試二")),
        NavigationRailDestination(
            icon: Icon(Icons.bubble_chart),
            selectedIcon: Icon(Icons.broken_image),
            label: Text("測試三")),
      ],
      //頂部widget
      leading: topWidget,
      //底部widget
      trailing: bottomWidget,
      selectedIndex: state.selectedIndex,
      onDestinationSelected: (int index) {
        ///添加切換tab事件
        context.bloc<MainBloc>().add(SwitchTabEvent(selectedIndex: index));
      },
    );
  });
}

反思從上面的代碼來看,實際存在幾個隱式問題,這些問題,剛開始使用時候,沒異常的感覺,但是使用bloc久了後,感覺肯定越來越強烈初始化問題:這邊初始化是在bloc裡,直接在構造方法裡面賦初值的,state中一旦變量多了,還是這麼寫,會感覺極其難受,不好管理。需要優化可以看見這邊我們只改動selectedIndex或者isExtended;另一個變量不需要變動,需要保持上一次的數據,進行了此類:state.selectedIndex或者state.isExtended賦值,一旦變量達到十幾個乃至幾十個,還是如此寫,是讓人極其崩潰的。需要優化如果進行一個頁面,需要進行複雜的運算或者請求接口後,才能知曉數據,進行賦值,這裡肯定需要一個初始化入口,初始化入口需要怎樣去定義呢?優化實現首先來看看我們對state中的優化,這邊進行了倆個很重要優化,增加倆個方法:init()和clone()init():這裡初始化統一用init()方法去管理clone():這邊克隆方法,是非常重要的,一旦變量達到倆位數以上,就能深刻體會該方法是多麼的重要

class MainState {
  int selectedIndex;
  bool isExtended;

  ///初始化方法,基礎變量也需要賦初值,不然會報空異常
  MainState init() {
    return MainState()
      ..selectedIndex = 0
      ..isExtended = false;
  }

  ///clone方法,此方法實現參考fish_redux的clone方法
  ///也是對官方Flutter Login Tutorial這個demo中copyWith方法的一個優化
  ///Flutter Login Tutorial(https://bloclibrary.dev/#/flutterlogintutorial)
  MainState clone() {
    return MainState()
      ..selectedIndex = selectedIndex
      ..isExtended = isExtended;
  }
}

這邊定義一個MainInit()初始化方法,同時去掉Equatable繼承,在我目前的使用中,感覺它用處不大。。。

@immutable
abstract class MainEvent {}

///初始化事件,這邊目前不需要傳什麼值
class MainInitEvent extends MainEvent {}

///切換NavigationRail的tab
class SwitchTabEvent extends MainEvent {
  final int selectedIndex;

  SwitchTabEvent({@required this.selectedIndex});
}

///展開NavigationRail,這個邏輯比較簡單,就不用傳參數了
class IsExtendEvent extends MainEvent {}

這增加了初始化方法,請注意,如果需要進行異步請求,同時需要將相關邏輯提煉一個方法,咱們在這裡配套Future和await就能解決在異步場景下同步數據問題這裡使用了克隆方法,可以發現,我們只要關注自己需要改變的變量就行了,其它的變量都在內部賦值好了,我們不需要去關注;這就大大的便捷了頁面中有很多變量,只需要變動一倆個變量的場景注意:如果變量的數據未改變,界面相關的widget是不會重繪的;只會重繪變量被改變的widget

class MainBloc extends Bloc<MainEvent, MainState> {
  MainBloc() : super(MainState().init());

  @override
  Stream<MainState> mapEventToState(MainEvent event) async* {
    ///main_view中添加的事件,會在此處回調,此處處理完數據,將數據yield,BlocBuilder就會刷新組件
    if (event is MainInitEvent) {
      yield await init();
    } else if (event is SwitchTabEvent) {
      ///獲取到event事件傳遞過來的值,咱們拿到這值塞進MainState中
      ///直接在state上改變內部的值,然後yield,只能觸發一次BlocBuilder,它內部會比較上次MainState對象,如果相同,就不build
      yield switchTap(event);
    } else if (event is IsExtendEvent) {
      yield isExtend();
    }
  }

  ///初始化操作,在網絡請求的情況下,需要使用如此方法同步數據
  Future<MainState> init() async {
    return state.clone();
  }

  ///切換tab
  MainState switchTap(SwitchTabEvent event) {
    return state.clone()..selectedIndex = event.selectedIndex;
  }

  ///是否展開
  MainState isExtend() {
    return state.clone()..isExtended = !state.isExtended;
  }
}

view層代碼太多,這邊只增加了個初始化事件,就不重新把全部代碼貼出來了,初始化操作直接在創建的時候,在XxxBloc上使用add()方法就行了,就能起到進入頁面,初始化一次的效果;add()方法也是Bloc類中提供的,遍歷事件的時候,就特地檢查了add()這個方法是否添加了事件;說明,這是框架特地提供了一個初始化的方法這個初始化方式是在官方示例找到的,項目名:Flutter Infinite List Tutorial(https://bloclibrary.dev/#/flutterinfinitelisttutorial?id=flutter-infinite-list-tutorial)

class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      ///在MainBloc上使用add方法,添加初始化事件
      create: (BuildContext context) => MainBloc()..add(MainInitEvent()),
      child: BodyPage(),
    );
  }
}
///下方其餘代碼省略.

搞定OK,經過這樣的優化,解決了幾個痛點。實際在view中反覆是要用BlocBuilder去更新view,寫起來有點麻煩,這裡我們可以寫一個,將其中state和context變量,往提出來的Widget方法傳值,也是蠻不錯的大家保持觀察者模式的思想就行了;觀察者(回調刷新控制項)和被觀察者(產生相應事件,添加事件,去通知觀察者),bloc層是處於觀察者和被觀察者中間的一層,我們可以在bloc裡面搞業務,搞邏輯,搞網絡請求,不能搞基;拿到Event事件傳遞過來的數據,把處理好的、符合要求的數據返回給view層的觀察者就行了。使用框架,不拘泥框架,在觀察者模式的思想上,靈活的去使用flutter_bloc提供Api,這樣可以大大的縮短我們的開發時間!Cubit是Bloc模式的一種簡化版,去掉了event這一層,對於簡單的頁面,用Cubit來實現,開發體驗是大大的好啊,下面介紹下該種模式的寫法創建首先創建Cubit一組文件,選擇「Cubit Class」,點擊,新建名稱填寫:Counter新建好後,他會生成倆個文件:counter_cubit,counter_state,來看下生成的代碼

class CounterCubit extends Cubit<CounterState> {
  CounterCubit() : super(CounterInitial());
}

@immutable
abstract class CounterState {}

class CounterInitial extends CounterState {}

按照生成的這種state方式去寫,比較麻煩,這邊調整下

class CounterCubit extends Cubit<CounterState> {
  CounterCubit() : super(CounterState().init());
}

class CounterState {
  ///初始化方法
  CounterState init() {
    return CounterState();
  }

  ///克隆方法,針對於刷新界面數據
  CounterState clone() {
    return CounterState();
  }
}

OK,這樣調整了下,下面寫起來就會舒服很多,也會很省事實現計時器來看下實現效果吧,這邊不上圖了,大家點擊下面的連結,可以直接體驗Cubit模式寫的計時器,實現效果:(https://cnad666.gitee.io/flutter_use/#/counter)實現很簡單,三個文件就搞定,看下流程:state -> cubit -> view

class CounterState {
  int count;

  CounterState init() {
    return CounterState()..count = 0;
  }

  CounterState clone() {
    return CounterState()..count = count;
  }
}

event層實際是所有行為的一種整合,方便對邏輯過於複雜的頁面,所有行為的一種維護;但是過於簡單的頁面,就那麼幾個事件,還單獨維護,就沒什麼必要了在cubit層寫的公共方法,在view裡面能直接調用,更新數據使用:emit()cubit層應該可以算是:bloc層和event層一種結合後的簡寫

class CounterCubit extends Cubit<CounterState> {
  CounterCubit() : super(CounterState().init());

  ///自增
  void increase() => emit(state.clone()..count = ++state.count);
}

view層的代碼就非常簡單了,點擊方法裡面調用cubit層的自增方法就ok了

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (BuildContext context) => CounterCubit(),
      child: BlocBuilder<CounterCubit, CounterState>(builder: _counter),
    );
  }

  Widget _counter(BuildContext context, CounterState state) {
    return Scaffold(
      appBar: AppBar(title: const Text('Cubit範例')),
      body: Center(
        child: Text('點擊了 ${state.count} 次', style: TextStyle(fontSize: 30.0)),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.bloc<CounterCubit>().increase(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

總結在Bloc模式裡面,如果頁面不是過於複雜,使用Cubit去寫,基本完全夠用了;但是如果業務過於複雜,還是需要用Bloc去寫,需要將所有的事件行為管理起來,便於後期維護OK,Bloc的簡化模塊,Cubit模式就這樣講完了,對於自己業務寫的小項目,我就經常用這個Cubit去寫Bloc還有很多Api針對不同的場景非常的實用,例如:MultiBlocProvider,BlocListener,MultiBlocListener,BlocConsumer等等,這裡面有些Api和Provider的Api是非常相似的,例如MultiXxxxx,這都是為了減少嵌套,提供多個全局Bloc而提供,大家可以去瞧瞧看,用法也都非常的相似https://github.com/CNAD666/ExampleCode/tree/master/Flutter/flutter_usehttps://github.com/CNAD666/book_managehttps://bloclibrary.dev/#/flutterbloccoreconcepts?id=bloc-widgetsGitHub:https://github.com/felangel/blocPub:https://pub.dev/packages/flutter_bloc

相關焦點

  • 探索Flutter混合開發技術方案(下)——淺析Flutter Boost原理
    好不容易閒下來了,只想著狠狠睡一覺,睡醒了才想起來還有幾篇計劃要寫的博客。對了,公眾號的名字改成了開水Kais,未來會更多地去聊一下人生的暢想,寫一些心情隨記,不再單單是寫技術類的文章。上篇講了Native和Flutter端混合開發的交互方式,並由此引出了Flutter Boost的基本用法。
  • Flutter 原理及美團實踐
    在Flutter誕生之前,已經有許多跨平臺UI框架的方案,比如基於WebView的Cordova、AppCan等,還有使用HTML+JavaScript渲染成原生控制項的React Native、Weex等。
  • 半小時帶你入門 Flutter
    國慶後面兩天在家學習整理了一波flutter,基本把能擼過能看到的代碼都過了一遍,此文篇幅較長,建議保存(star)再看。Questions tagged [flutter]img本文我們從介紹flutter基本概念到梳理常用Widget到常用app demos編寫到~放棄~,希望可以幫助每一個像我一樣的初學者。有誤地方還望大神不吝賜教~
  • Flutter 完整開發實戰詳解 (Flutter 畫面渲染的全面解析) | 開發者說·DTalk
    I/flutter (32494): TransformLayer#f8fa5I/flutter (32494): │ owner: RenderView#2d51eI/flutter (32494): │ creator: [root]I/flutter (32494): │ offset: Offset(0.0, 0.0)I/flutter (32494
  • Flutter Widgets 之 Container
    無任何參數設置如果只用Container包裝子控制項而沒有任何其他參數的設置,代碼如下:Container( child: Text('老孟'), )Container內的子控制項不會發生任何外觀上的變化
  • Flutter項目實戰:編寫一個非常精美的Flutter Todo-List項目
    下面就我來帶領各位參觀參觀這個項目的內部構造第三方庫項目中使用了一些非常優秀的第三方庫,也特別感謝這些開發者們,讓我的發量保持健康下面就是這些控制項的信息控制項說明dio網絡請求shared_preferences本地存儲provider狀態管理test單元測試carousel_slider滑動控制項circle_list環形列表intlintl語言包sqflite本地資料庫flutter_colorpicker
  • flutter逆向演練
    大家周末好,現在的app框架是越來越多了,uniapp->flutter->webview與java交互...
  • Flutter 開發從 0 到 1(六)Markdown 與代碼高亮
    準備將 flutter_markdown 添加到 pubspec.yaml 文件中:dependencies:  flutter_markdown: ^0.4.4項目根目錄執行如下命令安裝 flutter_markdown
  • Flutter Widget - Container 布局詳解
    也就是說,最大寬度和最大高度,都是 double.INFINITY。• 一個嘗試儘可能大的 widget,遇到無限制約束的時候,是不會起作用的,因為它不知道到底該有多大,在 debug 模式下,就會拋出異常。• 最常見的擁有無限制約束的情況,就是嵌入在彈性盒子裡面,比如 Row,Column 或者可以滾動的區域(ListView 或者其他 ScrollView 子類)。
  • 編寫一個非常精美的Flutter Todo-List項目
    取色框cached_network_image圖片緩存image_picker圖片選取permission_handler權限申請path_provider路徑獲取image_crop圖片裁剪flutter_svgsvg解析package_info獲取package信息flutter_webview_plugin網頁pull_to_refresh上拉加載photo_vie圖片展示url_launcher
  • 通過與React的簡單對比來入門Flutter
    還有一個不太準確的類比,我們都知道Java可以在大部分平臺上運行,這都得益於Java是跑在Java虛擬機上,這使得平臺的差異消失了)。當然由於這種形式會造成一些缺點,因為我們的頁面實際上是繪圖引擎畫出的一張圖片,所以類似於「選中某些文字然後複製」這種功能實現起來就會變得比較困難。為什麼Flutter高性能?
  • Flutter - 不聽話的 Container
    前言在閱讀本文之前我們先來回顧下在 Flutter 開發過程中,是不是經常會遇到以下問題:•Container 設置了寬高無效•Column 溢出邊界,Row 溢出邊界•什麼時候該使用 ConstrainedBox 和 UnconstrainedBox每當遇到這種問題
  • 2020年20個Flutter最漂亮的UI庫和項目
    flutter_swiper 地址:https://github.com/best-flutter/flutter_swiperflutter-ui-nice 地址:https://github.com/nb312/flutter-ui-nice
  • flutter實現簡單的旋轉動畫
    前言flutter實現簡單的旋轉動畫,和大家一起學習探討。
  • 愛奇藝開播助手Flutter跨平臺Hybrid實踐
    除了渲染性能之外,Flutter 還有一個非常誘人的特性:HotReload,在 debug 下的 Flutter 工程可以快速熱重載到真機上,修改完代碼後 Ctrl+S 就能實時展現在真機界面上,不需要重新安裝 apk 包
  • 用前端最舒服的躺姿 "搞定" Flutter
    然後「創建一個Flutter項目」(https://flutter.cn/docs/get-started/codelab)。項目創建完成,可以先用 flutter run 跑一下。flutter doctor // 讓 flutter 醫生檢查一下環境和配置,得到四個 ☑ 後,恭喜你通過考核。flutter run // 奔跑吧...好了,跑起來了吧,你會看到一個計數的官方示例,點擊加號圖片可以做加運算。這時候我們看項目的project 目錄裡 有一個入口文件叫 main.dart。
  • 從Container尺寸之謎看Flutter的渲染規則
    import 'package:flutter/material.dart';  2.    3.  void main() => runApp(MaterialApp(  4.        title: 'Travel',  5.
  • 已開源|碼上用它開始Flutter混合開發——FlutterBoost
    我們可以這樣簡單去理解這個方案:我們把共享的Flutter View當成一個畫布,然後用一個Native的容器作為邏輯的頁面。每次在打開一個容器的時候我們通過通信機制通知Flutter View繪製成當前的邏輯頁面,然後將Flutter View放到當前容器裡面。
  • FlutterBoost1.0到2.0,我一共做了這幾件事...
    VM的內存增長從內存分析的Anonymous VM上看不出VM的內存增長的原因,但從升級後有多個VC這個角度看,有許多可解釋這個增長的地方。目前我們推薦兩種方式:通過頁面棧裡頁面個數限制來避免過多頁面導致OOM。如閒魚這邊限制了頁面多次push後,僅保留最近3個頁面。避免從Flutter頁面打開Flutter頁面就新建FlutterVC,可重用上次的FlutterVC。FlutterBoost提供了這種能力,BoostContainer繼承了Navigator,原生支持Navigator的能力。
  • 這種英語用法正是你最為生疏的嗎?
    其實只有「英語母語者」,才能把一個詞的釋義、用法全部搞懂,進而把這些詞用「活」了;而「非英語母語者」,由於缺少「環境的薰陶」,聽到這些詞「最生活」「最地道」的用法,反而就會覺得「生疏」了。我以小見大舉個例子。