中间凹陷的 BottomNavigationView(请滑倒最底部直接复制使用)

 


简书地址:https://www.jianshu.com/p/04c44a882332

直接上代码

注:使用时一定先指定Background为透明色

        添加menu为奇数个,最中间item的icon title都为空        

 


xml:

<?xml version="1.0" encoding="utf-8"?>

        xmlns:android="http://schemas.android.com/apk/res/android"

        xmlns:app="http://schemas.android.com/apk/res-auto"

        xmlns:tools="http://schemas.android.com/tools"

        android:orientation="vertical"

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:background="#00BCD4">

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            app:layout_constraintStart_toStartOf="parent"

            app:layout_constraintEnd_toEndOf="parent"

            android:background="#00FFFFFF"

            app:menu="@menu/navigation"

            app:layout_constraintBottom_toBottomOf="parent"/>

 


Menu:

<?xml version="1.0" encoding="utf-8"?>

        android:id="@+id/navigation_home"

        android:icon="@drawable/nav_selector_home"

        android:title="首页" />

        android:id="@+id/navigation_find"

        android:icon="@drawable/nav_selector_find"

        android:title="发现" />

            android:id="@+id/navigation_null"

            android:icon="@null"

            android:title="@null"

        />

            android:id="@+id/navigation_message"

            android:icon="@drawable/nav_selector_message"

            android:title="消息" />

        android:id="@+id/navigation_mine"

        android:icon="@drawable/nav_selector_mine"

        android:title="我的" />

 


GapNavigationView类:

 

注:需先自行导入 BottomNavigationView

 

public class GapNavigationView extends BottomNavigationView {

Contextcontext;

    public GapNavigationView(Context context) {

super(context);

        this.context = context;

    }

public GapNavigationView(Context context, AttributeSet attrs) {

super(context, attrs);

    }

public GapNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

    }

@RequiresApi(api = Build.VERSION_CODES.KITKAT)

@SuppressLint("DrawAllocation")

@Override

    protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

        setLayerType(View.LAYER_TYPE_SOFTWARE, null);

        //将中间类圆区域间距设置为总高度的 3/4

        int centerRadius = getHeight() *3/4;

        //设置阴影大小

        float shadowLength =5f;

        //创建画笔

        Paint paint =new Paint();

        //画笔抗锯齿

        paint.setAntiAlias(true);

        //创建路径

        Path path =new Path();

        //开始画View

//将起点设置在阴影之下

        path.moveTo(0, shadowLength);

        //凹陷部分

        path.lineTo(getWidth() /2f - centerRadius, shadowLength);

        path.lineTo(getWidth()/2f - centerRadius/3f *2f ,shadowLength + centerRadius/4f);

        path.lineTo(getWidth()/2f - centerRadius/4f ,shadowLength + centerRadius *3/4f);

        path.lineTo(getWidth()/2f + centerRadius/4f ,shadowLength + centerRadius *3/4f);

        path.lineTo(getWidth()/2f + centerRadius/3f *2f ,shadowLength + centerRadius/4f);

        path.lineTo(getWidth()/2f + centerRadius,shadowLength);

        //封闭区域

        path.lineTo(getWidth(), shadowLength);

        path.lineTo(getWidth(), getHeight());

        path.lineTo(0, getHeight());

        path.lineTo(0, shadowLength);

        path.close();

        //设置挂角处的圆角角度

        paint.setPathEffect(new CornerPathEffect(centerRadius /4f));

        //画阴影

        paint.setStyle(Paint.Style.STROKE);

        paint.setColor(Color.GRAY);

        paint.setStrokeWidth(1);

        paint.setMaskFilter(new BlurMaskFilter(shadowLength -1, BlurMaskFilter.Blur.NORMAL));

        canvas.drawPath(path, paint);

        //填充背景

        paint.setStyle(Paint.Style.FILL);

        paint.setColor(Color.WHITE);

        paint.setStrokeWidth(1);

        paint.setMaskFilter(null);

        canvas.drawPath(path, paint);

    }

}

        没有对它进行封装,代码很少,注释很多,根据注释修改需求即可!

        到这里凹陷导航栏已经完成了,除了样子不同于 BottomNavigationView ,其余与 NavigationView 是一摸一样的 。

        但是这里有个小bug,如果开启动画观察效果,你会发现当我点击导航栏底部中间时,同样时有效的,其余按钮的缩小动画会触发,因为中部本身为null,所以我们看不见。这种体验肯定是很差的,那么我们就需要屏蔽掉中间按钮的点击事件,如何屏蔽,只有看源码了~


