用 Flutter 水一個可醜的漸變首頁(二)

Keriy 發表於 2019-10-20

該考慮Provider

官方說好用,那麼用就是了~

使用provider是為了更好的進行狀態管理,為什麼要進行狀態管理?當然你怕你元件多了自己瞎搞,亂成一鍋粥~

沒圖啊,效果圖再貼一遍吧,狗帶

效果圖

看似理性的分析

  1. 要時間滾動日記卡片的時候,下面的導航按鈕隨著改變顏色,導航欄就必須知道,當前滾到第幾頁(double)了,才能做出相應的改變
  2. 導航欄和日記卡片使用相同的色盤:
    /// 色盤: 寫兩個意思一下,十二個月,應該有十二個。。
     static const List<List<Color>> linerColor = [
         [
           Color.fromARGB(255, 87, 211, 255),
           Color.fromARGB(255, 86, 173, 254),
         ],
         [
           Color.fromARGB(255, 86, 173, 254),
           Color.fromARGB(255, 82, 118, 254),
         ],
     ];
    複製程式碼
  3. 日記卡片在滾動的時候儘量減少其他元件的build
  4. 底部導航要寫漸變動畫嗎?不用,卡片的滾動會通知到導航欄,使其重新build,只要我build的足夠快,你的眼睛就跟不上我。。。動畫也不是這麼搞的嘛,笑摸我狗頭~

provider走起來

有了上面的分析,我們的provider只要通知page就夠了,

/// 哇靠,怎麼和官方的寫法有差別!!
class HomeState with ChangeNotifier {
  HomeState(this._ctrl) : assert(_ctrl != null) {
    _ctrl.addListener(() {
      _curPage = _ctrl.page.floor();
      notifyListeners();
    });
  }

  final PageController _ctrl;

  int get curPage => _curPage;

  double get value => _ctrl?.page ?? 0;

  int _curPage = 0;

  void setPage(int index) {
    _curPage = index;
    notifyListeners();
  }

  void buildChild() {
    notifyListeners();
  }
 }
複製程式碼

哇靠,怎麼和官方的寫法有差別!!其實沒什麼差別,只是我們有一個addListener的操作,而我們的PageController不是由HomeState來管理的。(其實是可以放在HomeState管理,當時有一個什麼顧慮,我現在想不起來了,可怕。。繼續)

這裡我可以看到,只要滾動卡片就會buildChild

拆分元件

剛開始的時候,我們的頁面都堆在一個頁面裡,看起來及其凶殘,現在外面來拆分一下,

_FloatBtnWidget懸浮的新增按鈕

class _FloatBtnWidget extends StatelessWidget {
  _FloatBtnWidget(this._homeProvider);

  final HomeState _homeProvider;

  @override
  Widget build(BuildContext context) {
    double cil = _homeProvider.value - _homeProvider.value.floor();
    double lerp = cil == 0 ? 1 : cil;
    return Container(
      width: 56,
      height: 56,
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        gradient: LinearGradient(
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
          colors: [
            Color.lerp(
              StaticStyle.linerColor[_homeProvider.curPage][0],
              StaticStyle.linerColor[_homeProvider.value.ceil()][0],
              lerp,
            ),
            Color.lerp(
              StaticStyle.linerColor[_homeProvider.curPage][1],
              StaticStyle.linerColor[_homeProvider.value.ceil()][1],
              lerp,
            ),
          ],
        ),
        boxShadow: [
          BoxShadow(
            color: Color.fromARGB(100, 87, 211, 255),
            blurRadius: 8,
          )
        ],
      ),
      child: Icon(Icons.add, color: Colors.white),
    );
  }
}
複製程式碼

_BottomNavWidget底部導航欄


class _BottomNavWidget extends StatelessWidget {
  _BottomNavWidget(this._homeProvider, this.tabState);

  final TabState tabState;
  final HomeState _homeProvider;

