Flutter學習筆記--仿閒魚底部導航欄帶有中間凸起圖示

小碼農沐楓發表於2018-07-28

仿閒魚底部導航欄帶有中間凸起圖示

剛接觸Flutter,需要實現一個類似閒魚APP的底部導航欄的實現
原始碼連結:  
github.com/hanlin19900…
要實現的效果如圖:

Flutter學習筆記--仿閒魚底部導航欄帶有中間凸起圖示

在第一版本的時候, 只是為了實現按鈕凸起的效果,忽略了介面主體部分和底部導航欄之間的間距, 導致出現底部導航欄與介面主體部分有部分空白, 問題已經修改,效果圖如上, 修改程式碼見文章底部

好的,下面開始上程式碼了:

一. 在main.dart檔案中,定義APP的入口資訊
import 'package:flutter/material.dart';
import 'pages/MainPage.dart';

void main() => runApp(LightLanguageClient());

class LightLanguageClient extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue
      ),
      home: MainPage(),
    );
  }
}
複製程式碼
二. 我們需要定義三個頁面,功能類似Android的Fragment,分別為HomePage.dart, AssistantPage.dart,MinePage.dart, 這三個頁面的程式碼很簡單:
import 'package:flutter/material.dart';

class HomePage extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    return _HomePageState();
  }
}

class _HomePageState extends State<HomePage>{
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Text('這是首頁'),
        ),
      ),
    );
  }
}
複製程式碼

這個三個頁面的程式碼都一樣就沒有都貼出來

三.現在我們就需要去建立我們的主頁了,"MainPage.dart"檔案
第一步,我們先去實現一個最簡單的底部導航欄
import 'package:flutter/material.dart';

import 'HomePage.dart';
import 'AssistantPage.dart';
import 'MinePage.dart';

class MainPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _MainPage();
  }
}

class _MainPage extends State<MainPage> with SingleTickerProviderStateMixin {
  PageController pageController;
  int page = 0;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
      body: new PageView(
        children: <Widget>[HomePage(), AssistantPage(), MinePage()],
        controller: pageController,
        onPageChanged: onPageChanged,
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('首頁')),
          BottomNavigationBarItem(icon: Icon(Icons.assessment), title: Text('助手')),
          BottomNavigationBarItem(icon: Icon(Icons.person), title: Text('我的')),
        ],
        onTap: onTap,
        currentIndex: page,
      ),
    ));
  }

  @override
  void initState() {
    super.initState();
    pageController = PageController(initialPage: this.page);
  }

  @override
  void dispose() {
    pageController.dispose();
    super.dispose();
  }

  void onTap(int index) {
    pageController.animateToPage(index,
        duration: const Duration(milliseconds: 300), curve: Curves.ease);
  }

  void onPageChanged(int page) {
    setState(() {
      this.page = page;
    });
  }
}
複製程式碼
在MainPage.dart中我們用到了幾個控制元件:
1. PageView : 此控制元件類似Android的ViewPager,把之前建立的3個頁面一次新增進去,之後需要給PageView設定一個控制器-PageController,給PageView設定一個onPageChanged頁面切換監聽方法,此方法的功能類似與Android中ViewPager中的OnPageChangeListener裡的監聽方法
2. BottomNavigationBar :此控制元件主要用於配置底部導航欄,詳細用法請參見官方文件,在此控制元件的使用中,我們需要設定三個屬性,
  1. items: 新增底部導航欄的每個Item
  2. onTap: 為底部導航欄設定點選事件
  3. currentIndex: 為底部導航設定當前選中項
3. BottomNavigationBarItem: 此控制元件是底部導航欄的Item
至此,我們實現了最基本的底部導航欄的實現

四.我們要實現仿閒魚的底部導航欄,需要重構一下底部導航欄,

重構方案:
1.把中間的文字去掉
2.在BottomNavigationBar控制元件的中上的位置放入一個圖片
3.重構底部導航的事件方法
4.禁止PageView的滑動事件
現在開始重構:
1.要在BottomNavigationBar上面覆蓋一個圖片,我們需要用到一個佈局Widget---Stack,類似於Framelayout
class _MainPage extends State<MainPage> with SingleTickerProviderStateMixin {
  PageController pageController;
  int page = 0;
  //新增圖片地址,需要動態更換圖片
  String bigImg = 'images/home_green.png';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
      body: new PageView(
        children: <Widget>[HomePage(), AssistantPage(), MinePage()],
        controller: pageController,
        onPageChanged: onPageChanged,
      ),
      //重構bottomNavigationBar
      bottomNavigationBar: Stack(
        children: <Widget>[
          Align(
            alignment: Alignment.bottomCenter,
            child: BottomNavigationBar(
              items: [
                BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('首頁')),
                BottomNavigationBarItem(icon: Icon(Icons.assessment), title: Text('')),
                BottomNavigationBarItem(icon: Icon(Icons.person), title: Text('我的')),
              ],
              onTap: onTap,
              currentIndex: page,
            ),
          ),
          Align(
            alignment: Alignment.bottomCenter,
            child: InkWell(
              child: new Image.asset(bigImg,width: 80.0,height: 80.0,),
              onTap:onBigImgTap,
            ),
          )
        ],
      )
    ));
  }

  @override
  void initState() {
    super.initState();
    pageController = PageController(initialPage: this.page);
  }

  @override
  void dispose() {
    pageController.dispose();
    super.dispose();
  }