BottomNavigationView源码分析

首先从我们的 BottomNavigationView 类入手

 

BottomNavigationView

        发现有一些属性 比较重要的三个 :  menu   menuView     presenter  , 看名字大概是MVP模式写的吧,不过不重要~

        分析一下这几个属性,我猜真实的点击在 menuView 里面(初始化在第三个构造函数里),那我们点进 BottomNavigationMenuView 看一下

        果然发现了几个名字让我心潮澎湃的属性

        onClickListener  itemPool  buttons

        itemPool 只是一个存放了多个 BottomNavigationItemView 的池子没有实际操作意义,buttons  BottomNavigationItemView 的数组吗,onClickListener 就是View的监听器,点击事件应该就在它里面!我们去看它在哪里被赋值,进入构造函数看看

 

构造函数

    果然在这里被赋值了,点击之后的事件在这里被消费,里面有view参数可以用来判断点击的是哪个button,那我只要能改变这个 onClickListener 再里面加上判断是否为中间按钮不就大功告成了吗?

    但是问题来了,这个属性是private!google 不希望我们修改它~于是我想到了反射,利用反射打开权限,赋给 onClickListener 自定义的值不就可以了?(前面 NavigationView 里的 menuView也是私有,也需要反射再写个BottomNavigationMenuView的衍生类)

    于是我真的这么做了!但是很遗憾 ,没有成功,所以没用的代码我就不贴了。

    放弃 menuView ,我们去看看 BottomNavigationView 的 menu 属性 ,它是 MenuBuilder 类 ,我们不熟这个类是做什么的,但大概猜出是个menu相关的构造类,我们找一下 menu 在哪里被赋值,发现就在构造函数里

BottomNavigation构造函数

这名字取得太明显了吧 CallBack 都出来了 ,里面实现了 onMenuItemSelected 和 onMenuModeChange 个方法,选中时作了一个判空操作和一个是否是当前选项,不管是否通过判断都是有一个  onNavigationItemSelected(onNavigationItemReselected)操作

所以都将事件传递给了以下两个监听者返回  true 和 false 代表已处理点击和未处理点击

BottomNavigationView属性

现在我们知道这里可以处理点击事件,那只要我们在它执行判断前再判断一次是否为中间按钮,是就直接返回true不就完成了吗?

说干就干,同样的这里的 menu 是私有属性,我们可以使用反射将 menu 的callBack设置成我们刚才想要的,但是考虑到反射严重影响程序执行效率,我选择直接将 BottomNavigationView 源码 copy 下来修改。

以下是主要修改部分:

主要修改部分

其余修改部分:

1.styleble根据IDE提示导入

2.红线部分名字修改


完整GapBottomNavigationView类代码(直接复制使用)

 

@SuppressLint("RestrictedApi")

public class GapBottomNavigationViewextends FrameLayout {

private static final int MENU_PRESENTER_ID =1;

    private final MenuBuildermenu;

    private final BottomNavigationMenuViewmenuView;

    private final BottomNavigationPresenterpresenter;

    private MenuInflatermenuInflater;

    private BottomNavigationView.OnNavigationItemSelectedListenerselectedListener;

    private BottomNavigationView.OnNavigationItemReselectedListenerreselectedListener;

    public GapBottomNavigationView(Context context) {

this(context, (AttributeSet)null);

    }

public GapBottomNavigationView(Context context, AttributeSet attrs) {

this(context, attrs, attr.bottomNavigationStyle);

    }

public GapBottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

        this.presenter =new BottomNavigationPresenter();

        this.menu =new BottomNavigationMenu(context);

        this.menuView =new BottomNavigationMenuView(context);

        LayoutParams params =new LayoutParams(-2, -2);

        params.gravity =17;

        this.menuView.setLayoutParams(params);

        this.presenter.setBottomNavigationMenuView(this.menuView);

        this.presenter.setId(1);

        this.menuView.setPresenter(this.presenter);

        this.menu.addMenuPresenter(this.presenter);

        this.presenter.initForMenu(this.getContext(), this.menu);

