Android底部导航栏突出(贝塞尔曲线)

项目中底部导航栏有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来卡距离~

 

有帮助的话记得点个赞~

posted @ 2021-04-13 11:37  坏蛋不是蛋  阅读(2639)  评论(0编辑  收藏  举报