中间凹陷的 BottomNavigationView

  • Post author:
  • Post category:其他


中间凹陷的 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”/>

</androidx.constraintlayout.widget.ConstraintLayout>


Menu:

<?xml version=”1.0″ encoding=”utf-8″?>

<menu xmlns:android=”http://schemas.android.com/apk/res/android”>

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=”我的” />

</menu>


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);

}

}



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



版权声明:本文为zyf994318935原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。