        TintTypedArray a = ThemeEnforcement.obtainTintedStyledAttributes(context, attrs, styleable.BottomNavigationView, defStyleAttr, style.Widget_Design_BottomNavigationView, new int[]{styleable.BottomNavigationView_itemTextAppearanceInactive, styleable.BottomNavigationView_itemTextAppearanceActive});

        if (a.hasValue(styleable.BottomNavigationView_itemIconTint)) {

this.menuView.setIconTintList(a.getColorStateList(styleable.BottomNavigationView_itemIconTint));

        }else {

this.menuView.setIconTintList(this.menuView.createDefaultColorStateList(16842808));

        }

this.setItemIconSize(a.getDimensionPixelSize(styleable.BottomNavigationView_itemIconSize, this.getResources().getDimensionPixelSize(dimen.design_bottom_navigation_icon_size)));

        if (a.hasValue(styleable.BottomNavigationView_itemTextAppearanceInactive)) {

this.setItemTextAppearanceInactive(a.getResourceId(styleable.BottomNavigationView_itemTextAppearanceInactive, 0));

        }

if (a.hasValue(styleable.BottomNavigationView_itemTextAppearanceActive)) {

this.setItemTextAppearanceActive(a.getResourceId(styleable.BottomNavigationView_itemTextAppearanceActive, 0));

        }

if (a.hasValue(styleable.BottomNavigationView_itemTextColor)) {

this.setItemTextColor(a.getColorStateList(styleable.BottomNavigationView_itemTextColor));

        }

if (a.hasValue(styleable.BottomNavigationView_elevation)) {

ViewCompat.setElevation(this, (float) a.getDimensionPixelSize(styleable.BottomNavigationView_elevation, 0));

        }

this.setLabelVisibilityMode(a.getInteger(styleable.BottomNavigationView_labelVisibilityMode, -1));

        this.setItemHorizontalTranslationEnabled(a.getBoolean(styleable.BottomNavigationView_itemHorizontalTranslationEnabled, true));

        int itemBackground = a.getResourceId(styleable.BottomNavigationView_itemBackground, 0);

        this.menuView.setItemBackgroundRes(itemBackground);

        if (a.hasValue(styleable.BottomNavigationView_menu)) {

this.inflateMenu(a.getResourceId(styleable.BottomNavigationView_menu, 0));

        }

a.recycle();

        this.addView(this.menuView, params);

        if (VERSION.SDK_INT <21) {

this.addCompatibilityTopDivider(context);

        }

this.menu.setCallback(new Callback() {

public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {

//menu必须为奇数个

                if (menu.size() %2 !=0) {

//屏蔽中间按钮的点击事件

                    if ( menu.getItem(menu.size()/2).equals(item)) {

return true;

                    }

}

if (GapBottomNavigationView.this.reselectedListener !=null && item.getItemId() == GapBottomNavigationView.this.getSelectedItemId()) {

GapBottomNavigationView.this.reselectedListener.onNavigationItemReselected(item);

return true;

                }else {

return GapBottomNavigationView.this.selectedListener !=null && !GapBottomNavigationView.this.selectedListener.onNavigationItemSelected(item);

                }

}

public void onMenuModeChange(MenuBuilder menu) {

}

});

    }

public void setOnNavigationItemSelectedListener(@Nullable BottomNavigationView.OnNavigationItemSelectedListener listener) {

this.selectedListener = listener;

    }

public void setOnNavigationItemReselectedListener(@Nullable BottomNavigationView.OnNavigationItemReselectedListener listener) {

this.reselectedListener = listener;

    }

@NonNull

    public MenugetMenu() {

return this.menu;

    }

public void inflateMenu(int resId) {

this.presenter.setUpdateSuspended(true);

        this.getMenuInflater().inflate(resId, this.menu);

        this.presenter.setUpdateSuspended(false);

        this.presenter.updateMenuView(true);

    }

public int getMaxItemCount() {

return 5;

    }

@Nullable

    public ColorStateListgetItemIconTintList() {

return this.menuView.getIconTintList();

    }

public void setItemIconTintList(@Nullable ColorStateList tint) {

this.menuView.setIconTintList(tint);

    }

public void setItemIconSize(@Dimension int iconSize) {

this.menuView.setItemIconSize(iconSize);

    }