//修改bottomNavigationBar的點選事件
  void onTap(int index) {
    if (index != 1) {
      setState(() {
        this.bigImg = 'images/home_green.png';
      });
    }
    pageController.animateToPage(index,
        duration: const Duration(milliseconds: 300), curve: Curves.ease);
  }

//新增圖片的點選事件
  void onBigImgTap() {
    setState(() {
      this.page = 1;
      this.bigImg = 'images/icon_home.png';
      onTap(1);
    });
  }

  void onPageChanged(int page) {
    setState(() {
      this.page = page;
    });
  }
}
複製程式碼

重構完成之後,效果圖如下,我們發現這並不是我們想要的,底部導航欄我們是實現了,但是PageView被遮蓋了

PageView被遮蓋的解決辦法,我們給Stack新增一個可以指定高度的父級--Container,修改的程式碼如下:
bottomNavigationBar: Container(
            height: 100.0,
            child: Stack(
              children: <Widget>[
                Align(
                  alignment: Alignment.bottomCenter,
                  child: BottomNavigationBar(
                    items: [
                      BottomNavigationBarItem(
                          icon: Icon(Icons.home), title: Text('首頁')),
                      BottomNavigationBarItem(
                          icon: Icon(Icons.accessibility_new), title: Text('')),
                      BottomNavigationBarItem(
                          icon: Icon(Icons.person), title: Text('我的')),
                    ],
                    onTap: onTap,
                    currentIndex: page,
                  ),
                ),
                Align(
                    alignment: Alignment.bottomCenter,
                    child: Padding(
                      padding: const EdgeInsets.only(bottom: 10.0),
                      child: InkWell(
                        child: new Image.asset(
                          bigImg,
                          width: 80.0,
                          height: 80.0,
                        ),
                        onTap: onBigImgTap,
                      ),
                    )),
              ],
            ),
          )
複製程式碼
然後我們需要禁止PageView的滑動,我們只需要在給PageView設定一個屬性就好了
physics: NeverScrollableScrollPhysics(),
複製程式碼
在執行Flutter專案的時候出現了一個問題,執行時會出現一段時間的白屏,解決辦法:

解決方案很簡單,Android原生的白屏問題可以通過為 Launcher Activity 設定 windowBackground 解決,而 Flutter 也是基於此辦法,同時優化了 Flutter 初始化階段的白屏問題(覆蓋一個launchView),只用兩步設定便能解決 Flutter 中白屏問題。

在專案的 android/app/src/main/res/mipmap-xhdpi/ 目錄下新增閃屏圖片;

開啟 android/app/src/main/res/drawable/launch_background.xml 檔案,這個檔案就是閃屏的背景檔案,具體如何設定可以查閱 Android Drawable,我在 demo 中的設定如下:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:drawable="@android:color/background_dark" />
 
  <!-- You can insert your own image assets here -->
  <item
    android:bottom="84dp">
    <bitmap
      android:src="@mipmap/launch_image" />
  </item>
</layer-list>
複製程式碼

如此一來,我們就完成了,文章開始提出的需求了.

原始碼連結:

github.com/hanlin19900…


剛開始接觸Flutter,程式碼寫的有些混亂,可能有些問題考慮不是很深入,有不足之處,還請各位大佬指出


感謝GitHub上大佬提出的問題, 解決辦法如下
//Stack裡面包裹兩個元件: Scaffold 和 FloatingActionButtonhome: Stack(
  children: <Widget>[
    //Scaffold裡面按照之前的寫法body->PageView,bottomNavigationBar直接使用BottomNavigationBar
    Scaffold(
      body: PageView(
        physics: NeverScrollableScrollPhysics(),
        children: <Widget>[
          new HomePage(),
          new AssistantPage(),
          new MinePage()
        ],
        controller: pageController,
        onPageChanged: onPageChanged,
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(
              icon: Icon(Icons.home), title: Text('首頁')),
          BottomNavigationBarItem(
              icon: Icon(Icons.assessment), title: Text('釋出')),
          BottomNavigationBarItem(
              icon: Icon(Icons.person), title: Text('我的')),
        ],
        onTap: onTap,
        currentIndex: page,
      ),
    ),
    //在整個介面的上層放一個FloatingActionButton 元件,位置在底部居中
    Align(
      child: Padding(
        padding: const EdgeInsets.only(bottom: 30.0),
        child: FloatingActionButton(
          child: new Image.asset(bigImg),
          onPressed: onBigImgTap,
        ),
      ),
      alignment: Alignment.bottomCenter,
    ),
  ],
)複製程式碼


推薦閱讀
1. Flutter中文網 
https://flutterchina.club/
2. 阿韋大神的Github 
https://github.com/AweiLoveAndroid/Flutter-learning
3. Flutter學習筆記 - 底部導航欄 
https://blog.csdn.net/u011045726/article/details/79583423


相關文章