底部导航栏开发挑战(一、Android篇)
一、前言
1.1 为什么选择底部导航栏作为开发挑战
日常Android锻炼技术的开发内容多数可分为 业务向的开发,UI开发,功能开发。而底部导航栏可以说没太大难度,但是有时经常用到。行吧,我坦白吧,我只是想挑个软的柿子捏,找找思路,业务向,C++方向,功能向,写多了人都木了
二、先看看效果图
三、拆分细节
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;
第一眼看上去一脸茫然,确实那我们怎么理解这些控制点呢。先看看刚刚那个学习工具 我们大概如下图:捏大概这样两条平滑的曲线出来:
然后根据自己的要求啊,审美等等把线分割成四个部分。其中两部分都是各自基于中线对称。 这里我就不还原我当时定的点,因为每个人对于线条的审美不同,深一点,凹一点,弧度大一点都不同。你们可以看我代码去修改。
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)
六、实际使用
在Demo里MainActivity里可以查看到源码,方法也是很简单。但是暂时不支持从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的地址
转载自:https://juejin.cn/post/7043694392665702437