  @override
  Widget build(BuildContext context) {
    double cil = _homeProvider.value - _homeProvider.value.floor();
    double lerp = cil == 0 ? 1 : cil;

    final Gradient gradient = LinearGradient(
      begin: Alignment.topLeft,
      end: Alignment.bottomRight,
      colors: [
        Colors.black54,
        Colors.black,
      ],
    );

    final Gradient selectedGradient = LinearGradient(
      begin: Alignment.topLeft,
      end: Alignment.bottomRight,
      colors: [
        Color.lerp(
          StaticStyle.linerColor[_homeProvider.curPage][0],
          StaticStyle.linerColor[_homeProvider.value.ceil()][0],
          lerp,
        ),
        Color.lerp(
          StaticStyle.linerColor[_homeProvider.curPage][1],
          StaticStyle.linerColor[_homeProvider.value.ceil()][1],
          lerp,
        ),
      ],
    );

    return DecoratedBox(
      decoration: BoxDecoration(boxShadow: [
        BoxShadow(
          color: Color.fromARGB(100, 200, 200, 200),
          blurRadius: 8,
        )
      ]),
      child: ClipRRect(
        borderRadius: BorderRadius.only(topRight: Radius.circular(20), topLeft: Radius.circular(20)),
        child: BottomAppBar(
          elevation: 0,
          notchMargin: 6,
          shape: CircularNotchedRectangle(),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: <Widget>[
              GradientIconBtn(
                Icons.note_add,
                key: ValueKey('page-index-0'),
                iconSize: 26,
                gradient: gradient,
                selectedGradient: selectedGradient,
                selected: tabState.tabIndex == 0,
                onPress: () {
                  tabState.setTab(0);
                },
              ),
              Text(''),
              GradientIconBtn(
                Icons.person,
                key: ValueKey('page-index-1'),
                iconSize: 26,
                gradient: gradient,
                selectedGradient: selectedGradient,
                selected: tabState.tabIndex == 1,
                onPress: () {
                  tabState.setTab(1);
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}
複製程式碼

導航欄的選單也進行的封裝GradientIconBtn

class GradientIconBtn extends StatelessWidget {
  GradientIconBtn(
    this.icon, {
    Key key,
    @required this.onPress,
    this.iconSize,
    this.gradient,
    this.selectedGradient,
    this.selected = false,
  }) : super(key: key);

  final VoidCallback onPress;
  final IconData icon;
  final double iconSize;
  final Gradient gradient;
  final Gradient selectedGradient;
  final bool selected;

  @override
  Widget build(BuildContext context) {
    return IconButton(
      onPressed: onPress,
      icon: gradient == null
          ? Icon(icon, size: iconSize)
          : GradientText(
              iconData: icon,
              iconSize: iconSize,
              gradient: selected ? selectedGradient : gradient,
            ),
    );
  }
}
複製程式碼

最終組裝ChangeNotifierProvider

ChangeNotifierProvider<HomeState>(
  builder: (_) => HomeState(_pageController),
  child: Consumer<HomeState>(
    child: IndexedStack(
      index: widget.tabState.tabIndex,
      children: <Widget>[
        NoteYearViewPage(_pageController),
        MinePage(),
      ],
    ),
    builder: (_, HomeState homeProvider, Widget child) => Scaffold(
      body: child,
      floatingActionButton: _FloatBtnWidget(homeProvider),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
      bottomNavigationBar: _BottomNavWidget(homeProvider, widget.tabState),
    ),
  ),
)
複製程式碼

NoteYearViewPage是卡片頁面,比較簡單,寫死的漸變。MinePage是我的頁面,空蕩蕩。。

可以看到,我們使用了ChangeNotifierProvider,當收到buildChild事件後,就會build一個Scaffold而我們的child則會原封不動的放進Scaffold中,避免了重新build

搞定收工

好像完成了,呵呵呵~貼程式碼真是爽,整個過程只要28分鐘。。 中間有一些小細節可能沒有說明,想了解的小夥伴可以檢視原始碼: gayhub