仿閒魚底部導航欄帶有中間凸起圖示
剛接觸Flutter,需要實現一個類似閒魚APP的底部導航欄的實現
原始碼連結:
github.com/hanlin19900…
要實現的效果如圖:
在第一版本的時候, 只是為了實現按鈕凸起的效果,忽略了介面主體部分和底部導航欄之間的間距, 導致出現底部導航欄與介面主體部分有部分空白, 問題已經修改,效果圖如上, 修改程式碼見文章底部
好的,下面開始上程式碼了:
一. 在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 :此控制元件主要用於配置底部導航欄,詳細用法請參見官方文件,在此控制元件的使用中,我們需要設定三個屬性,
- items: 新增底部導航欄的每個Item
- onTap: 為底部導航欄設定點選事件
- 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>
複製程式碼
如此一來,我們就完成了,文章開始提出的需求了.
原始碼連結:
剛開始接觸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,
),
],
)複製程式碼