兼容横竖屏的TabBar定制

2020-07-15  本文已影响0人  小强开学前

TabBar竖屏没有争议,iOS 和 Android 都是在底部,要说有什么不同,那也是类似Pinterest那样的底部悬浮式。横屏的话,iOS基本都是侧边按钮,Android 谷歌推荐是变成汉堡包然后通过侧滑出抽屉切换页面,帅是帅,但是现在全面屏手势出来抽屉这种交互立马被打入冷宫, YouTube网页版那种我觉得也不错。

实现效果
具体代码可参考我的Github
所以我想做一个竖屏是横向一字排开,横屏是纵向一字排开的TabBar。

父布局的选择

最简单的肯定就是不用自定义,直接在 xml 中写LinearLayout或者其他可定制性强的Layout然后在onConfigurationChanged()中动态改变其 orientation和内部的TabItem的宽高就行,一开始我也是这么干的。

一点优化

后来我发现 LinearLayout内部子View宽高其实都可以设置为match_parent,但是还是要改变LinearLayout的宽高,感觉不优雅。
所以还是选择自定义View。

最后发现逃不开设置宽高的方法,因为 onMeasure 方法传入的宽高值是根据这个属性来的,唉

实现

由于TabItem已经自定义完成,这里只用处理三个步骤。

阻碍

还有就是宽高的修改。本来想的是:

但是 发现init()解析属性发生在系统解析之前,所以我设置的widthheight又会被用户在xml里面定义的覆盖。暂时没找到方法。

所以放弃了,还是封装个方法从Activity直接调用。其实,可以在onAttachedToWindow()方法中调的,但是我感觉性能不好,先放一放。

  1. styleable中定义所需属性。
    太简单不说了。参考上面给出的链接地址看代码。
  2. init方法中解析用户定义的属性并赋值。
    太简单不说了。参考上面给出的链接地址看代码。
  3. 重写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(),实测完美。

上一篇下一篇

猜你喜欢

热点阅读