十年河東,十年河西,莫欺少年窮
學無止境,精益求精
鄙人最初Net程序猿一枚,因被逼,自學了物聯網及網絡編程,又被逼,逐又自學了VUE及React,后又被逼,做起了小程序開發
人吶,都是被逼出來的
為什么要自定義navigationBar?
原生導航欄的限制
- 除了膠囊按鈕以外,原生導航欄只會出現返回按鈕和當用戶打開的小程序最底層頁面是非首頁時,默認展示的“返回首頁”按鈕 。
- 原生導航欄的標題文字的顏色只有黑白。
- 布局無法改變,不能做定制。
navigationBar是什么?
- 微信小程序一般來說有兩個bar,一個導航欄,一個tabbar(小程序下方一排切換按鈕),實現下方自定義tabbar的方法一般來說較為簡單,現在着重敘述上方自定義導航欄的實現。
小程序布局
- 談到導航欄與自定義導航欄,就需要解釋一下微信小程序的布局了。在小程序開發中使用wx.getSystemInfo() 方法可以獲取到系統信息。
- 部分獲取到的信息如上圖(截取自微信小程序開發者文檔),對我們理解布局有用的信息是以上跟寬度高度相關的屬性,如當前設備的屏幕高寬,可用高寬,以及saveArea。
- 上圖展示我們從systemInfo獲取到的數據的實際表現,以蘋果X的劉海屏為例(所有安卓劉海屏原理類似):最外層的紅色框即屏幕大小,藍色框即安全區域字面意思也就是開發者所能操縱的頁面區域,上面的黃色框即手機的狀態欄,綠色區域即我們要自定義的navigationBar。
- 可見,導航欄緊貼safeArea的上部,如果使用原生導航欄,導航欄下方即是真正意義的可操控范圍。
- 實際上我們自定義的導航欄也是在這個safeArea內與膠囊對齊最為和諧。很關鍵的原因就是微信將右上角的膠囊按鈕作為了內置組件,只有黑白兩種顏色,即我們無法改變它的大小位置透明度等等,所以為了配合膠囊按鈕,一般自定義的導航欄位置也與上圖位置一致。
自定義navigationBar怎么做?
去掉原生導航欄。
- 將需要自定義navigationBar頁面的page.json的navigationBarTitleText去掉。
- 加上"navigationStyle":"custom",這樣原生的導航欄就已經消失,甚至后退鍵也不會出現需要自定義。
- 另外,早在2016年微信已經開始適配沉浸式狀態欄,目前幾乎所有的機型里微信都是沉浸式狀態欄,也就是說去掉原生導航欄的同時,整個屏幕已經成為可編程區域。
計算navigationBarHeight。
- 原生的膠囊按鈕當然存在,那么下一步就需要你去定位出自定義的導航欄高度以及位置。
- 對於不同的機型,對於不同的系統,狀態欄以及膠囊按鈕的位置都不確定,所以需要用到一定的計算,從而面對任何機型都可以從容判定。
- 使用wx.getSystemInfo()獲取到statusBarHeight,這樣就確定了導航欄最基本的距離屏幕上方的據里。
- 使用wx.getMenuButtonBoundingClientRect()獲取到小程序的膠囊信息(注意這個api存在各種問題,在不同端表現不一致,后面會敘述這個api調用失敗的處理情況),如下圖,以下坐標信息以屏幕左上角為原點。
- 以下圖為例,上面的紅色框是statusBar,高度已知;下面的紅色框是正文內容,夾在中間的就是求解之一navigationBarHeight;而黃色的是原生膠囊按鈕也是在垂直居中位置,高度為膠囊按鈕基於左上角的坐標信息已知,不難得出,navigationBarHeight = 藍色框高度 × 2 + 膠囊按鈕.height。(藍色框高度 = 膠囊按鈕.top - statusBarHeight)
-
最后的計算公式為:navigationBarHeight = (膠囊按鈕.top - statusBarHeight) × 2 + 膠囊按鈕.height。navigationBar 距屏幕上方的距離即為navigationBarHeight。
-
這種計算方法在各種機型以及安卓ios都適用。
-
針對"wx.getMenuButtonBoundingClientRect()"獲取錯誤或者獲取數據為0的極少數情況,只能夠去模擬,對於android,一般navigationBarHeight為48px,而對於ios一般為40px,所有機型的膠囊按鈕高度是32px筆者也是通過網上很多的文章和自測得出的,這種誤差一般可以忽略。當然最理想的就是微信可以hold住所有機型,呵呵。最后提醒一下僅以真機為准,開發者工具的bug就更多不說了。

