likes
comments
collection
share

底部导航栏开发挑战(一、Android篇)

作者站长头像
站长
· 阅读数 4

一、前言

1.1 为什么选择底部导航栏作为开发挑战

日常Android锻炼技术的开发内容多数可分为 业务向的开发UI开发功能开发。而底部导航栏可以说没太大难度,但是有时经常用到。行吧,我坦白吧,我只是想挑个软的柿子捏,找找思路,业务向,C++方向,功能向,写多了人都木了

二、先看看效果图

底部导航栏开发挑战(一、Android篇)

三、拆分细节

1、曲线的实现

这里先分享一个学习工具:Bezier Curves (desmos.com) 这个网站可以方便你拖动每条线的控制点以及起点结束点

贝塞尔曲线的运用,在日常开发中,特别是一些圆滑曲线,十分好用。

先定义几个控制点

private static final float CONTROL_x1 = 0.3f;
private static final float CONTROL_x2 = 0.4f;
private static final float CONTROL_x3 = 0.5f;
private static final float CONTROL_x4 = 0.6f;
private static final float CONTROL_x5 = 0.7f;

private static final float CONTROL_y1 = 0.10f;
private static final float CONTROL_y2 = 0.15f;
private static final float CONTROL_y3 = 0.20f;
private static final float CONTROL_y4 = 0.35f;

第一眼看上去一脸茫然,确实那我们怎么理解这些控制点呢。先看看刚刚那个学习工具 我们大概如下图:捏大概这样两条平滑的曲线出来:

底部导航栏开发挑战(一、Android篇)

底部导航栏开发挑战(一、Android篇)

然后根据自己的要求啊,审美等等把线分割成四个部分。其中两部分都是各自基于中线对称。 这里我就不还原我当时定的点,因为每个人对于线条的审美不同,深一点,凹一点,弧度大一点都不同。你们可以看我代码去修改。

mItemMiddle:凹线的中心到最左边的X正方向的距离
itemLeft:凹线的最左边的坐标

mPath.cubicTo(
        itemLeft + mItemMiddle * CONTROL_x1,
        top,
        itemLeft + mItemMiddle * CONTROL_x2,
        top + mItemHeight * CONTROL_y1,
        itemLeft + mItemMiddle * CONTROL_x3,
        top + mItemHeight * CONTROL_y2);
mPath.cubicTo(
        itemLeft + mItemMiddle * CONTROL_x4,
        top + mItemHeight * CONTROL_y3,
        itemLeft + mItemMiddle * CONTROL_x5,
        top + mItemHeight * CONTROL_y4,
        itemLeft + mItemMiddle,
        top + mItemHeight * CONTROL_y4);
        
// 此处后的代码都是基于中线镜面对称的    
mPath.cubicTo(
        itemLeft + mItemMiddle * (2 - CONTROL_x5),
        top + mItemHeight * CONTROL_y4,
        itemLeft + mItemMiddle * (2 - CONTROL_x4),
        top + mItemHeight * CONTROL_y3,
        itemLeft + mItemMiddle * (2 - CONTROL_x3),
        top + mItemHeight * CONTROL_y2);
mPath.cubicTo(itemLeft + mItemMiddle * (2 - CONTROL_x2),
        top + mItemHeight * CONTROL_y1,
        itemLeft + mItemMiddle * (2 - CONTROL_x1),
        top,
        itemRight,
        top);

四、Item内容的实现

这里Item的实现,使用RecyclerView进行处理。由于每一个View都是可见且控制不可滚动,均等宽度。所以GridLayoutManager是最合适的方案。调整好宽高和间距,即可完成,属于基础的RecyclerView的使用。

五、动画实现

利用ValueAnimator 控制itemLeft的大小。 利用ValueAnimator 控制每个Item上方的圆形的颜色变化,谨记一点,颜色变化需要传入ArgbEvaluator,否则颜色变化会异常。

private final ArgbEvaluator evaluator = new ArgbEvaluator();
colorAnimator.setEvaluator(evaluator)

六、实际使用

DemoMainActivity里可以查看到源码,方法也是很简单。但是暂时不支持从menu.xml里解析这个Item

    //图片Icon
    val drawable = ContextCompat.getDrawable(this, R.drawable.select_home)
    val drawable2 = ContextCompat.getDrawable(this, R.drawable.select_people)
    val drawable3 = ContextCompat.getDrawable(this, R.drawable.select_setting)
 
    //添加Item
    binding.bar.addItem(
        listOf(
            MaterialNavigationBar.NavigationItem(drawable, "主页")
                .setSelectedTintColor(ContextCompat.getColor(this, R.color.color_1)),
            MaterialNavigationBar.NavigationItem(drawable2, "个人")
                .setSelectedTintColor(ContextCompat.getColor(this, R.color.color_2)),
            MaterialNavigationBar.NavigationItem(drawable3, "设置")
                .setSelectedTintColor(ContextCompat.getColor(this, R.color.color_3))

        )
    )

    //ViewPage的滚动
    val colors = mutableListOf(
        ContextCompat.getColor(this, R.color.color_4),
        ContextCompat.getColor(this, R.color.color_5),
        ContextCompat.getColor(this, R.color.color_6),
    )
    val adapter = DemoAdapter(colors)
    binding.apply {
        vp.adapter = adapter
        vp.orientation = ViewPager2.ORIENTATION_HORIZONTAL
        vp.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
                super.onPageSelected(position)
                bar.smoothToPosition(position)
            }
        })

        //互相嵌套的滚动,这里加入fromUser的字段用来解决,smoothToPosition方法触发onSelected,无法区分是来自用户的点击,还是ViewPager拖动联动导致的。
        bar.addOnItemSelectedListener(object : NavigationItemSelectedAdapter() {
            override fun onSelected(position: Int, fromUser: Boolean) {
                super.onSelected(position, fromUser)
                if(fromUser){
                    vp.currentItem = position
                }
            }
        })
    }
}

七、Demo的地址

ShowMeThe/MaterialNavigationBar (github.com)