Flutter關於一個登陸頁之夜間模式

前言

Hello,你們好,又是很久沒有水掘金了,我又回來啦!!今天給你們帶來得仍是一個登陸頁,不過今天這個登陸頁又黑又亮,就像這個登陸頁又白又長,夜間模式相信你們都不陌生,AndridiOS得全新版本也立刻要支持夜間模式了,因此本篇文章帶你們簡單粗略得認識下在Flutter中夜間模式怎麼實現~bash

效果

01

實現思路

提及夜間模式其實並無什麼神奇之處,只不過是資源得替換罷了,最簡單得咱們能夠設置一個變量isDark用來判斷是不是夜間模式,若是是則加載夜間資源若是不是則加載日間資源。 不過具體實現起來咱們就須要小小得開動下腦筋了less

  1. 咱們如何設置一個全局變量
  2. 咱們如何通知組件更新去替換資源

鐺鐺鐺~InheritedWidget來幫你

咱們首先來思考第一個問題如何通知組件去更新替換資源,首先咱們能夠想一想簡單得兩個組件間如何傳遞數據,咱們能夠經過構造方法等比較粗暴得傳入好比~ide

class Test {
  var name;
  Test(this.name);
}

class A extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(child: B(test: Test('蘇武難飛')));
  }
}

class B extends StatelessWidget {
  final Test test;
  const B({Key key, this.test}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Text(test.name);
  }
}
複製代碼

easy,可是啊可是,這種簡單得傳入只適合很是簡單得頁面邏輯交互時使用,想一想一下咱們某個頁面有七八個甚至於更多組件時,這樣傳數據簡直就是,邏輯地獄呀Flutter得開發團隊顯然也是意識到這點了因此咱們本篇得主角InheritedWidget就該出場了!post

InheritedWidget能夠作啥鴨?

簡而言之,InheritedWidget 容許在 widget 樹中有效地向下傳播(和共享)信息。 InheritedWidget 是一個特殊的 Widget,它將做爲另外一個子樹的父節點放置在 Widget 樹中。該子樹的全部 widget 都必須可以與該 InheritedWidget 暴露的數據進行交互。字體

舉個🌰子,咱們有一個TodoList應用,有一個頁面A列表頁,有一個B添加頁 ui

02
爲了讓 B添加頁添加以後在 A列表頁能夠直接看到,咱們使用 InheritedWidget

InheritedWidget怎樣使用

class _TodoInherited extends InheritedWidget {
  _TodoInherited({Key key, @required Widget child, this.data})
      : super(key: key, child: child);

  final TodoState data;

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return true;
  }
}
複製代碼

很是簡單,InheritedWidget得特殊性在於它做爲一個樹節點其全部得子節點均可以可以與該InheritedWidget暴露得數據進行交互。又因爲要使用InheritedWidget得時候咱們的數據大部分都是須要動態改變得,So,咱們勢必要使用StatefulWidget,那麼咱們得完整代碼就應該以下:this

class Todo {
  String title;

  Todo(this.title);

}

class _TodoInherited extends InheritedWidget {
  _TodoInherited({Key key, @required Widget child, this.data})
      : super(key: key, child: child);

  final TodoState data;

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return true;
  }
}

class TodoWidget extends StatefulWidget {
  final Widget child;

  TodoWidget({Key key, this.child}) : super(key: key);

  static TodoState instanceOf(BuildContext context) {
    _TodoInherited inherited = (context
        .inheritFromWidgetOfExactType(_TodoInherited) as _TodoInherited);
    return inherited.data;
  }

  @override
  State<StatefulWidget> createState() => TodoState();
}

class TodoState extends State<TodoWidget> {
  List<Todo> _todoList = [];

  List<Todo> get todoList => _todoList;

  void addTodo(String title) {
    setState(() {
      _todoList.add(Todo(title));
    });
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new _TodoInherited(child: widget.child, data: this);
  }
}
複製代碼
  • Todo做爲咱們得數據模型保存在TodoState
  • _TodoInherited中保存TodoState得引用,方便調用獲取Todo
  • _TodoInherited中的updateShouldNotify方法是用來判斷是否更新重建
  • TodoWidget中的instanceOf方法是用來獲取TodoState方便獲取其中的數據
  • instanceOf中的context.inheritFromWidgetOfExactType是用來註冊綁定InheritedWidget

因此咱們的完整業務代碼應該以下:spa

void main() {
  runApp(TodoWidget(child: MyApp()));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'TodoList', debugShowCheckedModeBanner: false, home: HomePage());
  }
}