代碼實現
- 獲取本機信息,筆者一般寫在App的onLaunch中。
App.JS ,鄙人將獲取到的相關信息存放在了全局數據中 【APPJS中判斷了是否是分享進來的場景值,如果是分享進來的,則不顯示返回按鈕】
場景值可參考:https://developers.weixin.qq.com/miniprogram/dev/reference/scene-list.html

import { request } from "./Request/Request"; App({ globalData: { statusBarHeight: 0, //狀態欄高度 menuButtonHeight: 0, //膠囊按鈕高度 navigationBarHeight: 0, //計算得出的導航欄高度 navigationBarAndStatusBarHeight: 0, //導航欄和狀態欄高度之和 platform: "", //手機型號 android 或 IOS isShare:false //是否由分享而來 }, request: request, /** * 當小程序初始化完成時,會觸發 onLaunch(全局只觸發一次) */ onLaunch: function (options) { let that = this; // 判斷是否由分享進入小程序 從分享進入小程序時 返回上一級按鈕不應該存在 if (options.scene == 1007 || options.scene == 1008) { tthathis.globalData.isShare = true; } else { that.globalData.isShare = false; }; const { statusBarHeight, platform } = wx.getSystemInfoSync();//獲取系統信息 const { top, height } = wx.getMenuButtonBoundingClientRect(); console.log(wx.getSystemInfoSync()) that.globalData.platform=platform; //狀態欄高度 that.globalData.statusBarHeight = statusBarHeight; // 膠囊按鈕高度 一般是32px 如果獲取不到就使用32px that.globalData.menuButtonHeight = height ? height : 32; // 判斷膠囊按鈕信息是否成功獲取 if (top && top !== 0 && height && height !== 0) { // 導航欄高度 that.globalData.navigationBarHeight = (top - statusBarHeight) * 2 + height } else { //個別手機獲取不到 根據機型進行賦值 that.globalData.navigationBarHeight = platform === 'android' ? 48 : 40 } //用於絕對定位 占用空間 that.globalData.navigationBarAndStatusBarHeight= that.globalData.navigationBarHeight+that.globalData.statusBarHeight; }, /** * 當小程序啟動,或從后台進入前台顯示,會觸發 onShow */ onShow: function (options) { }, /** * 當小程序從前台進入后台,會觸發 onHide */ onHide: function () { }, /** * 當小程序發生腳本錯誤,或者 api 調用失敗時,會觸發 onError 並帶上錯誤信息 */ onError: function (msg) { } })
App.Json 中 window 配置 【切莫將背景色設置為黑色,否則時間,電量等信息顯示不出來】
"window":{ "navigationBarBackgroundColor": "#FFF", "navigationBarTitleText": "陳大六的小程序", "navigationBarTextStyle":"black", "enablePullDownRefresh":true, "backgroundTextStyle":"dark", "onReachBottomDistance":100 },
在組件Components中創建 navigationBar 文件夾及 navigationBar 組件
navigationBar.js
var app = getApp(); Component({ /** * 組件的屬性列表 */ properties: { title: { //導航欄標題 type: String, value: "wecaht" }, navigationtype: { //跳轉方式 type: String, value: "navigateTo" }, navigationurl: { //URL type: String, value: "/pages/index/index" } }, /** * 組件的初始數據 */ data: { //是否是分享頁面 由分享而來 不展示返回按鈕 isShare: app.globalData.isShare, // 狀態欄高度 statusBarHeight: app.globalData.statusBarHeight + 'px', // 導航欄高度 navigationBarHeight: app.globalData.navigationBarHeight + 'px', // 膠囊按鈕高度 menuButtonHeight: app.globalData.menuButtonHeight + 'px', // 導航欄和狀態欄高度 navigationBarAndStatusBarHeight: app.globalData.navigationBarAndStatusBarHeight + 'px' }, /** * 組件的方法列表 */ methods: { GoBack(e) { let that=this; let url=e.currentTarget.dataset.navigationurl; //console.log(e) if (e.currentTarget.dataset.navigationtype == "navigateTo") { wx.navigateTo({ url: url, }) } if (e.currentTarget.dataset.navigationtype== "navigateBack") { wx.navigateBack({ url: url, }) } } } })
.navigationBar.wxml 【分享進來的頁面,不顯示返回按鈕】
代碼如下:
<!--navigationBar.wxml--> <view class="navigation-container" style="{{'height: ' + navigationBarAndStatusBarHeight}}"> <!--空白來占位狀態欄--> <view style="{{'height: ' + statusBarHeight}}"></view> <!--自定義導航欄--> <view class="navigation-bar" style="{{'height:' + navigationBarHeight}}"> <view bindtap="GoBack" data-navigationtype="{{navigationtype}}" data-navigationurl="{{navigationurl}}" class="navigation-buttons" style="{{'height:' + menuButtonHeight}}"> <image wx:if="{{!isShare}}" class="nav-img" src='/pages/images/reback.png' mode="widthFix"/> </view> <view class="navigation-title" style="{{'line-height:' + navigationBarHeight}}">{{title}}</view> </view> </view> <!--空白占位,填充定位造成的空間飄逸--> <view style="{{'height: ' + navigationBarAndStatusBarHeight}}"></view>
.navigationBar.wxml 樣式如下:
/* navigationBar.wxss */ .navigation-container { position: fixed; width: 100%; z-index: 9999999; top: 0; left: 0; background-color: #ffffff; } .navigation-bar { position: relative; display: flex; flex-direction: row; align-items: center; } .navigation-buttons { display: flex; align-items: center; margin-left: 10px; box-sizing: border-box; background-color: transparent; width: 68rpx; } .statusbarClas { background: transparent; color:black; } .nav-img { width: 16rpx; } .navigation-title { position: absolute; left: 104px; right: 104px; text-align: center; font-size: 30rpx; color: #000000; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: 楷體; }
外部頁面引用該組件如下,
.Json文件,引入組件
{ "usingComponents": { "navigationBar":"../Components/navigationBar/navigationBar" }, "navigationStyle":"custom" }
注意添加屬性:"navigationStyle":"custom" 代表我們要自定義組件
.wxss文件,樣式如下:
/* packageA/component/successlv.wxss */ .table{ width: 100%; /* width: calc(100% - 52rpx); */ /* height: calc(100vh - 450rpx); */ height: 540rpx; box-sizing: border-box; /* padding: 0 26rpx; */ /* margin-left: 26rpx; */ background-color: #fff; } .dxBox{ height: 128rpx; width: 100%; margin-top: 24rpx; display: flex; justify-content: space-between; align-items: center; background-color: #fff; font-size: 28rpx; color: #303133; font-weight: 600; font-family: PingFangSC; padding: 0 40rpx 0 40rpx; box-sizing: border-box; }
.wxml代碼如下:
<navigationBar title="自定義導航欄" navigationtype="navigateTo" navigationurl="/pages/echarts/echarts"></navigationBar> <view class="dxBox"> <view>{{title}}</view> <view style="color:#727273">今日:2358次</view> </view>
最終效果如下:
自定義組件中需要父頁面傳值 Title 、navigationtype 、 navigationurl 三個屬性,用來展示導航標題、 跳轉方式、跳轉地址。
以上便是鄙人封裝的自定義導航欄
參考博客:
https://www.cnblogs.com/lst619247/p/13255097.html
https://www.jianshu.com/p/7393c800ba09
@天才卧龍的博客