小程序自定义导航栏适配(完美版)

48

1、发现问题

小程序页面自定义导航栏功能已经开放有些日子了(还不知道这个功能的可以先>>了解一下),这极大的提升了小程序开发的自由度,相信不少小伙伴已经使用过这个功能,同时也相信不少小伙伴在此功能开发过程中踩过同样的一些坑:

  • 机型多如牛毛:自定义导航栏高度在不同机型始终无法达到视觉上的统一;
  • 调皮的胶囊按钮:导航栏元素(文字,图标等)怎么也对不齐那该死的胶囊按钮;
  • 各种尺寸的全面屏,奇怪的刘海屏,简直要抓狂。

同样的,这些问题也是小灰经历过的。但是小灰相信,办法总比问题多,于是开始了自己的探究:

2、一探究竟

为了搞明白到底该怎么去适配,老规矩,我先翻了一波官方文档,还别说,官方还真有这么一段介绍了相关细节,>>详情点击

169d422007d60196?w=2482&h=492&f=png&s=307793

从图中分析,我们可以得到如下信息:

  • Android跟iOS有差异,表现在顶部到胶囊按钮之间的距离差了6pt
  • 胶囊按钮高度为32pt, iOS和Android一致

这。。。,好像并没有什么L用啊??这仅仅是普通屏幕为参照的,ipx, 安卓全面屏完全没介绍。
沉着冷静,我们接着分析:

  • 胶囊按钮到状态栏下边缘这块距离,好像是固定的?
  • 安卓这个图,好像有点奇怪?导航栏分为 状态栏+标题栏?
  • 如果车两个条件成立,那我们的问题是不是就解决了80%了?

那么我们来论证一下:

第一个问题:胶囊按钮到状态栏下边缘的距离是不是固定的?

很简单,我们写一个状态栏,通过wx.getSystemInfoSync().statusBarHeight设置高度
为了好测量,我们设置状态栏背景色为深色
js代码:

var sysinfo = wx.getSystemInfoSync();
this.setData({ 
     statusBarHeight:sysinfo.statusBarHeight 
})

wxml代码:

<view class="status-bar" style="height:{{statusBarHeight}}px"></view>

wxss代码:

.status-bar{
    background: rgb(141, 71, 71);
 }

效果图(iPhone6):

169d46522c51dde4?w=756&h=188&f=png&s=16261

效果图(iPhoneX):

169d4668092ad319?w=748&h=206&f=png&s=15022

效果图(安卓):
169d475f38427b32?w=718&h=186&f=png&s=15776

是不是有点眉目了?是的,从截图可以看出,iOS是一致的,但是Android好像有所差别。
那究竟距离是多少?我们用神器(微信截图)来量一量:

Android:
169d47c7bc7e6798?w=762&h=666&f=png&s=64794

iOS:
169d47f76e2cbcc8?w=812&h=666&f=png&s=67052

可以看出,iOS胶囊按钮与状态栏之间距离为:6px, Android为8px,并且经过测量,iOS各机型,Android各机型结果一致(由于篇幅原因,就不一一展示截图了,有兴趣的可以自行测量)

第二个问题:导航栏分为 状态栏+标题栏?

通过对第一个问题的论证,很明显能看出来确实是这样的。并且通过第一个问题的测量结果以及官方提供的数据,我们可以对标题栏高度进行计算:
  • 导航栏高度 = 胶囊按钮高度 + 状态栏到胶囊按钮间距 * 2
  • Android导航栏高度 = 32px + 8px * 2 = 48px
  • iOS导航栏高度 = 32px + 6px * 2 = 44px

*注:由于胶囊按钮是原生组件,为表现一直,其单位在个系统都为px,所以我们的自定义导航栏各个高度的单位都必需是px(切记不能用rpx),才能完美适配。

3、解决问题

通过上述分析,相信小伙伴们都能有一个解决问题的思路了,在上代码之前,小灰再给大家画一下重点:

  • 写自定义导航组件的时候,需要将组件结构一分为二:状态栏 + 标题栏
  • 状态栏高度可通过wx.getSystemInfoSync().statusBarHeight获取
  • 标题栏高度:安卓:48px,iOS:44px
  • 单位必需跟胶囊按钮一致,用px

话不多说,上代码(gitHub地址):
js:

Component({   
   properties: {        
    background: {            
        type: String,            
        value: 'rgba(255, 255, 255, 1)'        
    },        
    color: {            
        type: String,            
        value: 'rgba(0, 0, 0, 1)'        
    },        
    titleText: {            
        type: String,            
        value: '导航栏'        
    },        
    titleImg: {            
        type: String,            
        value: ''        
    },        
    backIcon: {            
        type: String,            
        value: ''        
     },        
    homeIcon: {            
        type: String,            
        value: ''        
    },        
    fontSize: {            
        type: Number,            
        value: 16        
    },        
    iconHeight: {            
        type: Number,            
        value: 19       
    },        
    iconWidth: {            
        type:Number,            
        value: 58        
    }    
   },    
attached: function(){        
   var that = this;        
   that.setNavSize();        
   that.setStyle();    
},    
 data: {
    },    
methods: {        
// 通过获取系统信息计算导航栏高度        
setNavSize: function() {            
var that = this                
    , sysinfo = wx.getSystemInfoSync()                
    , statusHeight = sysinfo.statusBarHeight                
    , isiOS = sysinfo.system.indexOf('iOS') > -1                
    , navHeight;            
if (!isiOS) {                
    navHeight = 48;            
   } else {                
    navHeight = 44;            
}            
that.setData({                
    status: statusHeight,                
    navHeight: navHeight            
  })        
},        
setStyle: function() {            
    var that  = this                
    , containerStyle                
    , textStyle                
    , iconStyle;            
    containerStyle = [                
        'background:' + that.data.background                
        ].join(';');            
        textStyle = [                
        'color:' + that.data.color,                
        'font-size:' + that.data.fontSize + 'px'            
        ].join(';');            
        iconStyle = [                
        'width: ' + that.data.iconWidth + 'px',                
        'height: ' + that.data.iconHeight + 'px'            
        ].join(';');            
        that.setData({               
             containerStyle: containerStyle,                
             textStyle: textStyle,                
             iconStyle: iconStyle            
        })        },        
        // 返回事件        
back: function(){            
    wx.navigateBack({                
        delta: 1            
    })            
    this.triggerEvent('back', {back: 1})        
},        
home: function() {            
    this.triggerEvent('home', {});       
 }   
 }})