class HomePage extends StatelessWidget {

  Widget buildEmpty() {
    return ...;
  }

  Widget buildList(List<Todo> list) {
    return ...;
  }

  @override
  Widget build(BuildContext context) {
    List<Todo> list = TodoWidget.instanceOf(context).todoList;
    return list.isEmpty ? buildEmpty() : buildList(list);
  }

}

class AddTaskPage extends StatelessWidget {

  final controller = TextEditingController();

  void closeTask(BuildContext context) {
    Navigator.pop(context);
  }

  Widget getTextField() {
    return TextField(controller: controller);
  }

  @override
  Widget build(BuildContext context) {
    return onTap: () {
      TodoWidget.instanceOf(context).addTodo(controller.text);
      closeTask(context);
    }
  }
}
複製代碼

03

說了這麼多,夜間模式呢!!

相信你們理解了上面的內容對於夜間模式的實現應該也是胸中有竹子啦~我這邊介紹一個實現方法debug

class _CustomTheme extends InheritedWidget {
  final CustomThemeState data;

  _CustomTheme({this.data, Key key, @required Widget child})
      : super(key: key, child: child);

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    // TODO: implement updateShouldNotify
    return true;
  }
}

class CustomTheme extends StatefulWidget {
  final Widget child;
  final MyThemeKeys initialThemeKey;

  const CustomTheme({Key key, this.initialThemeKey, this.child})
      : super(key: key);

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

  static ThemeModel of(BuildContext context) {
    _CustomTheme inherited =
        (context.inheritFromWidgetOfExactType(_CustomTheme) as _CustomTheme);
    return inherited.data.theme;
  }

  static CustomThemeState instanceOf(BuildContext context) {
    _CustomTheme inherited =
        (context.inheritFromWidgetOfExactType(_CustomTheme) as _CustomTheme);
    return inherited.data;
  }
}

class CustomThemeState extends State<CustomTheme> {
  ThemeModel _model;

  ThemeModel get theme => _model;

  @override
  void initState() {
    _model = MyThemes.getThemeFromKey(widget.initialThemeKey);
    super.initState();
  }

  void changeTheme(MyThemeKeys themeKey) {
    print(themeKey);
    setState(() {
      _model = MyThemes.getThemeFromKey(themeKey);
    });
  }

  @override
  Widget build(BuildContext context) {
    return _CustomTheme(data: this, child: widget.child);
  }
}

enum MyThemeKeys { LIGHT, DARK }

class MyThemes {
  static final ThemeModel lightTheme = ThemeModel(
      imageUrl: 'assets/images/banner.png',
      backgroundColor: Color(0xffffffff),
      titleColor: Color(0xff3C4859),
      borderColor: Colors.black.withOpacity(0.3),
      isDark: false);

  static final ThemeModel darkTheme = ThemeModel(
      imageUrl: 'assets/images/banner_dark.png',
      backgroundColor: Color(0xff2B1C71),
      titleColor: Color(0xffffffff),
      borderColor: Colors.white.withOpacity(0.3),
      isDark: true);

  static ThemeModel getThemeFromKey(MyThemeKeys themeKey) {
    switch (themeKey) {
      case MyThemeKeys.LIGHT:
        return lightTheme;
      case MyThemeKeys.DARK:
        return darkTheme;
      default:
        return lightTheme;
    }
  }
}

void main() {
  runApp(CustomTheme(initialThemeKey: MyThemeKeys.LIGHT, child: MyApp()));
}

class MyApp extends StatelessWidget {
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        debugShowCheckedModeBanner: false,
        home: HomePage());
  }
}

複製代碼

而後咱們字體或者顏色等能夠直接從ThemeModel中獲取,可是有一些比較特殊的狀況可能須要特殊處理時,Flutter也能很是優雅方便的處理,好比:3d

Widget build(BuildContext context) {
    String image = CustomTheme.of(context).imageUrl;
    bool isDark = CustomTheme.of(context).isDark;
    Widget child = isDark
        ? Padding(...)
        : Padding(...);
    return child;
  }
複製代碼

Ok,至此咱們的夜間模式也算是實現了~

04

後記

本篇依然是一個很是簡單的內容分享,可是InheritedWidget是一個很是實用好用的組件,但願你們都熟練掌握!!登陸頁系列還在繼續,請你們敬請期待吧~ 距離掘金社區50個贊還差7個!!

鳴謝

[譯] Flutter 核心概念詳解: Widget、State、Context 及 InheritedWidget

Dynamic theming with Flutter - Flutter Community - Medium

相關文章
相關標籤/搜索