public void setItemIconSizeRes(@DimenRes int iconSizeRes) {

this.setItemIconSize(this.getResources().getDimensionPixelSize(iconSizeRes));

    }

@Dimension

    public int getItemIconSize() {

return this.menuView.getItemIconSize();

    }

@Nullable

    public ColorStateListgetItemTextColor() {

return this.menuView.getItemTextColor();

    }

public void setItemTextColor(@Nullable ColorStateList textColor) {

this.menuView.setItemTextColor(textColor);

    }

/**

    * @deprecated

    */

    @Deprecated

@DrawableRes

    public int getItemBackgroundResource() {

return this.menuView.getItemBackgroundRes();

    }

public void setItemBackgroundResource(@DrawableRes int resId) {

this.menuView.setItemBackgroundRes(resId);

    }

@Nullable

    public DrawablegetItemBackground() {

return this.menuView.getItemBackground();

    }

public void setItemBackground(@Nullable Drawable background) {

this.menuView.setItemBackground(background);

    }

@IdRes

    public int getSelectedItemId() {

return this.menuView.getSelectedItemId();

    }

public void setSelectedItemId(@IdRes int itemId) {

MenuItem item =this.menu.findItem(itemId);

        if (item !=null && !this.menu.performItemAction(item, this.presenter, 0)) {

item.setChecked(true);

        }

}

public void setLabelVisibilityMode(int labelVisibilityMode) {

if (this.menuView.getLabelVisibilityMode() != labelVisibilityMode) {

this.menuView.setLabelVisibilityMode(labelVisibilityMode);

            this.presenter.updateMenuView(false);

        }

}

public int getLabelVisibilityMode() {

return this.menuView.getLabelVisibilityMode();

    }

public void setItemTextAppearanceInactive(@StyleRes int textAppearanceRes) {

this.menuView.setItemTextAppearanceInactive(textAppearanceRes);

    }

@StyleRes

    public int getItemTextAppearanceInactive() {

return this.menuView.getItemTextAppearanceInactive();

    }

public void setItemTextAppearanceActive(@StyleRes int textAppearanceRes) {

this.menuView.setItemTextAppearanceActive(textAppearanceRes);

    }

@StyleRes

    public int getItemTextAppearanceActive() {

return this.menuView.getItemTextAppearanceActive();

    }

public void setItemHorizontalTranslationEnabled(boolean itemHorizontalTranslationEnabled) {

if (this.menuView.isItemHorizontalTranslationEnabled() != itemHorizontalTranslationEnabled) {

this.menuView.setItemHorizontalTranslationEnabled(itemHorizontalTranslationEnabled);

            this.presenter.updateMenuView(false);

        }

}

public boolean isItemHorizontalTranslationEnabled() {

return this.menuView.isItemHorizontalTranslationEnabled();

    }

private void addCompatibilityTopDivider(Context context) {

View divider =new View(context);

        divider.setBackgroundColor(ContextCompat.getColor(context, color.design_bottom_navigation_shadow_color));

        LayoutParams dividerParams =new LayoutParams(-1, this.getResources().getDimensionPixelSize(dimen.design_bottom_navigation_shadow_height));

        divider.setLayoutParams(dividerParams);

        this.addView(divider);

    }

private MenuInflatergetMenuInflater() {

if (this.menuInflater ==null) {

this.menuInflater =new SupportMenuInflater(this.getContext());

        }

return this.menuInflater;

    }

protected ParcelableonSaveInstanceState() {

Parcelable superState =super.onSaveInstanceState();

        GapBottomNavigationView.SavedState savedState =new GapBottomNavigationView.SavedState(superState);

        savedState.menuPresenterState =new Bundle();

        this.menu.savePresenterStates(savedState.menuPresenterState);

        return savedState;

    }

protected void onRestoreInstanceState(Parcelable state) {

if (!(stateinstanceof GapBottomNavigationView.SavedState)) {

super.onRestoreInstanceState(state);

        }else {

GapBottomNavigationView.SavedState savedState = (GapBottomNavigationView.SavedState) state;

            super.onRestoreInstanceState(savedState.getSuperState());

            this.menu.restorePresenterStates(savedState.menuPresenterState);

        }

}

static class SavedStateextends AbsSavedState {

BundlemenuPresenterState;

