项目中底部导航栏有UI定制需求,效果如下
在此记录一下实现方案
1.首先用组合控件的方式把图中图标按位置摆放好
xml文件如下
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="71dp" android:clipChildren="false" android:paddingBottom="10dp" android:background="#00000000" android:gravity="bottom" xmlns:app="http://schemas.android.com/apk/res-auto"> <ImageView android:id="@+id/first" android:layout_width="22dp" android:layout_height="22dp" android:layout_weight="1" android:src="@drawable/tab_home_selected2x"/> <ImageView android:id="@+id/second" android:layout_width="22dp" android:layout_height="22dp" android:layout_weight="1" android:src="@drawable/tab_find_unselected2x"/> <ImageView android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/plus" android:layout_marginBottom="9dp" android:id="@+id/centerIcon"/> <ImageView android:id="@+id/third" android:layout_width="22dp" android:layout_height="22dp" android:layout_weight="1" android:src="@drawable/tab_message_unselected2x" /> <ImageView android:id="@+id/forth" android:layout_width="22dp" android:layout_height="22dp" android:layout_weight="1" android:src="@drawable/tab_my_unselected2x"/> </LinearLayout>
Android studio内渲染效果
此处记得把最外层的LinearLayout背景设置为透明:android:background="#00000000"
2.自定义组合控件BottomNavigationBar继承自LinearLayout,代码如下:
public class BottomNavigationBar extends LinearLayout implements View.OnClickListener { private Paint paint; private Path path; private float width; private int currentPosition = 0; private onBottomNavClickListener listener; // private String[] tabText = {"打卡", "发现", "消息", "我的"}; //未选中icon private int[] normalIcon = {R.drawable.tab_home_unselected2x, R.drawable.tab_find_unselected2x, R.drawable.tab_message_unselected2x, R.drawable.tab_my_unselected2x}; //选中时icon private int[] selectIcon = {R.drawable.tab_home_selected2x, R.drawable.tab_find_selected2x, R.drawable.tab_message_selected2x, R.drawable.tab_my_selected2x}; private ImageView img1, img2, imgCenter, img3, img4; private ViewPager viewPager; public BottomNavigationBar(Context context) { super(context); init(context); } public BottomNavigationBar(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context); } private void init(Context context) { paint = new Paint(Paint.ANTI_ALIAS_FLAG); path = new Path(); paint.setStyle(Paint.Style.FILL_AND_STROKE); paint.setColor(Color.WHITE); View view = LayoutInflater.from(context).inflate(R.layout.bottom_navigator, this); img1 = view.findViewById(R.id.first); img2 = view.findViewById(R.id.second); imgCenter = view.findViewById(R.id.centerIcon); img3 = view.findViewById(R.id.third); img4 = view.findViewById(R.id.forth); setWillNotDraw(false); //2、通过Resources获取 DisplayMetrics dm = getResources().getDisplayMetrics(); width = dm.widthPixels; img1.setOnClickListener(this::onClick); img2.setOnClickListener(this::onClick); img3.setOnClickListener(this::onClick); img4.setOnClickListener(this::onClick); imgCenter.setOnClickListener(this::onClick); } @Override protected void onDraw(Canvas canvas) { paint.setColor(getResources().getColor(R.color.White)); paint.setShadowLayer(30,0,20,Color.BLACK); path.moveTo(0, dip2px(28)); path.lineTo(dip2px(150), dip2px(28)); path.quadTo(width / 2 - dip2px(30), dip2px(28), width / 2 - dip2px(25), dip2px(18)); path.quadTo(width / 2, -45, width / 2 + dip2px(25), dip2px(18)); path.quadTo(width / 2 + dip2px(30), dip2px(28), width - dip2px(150), dip2px(28)); path.lineTo(width, dip2px(28)); path.lineTo(width, dip2px(71)); path.lineTo(0, dip2px(71)); path.close(); canvas.drawPath(path, paint); super.onDraw(canvas); } /** * 根据屏幕的分辨率从 dp 的单位 转成为 px(像素) */ private int dip2px(float dpValue) { final float scale = getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } public void setUpWithViewPager(ViewPager viewPager) { this.viewPager = viewPager; } @Override public void onClick(View v) { switch (v.getId()) { case R.id.first: if (currentPosition == 0) break; setUnSelect(currentPosition); currentPosition = 0; viewPager.setCurrentItem(currentPosition,true); img1.setImageResource(selectIcon[currentPosition]); break; case R.id.second: if (currentPosition == 1) break; setUnSelect(currentPosition); currentPosition = 1; viewPager.setCurrentItem(currentPosition,true); img2.setImageResource(selectIcon[currentPosition]); break; case R.id.third: if (currentPosition == 2) break; setUnSelect(currentPosition); currentPosition = 2; viewPager.setCurrentItem(currentPosition,true); img3.setImageResource(selectIcon[currentPosition]); break; case R.id.forth: if (currentPosition == 3) break; setUnSelect(currentPosition); currentPosition = 3; viewPager.setCurrentItem(currentPosition,true); img4.setImageResource(selectIcon[currentPosition]); break; case R.id.centerIcon: if (listener != null) listener.onCenterIconClick(); break; } } private void setUnSelect(int position) { switch (position) { case 0: img1.setImageResource(normalIcon[0]); break; case 1: img2.setImageResource(normalIcon[1]); break; case 2: img3.setImageResource(normalIcon[2]); break; case 3: img4.setImageResource(normalIcon[3]); break; } } public interface onBottomNavClickListener { void onCenterIconClick(); } public void setOnListener(onBottomNavClickListener listener){ this.listener = listener; } }
其中只需关注和UI有关的方法(其余方法用于配合ViewPager),也就是init方法、dip2px方法和onDraw方法
init初始化paint、ptah,并获取屏幕宽度,为onDraw方法画贝塞尔曲线做准备
onDraw方法绘制按照xml中的尺寸绘制path,由于xml使用的是dp,而实际绘制时需要以px(像素)为单位,所以需要通过dip2px
进行转换,注意paint.setShadowLayer(30,0,20,Color.BLACK);用于设置阴影,否则颜色相近的情况下边界不明显
path.quadTo()方法用于绘制贝塞尔曲线,其中的坐标参数是我根据UI给的效果图手动计算滴
不了解path.quadTo()的同学可以戳这个传送门
还有一件事,由于viewgroup默认不触发onDraw方法,需要加一句:setWillNotDraw(false);(我的代码中在init方法里面)
最终效果:
拿下~
最最最最后一句,使用的时候不能直接把上方的布局放在BottomNavigationBar之上,因为BottomNavigationBar的高度是按最高的地方算的,直接放上去会出现突起的地方左右侧是空白,建议使用相对布局,然后上方的控件使用marginBottom来卡距离~
有帮助的话记得点个赞~