wxml:

<view class='nav' style='height: {{status + navHeight}}px'>    
    <view class='status' style='height: {{status}}px;{{containerStyle}}'></view>                               <view class='navbar' style='height:{{navHeight}}px;{{containerStyle}}'>        <view class='back-icon' wx:if="{{backIcon}}" bindtap='back'>                    <image src='{{backIcon}}'></image>       
         </view>        
    <view class='home-icon' wx:if="{{homeIcon}}" bindtap='home'>            
        <image src='{{homeIcon}}'></image>        
    </view>        [链接描述][10]
    <view class='nav-icon' wx:if="{{titleImg}}">            
    <image src='{{titleImg}}' style='{{iconStyle}}'></image>       
     </view>
            <view class='nav-title' wx:if="{{titleText && !titleImg}}">
                <text style='{{textStyle}}'>{{titleText}}</text>
            </view>
        </view>
    </view>

wxss:

.navbar{
    position: relative
}
.back-icon, .home-icon{
    width: 28px;
    height: 100%;
    position: absolute;    
    transform: translateY(-50%);    
    top: 50%;    
    display: flex;
    }
.back-icon{    
    left: 16px;
}
.home-icon{    
    left: 44px
}
.back-icon image{    
    width: 28px;    
    height: 28px;    
    margin: auto;
}
.home-icon image{    
    width: 20px;    
    height: 20px;    
    margin: auto;
}
.nav-title, .nav-icon{    
    position: absolute;    
    transform: translate(-50%, -50%);    
    left: 50%;    
    top: 50%;    
    font-size: 0;    
    font-weight: bold;
}

运行效果图:
169d49ab1b497a06?w=748&h=181&f=png&s=19336
文字标题:

169d49deca93da69?w=746&h=132&f=png&s=19980

图片标题:
169d49c23c3c9a66?w=750&h=174&f=png&s=19465

4、总结

经过小灰的一番论证以及实践经验,最终总结出以上最终解决方案,但希望对小伙伴们有所帮助,如果小伙伴们觉得有用,记得给颗star哦 --> 点我,后续还会更新其他组件。

如果大家有更好的方案或者觉得小灰的方案有问题,欢迎大家留言。

你可能感兴趣的

zeno · 4月18日

ios标题栏应该是40px,44px有点高了

回复

0

应该不会的,您可以通过API:
((wx.getMenuButtonBoundingClientRect().top - wx.getSystemInfoSync().statusBarHeight) * 2) + wx.getMenuButtonBoundingClientRect().height
计算一下得到精确高度

小灰oO 作者 · 4月18日
0

会的,你这个应该是用微信小程序工具调的吧,我用真机都是40px最合适,44px过高

zeno · 4月18日
0

不过用api完美解决,谢啦

zeno · 4月18日
周振超 · 6月26日

刷新,头部会被拉下来,之前看到过可以不拉下来的方案,还需要优化哦

回复

0

我给你改了下结构和样式,空view里可以放加载中动画(好像不要也行),用flex换了大部分绝对定位,修复标题过长换行覆盖问题
<view class='nav' style='height: {{status + navHeight}}px'>
<view class='status' style='height: {{status}}px;{{containerStyle}}'></view>
<view class='navbar' style='height:{{navHeight}}px;{{containerStyle}}'>

<view class='capsule'>
  <view class='back-icon' wx:if="{{backIcon}}" bindtap='back'>
    <image src='{{backIcon}}'></image>
  </view>
  <view class='home-icon' wx:if="{{homeIcon}}" bindtap='home'>
    <image src='{{homeIcon}}'></image>
  </view>
</view>
<view class='nav-title'>
  <image src='{{titleImg}}' style='{{iconStyle}}' wx:if="{{titleImg}}"></image>
  <text style='{{textStyle}}' wx:if="{{titleText && !titleImg}}">{{titleText}}</text>
</view>

</view>
</view>
<view style="height:{{navHeight+status}}px;background: white;"></view>
.nav {
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 999;
}

.navbar {
display: flex;
align-items: center;
position: relative;
}

.capsule {
margin-left: 10px;
height: 30px;
background: rgba(255, 255, 255, 0.6);
border-radius: 16px;
display: flex;
align-items: center;
}

.capsule > view {
width: 45px;
height: 60%;
}

.capsule > view:nth-child(2) {
border-left: 1px solid #fff;
}

.capsule image {
display: block;
width: 50%;
height: 100%;
margin: auto;
}

.nav-title {
position: absolute;
left: 104px;
right: 104px;
top: 50%;
transform: translateY(-50%);
max-height: 30px;
text-align: center;
font-size: 14px;
font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

周振超 · 6月26日
载入中...