兼容横竖屏的TabBar定制
实现效果TabBar竖屏没有争议,iOS 和 Android 都是在底部,要说有什么不同,那也是类似Pinterest那样的底部悬浮式。横屏的话,iOS基本都是侧边按钮,Android 谷歌推荐是变成汉堡包然后通过侧滑出抽屉切换页面,帅是帅,但是现在全面屏手势出来抽屉这种交互立马被打入冷宫, YouTube网页版那种我觉得也不错。
具体代码可参考我的Github
所以我想做一个竖屏是横向一字排开,横屏是纵向一字排开的TabBar。
父布局的选择
最简单的肯定就是不用自定义,直接在 xml 中写LinearLayout
或者其他可定制性强的Layout然后在onConfigurationChanged()
中动态改变其 orientation
和内部的TabItem的宽高就行,一开始我也是这么干的。
一点优化
后来我发现 LinearLayout内部子View宽高其实都可以设置为match_parent
,但是还是要改变LinearLayout的宽高,感觉不优雅。
所以还是选择自定义View。
最后发现逃不开设置宽高的方法,因为 onMeasure 方法传入的宽高值是根据这个属性来的,唉
实现
由于TabItem已经自定义完成,这里只用处理三个步骤。
- styleable中定义所需属性。
- init方法中解析用户定义的属性并赋值。
- 重写onMeasure和onLayout方法。
阻碍
还有就是宽高的修改。本来想的是:
-
init()
中根据横竖屏状态先设置一遍。 -
onConfigurationChanged()
中再设置一遍。
但是 发现init()
解析属性发生在系统解析之前,所以我设置的width
和height
又会被用户在xml
里面定义的覆盖。暂时没找到方法。
所以放弃了,还是封装个方法从Activity
直接调用。其实,可以在onAttachedToWindow()
方法中调的,但是我感觉性能不好,先放一放。
- styleable中定义所需属性。
太简单不说了。参考上面给出的链接地址看代码。 - init方法中解析用户定义的属性并赋值。
太简单不说了。参考上面给出的链接地址看代码。 - 重写onMeasure和onLayout方法。
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// 参数初始化,存储子view的位置及大小。
layoutLeft.clear()
layoutTop.clear()
layoutRight.clear()
layoutBottom.clear()
// 当前是横屏还是竖屏
val isLandScape = if (isInEditMode) false else isLandScape(context)
// 每个tabItem的宽高,
val childWidth: Int
val childHeight: Int
// 父View给tabBar的可用宽度和可用高度,我们直接取这么大
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec))
if (isLandScape) {
// 横屏《==》宽度等于tabBar宽度,高度均分
childWidth = measuredWidth
childHeight = measuredHeight / childCount
} else {
// 竖屏《==》高度等于tabBar高度,宽度均分
childWidth = measuredWidth / childCount
childHeight = measuredHeight
}
// 测量 子View
for (i in 0 until childCount) {
val child = getChildAt(i)
// 组装宽高的MeasureSpec,宽高都是确定的
val childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY)
val childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)
// 开始测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec)
// 根据宽高设置子View位置
if (isLandScape) {
layoutLeft.add(0)
layoutTop.add(i * childHeight)
layoutRight.add(childWidth)
layoutBottom.add((i + 1) * childHeight)
} else {
// 竖屏
layoutLeft.add(i * childWidth)
layoutTop.add(0)
layoutRight.add((i + 1) * childWidth)
layoutBottom.add(childHeight)
}
}
}
onLayout就很简单
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
for (i in 0 until childCount) getChildAt(i).layout(layoutLeft[i], layoutTop[i], layoutRight[i], layoutBottom[i])
}
2020年07月16日10:07:36更新
对于宽高的修改,今天测试了几个方案,分别与从Activity中直接调用对比性能。
1. onSizeChanged()中修改宽高
这种方案都不用处理横竖屏切换,但是横竖屏切换会调用两次onSizeChanged,一次是横竖屏切换导致的宽高改变,一次是里面调用的改变宽高导致需要再次绘制又会调用一次,性能不好,生命周期如下:
// 刚进入APP,与从Activity调用生命周期一样
D/LQ: onMeasure
D/LQ: onMeasure
D/LQ: onSizeChanged
D/LQ: onLayout
D/LQ: onDraw
D/LQ: onDraw
D/LQ: onDraw
D/LQ: onDraw
D/LQ: onMeasure
D/LQ: onLayout
D/LQ: onDraw
D/LQ: onDraw
D/LQ: onDraw
// 横竖屏切换(横竖切换都一样)
D/LQ: onMeasure
D/LQ: onSizeChanged
D/LQ: onLayout
D/LQ: onDraw
D/LQ: onDraw
D/LQ: onMeasure
D/LQ: onSizeChanged
D/LQ: onLayout
D/LQ: onDraw
D/LQ: onDraw
2. onAttachToWindow()中修改宽高
这种与第一种方案刚进入的时候生命周期完全一致,但是它不会响应横竖屏切换(当然,听名字就不会响应)。
3. onAttachToWindow() 结合 onConfigurationChanged(),两者都修改宽高
刚进入:APP只会调用onAttachToWindow()
,横竖屏切换,只会调用onConfigurationChanged()
,实测完美。