        public static final CreatorCREATOR =new ClassLoaderCreator() {

public GapBottomNavigationView.SavedStatecreateFromParcel(Parcel in, ClassLoader loader) {

return new GapBottomNavigationView.SavedState(in, loader);

            }

public GapBottomNavigationView.SavedStatecreateFromParcel(Parcel in) {

return new GapBottomNavigationView.SavedState(in, (ClassLoader)null);

            }

public GapBottomNavigationView.SavedState[]newArray(int size) {

return new GapBottomNavigationView.SavedState[size];

            }

};

        public SavedState(Parcelable superState) {

super(superState);

        }

public SavedState(Parcel source, ClassLoader loader) {

super(source, loader);

            this.readFromParcel(source, loader);

        }

public void writeToParcel(@NonNull Parcel out, int flags) {

super.writeToParcel(out, flags);

            out.writeBundle(this.menuPresenterState);

        }

private void readFromParcel(Parcel in, ClassLoader loader) {

this.menuPresenterState = in.readBundle(loader);

        }

}

public interface OnNavigationItemReselectedListener {

void onNavigationItemReselected(@NonNull MenuItem var1);

    }

public interface OnNavigationItemSelectedListener {

boolean onNavigationItemSelected(@NonNull MenuItem var1);

    }

@RequiresApi(api = Build.VERSION_CODES.KITKAT)

@SuppressLint("DrawAllocation")

@Override

    protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

        //setLayerType(View.LAYER_TYPE_SOFTWARE, null);

        int centerRadius = getHeight() *3 /4;

        float shadowLength =5f;

        Paint paint =new Paint();

        paint.setAntiAlias(true);

        Path path =new Path();

        path.moveTo(0, shadowLength);

        path.lineTo(getWidth() /2f - centerRadius, shadowLength);

        path.lineTo(getWidth() /2f - centerRadius /3f *2f, shadowLength + centerRadius /4f);

        path.lineTo(getWidth() /2f - centerRadius /4f, shadowLength + centerRadius *3 /4f);

        path.lineTo(getWidth() /2f + centerRadius /4f, shadowLength + centerRadius *3 /4f);

        path.lineTo(getWidth() /2f + centerRadius /3f *2f, shadowLength + centerRadius /4f);

        path.lineTo(getWidth() /2f + centerRadius, shadowLength);

        path.lineTo(getWidth(), shadowLength);

        path.lineTo(getWidth(), getHeight());

        path.lineTo(0, getHeight());

        path.lineTo(0, shadowLength);

        path.close();

        paint.setPathEffect(new CornerPathEffect(centerRadius /4f));

        //画阴影

        paint.setStyle(Paint.Style.STROKE);

        paint.setColor(Color.GRAY);

        paint.setStrokeWidth(1);

        //paint.setMaskFilter(new BlurMaskFilter(shadowLength - 1, BlurMaskFilter.Blur.NORMAL));

        canvas.drawPath(path, paint);

        //填充白色

        paint.setStyle(Paint.Style.FILL);

        paint.setColor(Color.WHITE);

        paint.setStrokeWidth(1);

        paint.setMaskFilter(null);

        canvas.drawPath(path, paint);

    }

}


完美收工~喜欢记得点赞哦~

更多相关文章

  1. Activity使用Dialog样式导致点击空白处自动关闭的问题
  2. Android(安卓)DEV : Custom new state for TextView
  3. 关于Android中Dialog点击屏幕外失去焦点消失的问题
  4. android手机修改系统分变率/修改机型/系统属性等信息
  5. RecyclerView实现Item点击事件方法二
  6. Android(安卓)support Repository
  7. Android模拟器的属性配置介绍
  8. 属性动画(property animation) &重复执行
  9. Android布局_LinearLayout布局

随机推荐

  1. ANDROID SHAPE鐢诲渾褰㈣儗鏅痏ANDROID瀹
  2. Android(安卓)自定义View--ProgressBar篇
  3. android 的四个层次开发
  4. Android 系列 2.8在Android应用程序中使
  5. Android Studio 4.0.0的介绍
  6. android APP自动增量更新
  7. 【Android】动态链接库so的加载原理
  8. 新书出版:《Android深度探索(卷1):HAL与驱动
  9. Android启动(1)启动过程概述
  10. 《Android经验分享》周刊第3期