美文网首页
兼容横竖屏的TabBar定制

兼容横竖屏的TabBar定制

作者: 小强开学前 | 来源:发表于2020-07-15 16:46 被阅读0次

    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()解析属性发生在系统解析之前,所以我设置的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(),实测完美。

    相关文章

      网友评论

          本文标题:兼容横竖屏的TabBar定制

          本文链接:https://www.haomeiwen.com/subject/izfjhktx.html