項目中底部導航欄有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來卡距離~
有幫助的話記得點個贊~