导入依赖库
implementation 'com.android.support:design:27.1.1'
编辑布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".BottomNavigationViewActivity">
<FrameLayout
android:id="@+id/fragment_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</FrameLayout>
<android.support.design.widget.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemIconTint="@color/tab_text_color_checked_selector"
app:itemBackground="@android:color/transparent"
app:itemTextColor="@color/tab_text_color_checked_selector"
app:menu="@menu/tabs_menu">
</android.support.design.widget.BottomNavigationView>
</LinearLayout>
属性说明:
- itemIconTint:设置tab图片的颜色,像控制文字颜色一样使用selector来控制
- itemBackground:设置tab的背景,可以用来消除点击时的涟漪效果
- itemTextColor:设置tab文字的颜色
- menu:设置tab菜单
创建tabs_menu
在项目的res文件下创建menu文件夹,再创建tabs_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/tab_home"
android:icon="@drawable/ic_home_common"
android:title="首页" />
<item
android:id="@+id/tab_classification"
android:icon="@drawable/ic_classification_common"
android:title="分类" />
<item
android:id="@+id/tab_case"
android:icon="@drawable/ic_case_common"
android:title="案例" />
<item
android:id="@+id/tab_setting"
android:icon="@drawable/ic_setting_common"
android:title="设置" />
</menu>
activity代码
package com.matrix.navigation;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.internal.BottomNavigationItemView;
import android.support.design.internal.BottomNavigationMenuView;
import android.support.design.widget.BottomNavigationView;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import com.matrix.navigation.fragment.CaseFragment;
import com.matrix.navigation.fragment.ClassificationFragment;
import com.matrix.navigation.fragment.HomeFragment;
import com.matrix.navigation.fragment.SettingFragment;
import java.lang.reflect.Field;
public class BottomNavigationViewActivity extends AppCompatActivity {
private Fragment mHomeFragment = new HomeFragment();
private Fragment mClassificationFragment = new ClassificationFragment();
private Fragment mCaseFragment = new CaseFragment();
private Fragment mSettingFragment = new SettingFragment();
private Fragment[] mFragments = new Fragment[]{mHomeFragment, mClassificationFragment,
mCaseFragment, mSettingFragment};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bottom_navigation_view);
BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_navigation);
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
navigationItemSelected(item.getItemId());
return true;
}
});
// 手动设置第一个展示的fragment
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_content, mHomeFragment).commit();
}
private void navigationItemSelected(int itemId) {
Fragment fragment = null;
switch (itemId) {
case R.id.tab_home:
fragment = mFragments[0];
break;
case R.id.tab_classification:
fragment = mFragments[1];
break;
case R.id.tab_case:
fragment = mFragments[2];
break;
case R.id.tab_setting:
fragment = mFragments[3];
break;
}
if (fragment != null) {
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_content, fragment).commit();
}
}
}
来跑一下,看下效果
一脸懵,这是啥效果,完全不是我们想要的?为什么会这样呢?看源码!!!(ps:主要需要关注三个类BottomNavigationView.java、BottomNavigationMenuView.java、BottomNavigationItemView.java)在源码的BottomNavigationMenuView.java中有这么一段代码
public void buildMenuView() {
removeAllViews();
if (mButtons != null) {
for (BottomNavigationItemView item : mButtons) {
mItemPool.release(item);
}
}
if (mMenu.size() == 0) {
mSelectedItemId = 0;
mSelectedItemPosition = 0;
mButtons = null;
return;
}
mButtons = new BottomNavigationItemView[mMenu.size()];
mShiftingMode = mMenu.size() > 3;
for (int i = 0; i < mMenu.size(); i++) {
mPresenter.setUpdateSuspended(true);
mMenu.getItem(i).setCheckable(true);
mPresenter.setUpdateSuspended(false);
BottomNavigationItemView child = getNewItem();
mButtons[i] = child;
child.setIconTintList(mItemIconTint);
child.setTextColor(mItemTextColor);
child.setItemBackground(mItemBackgroundRes);
child.setShiftingMode(mShiftingMode);
child.initialize((MenuItemImpl) mMenu.getItem(i), 0);
child.setItemPosition(i);
child.setOnClickListener(mOnClickListener);
addView(child);
}
mSelectedItemPosition = Math.min(mMenu.size() - 1, mSelectedItemPosition);
mMenu.getItem(mSelectedItemPosition).setChecked(true);
}
看到没,这种效果叫做ShiftingMode,当tab超过3个的话,ShiftingMode就会被设置为true,我们先将tab个数控制三个以内看看效果,删掉tab_menu.xml中的一个item。
好像达到了我们需要的效果,但是被选中的文字变大了(这个问题稍候在处理)。先处理如何在tab个数大于3个时也是这样的效果呢?很简单,我们只要控制住shiftingMode为false就行了,那怎么做到呢?通过反射机制来处理。具体代码如下:
@SuppressLint("RestrictedApi")
public void disableShiftMode(BottomNavigationView navigationView) {
BottomNavigationMenuView menuView = (BottomNavigationMenuView) navigationView.getChildAt(0);
try {
Field shiftingMode = menuView.getClass().getDeclaredField("mShiftingMode");
shiftingMode.setAccessible(true);
shiftingMode.setBoolean(menuView, false);
shiftingMode.setAccessible(false);
for (int i = 0; i < menuView.getChildCount(); i++) {
BottomNavigationItemView itemView = (BottomNavigationItemView) menuView.getChildAt(i);
itemView.setShiftingMode(false);
itemView.setChecked(itemView.getItemData().isChecked());
}
} catch (Exception e) {
e.printStackTrace();
}
}
然后在activity中调用该方法即可,disableShiftMode(bottomNavigationView)。看下效果
的确如此,达到了我们想要的效果。接下来我们处理点击后文字变大的文字,在源码BottomNavigationItemView.java类中有这样一段代码
public BottomNavigationItemView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final Resources res = getResources();
int inactiveLabelSize =
res.getDimensionPixelSize(R.dimen.design_bottom_navigation_text_size);
int activeLabelSize = res.getDimensionPixelSize(
R.dimen.design_bottom_navigation_active_text_size);
mDefaultMargin = res.getDimensionPixelSize(R.dimen.design_bottom_navigation_margin);
mShiftAmount = inactiveLabelSize - activeLabelSize;
mScaleUpFactor = 1f * activeLabelSize / inactiveLabelSize;
mScaleDownFactor = 1f * inactiveLabelSize / activeLabelSize;
LayoutInflater.from(context).inflate(R.layout.design_bottom_navigation_item, this, true);
setBackgroundResource(R.drawable.design_bottom_navigation_item_background);
mIcon = findViewById(R.id.icon);
mSmallLabel = findViewById(R.id.smallLabel);
mLargeLabel = findViewById(R.id.largeLabel);
}
关注其中三个值:
- design_bottom_navigation_text_size:未选中时文字大小,默认12sp
- design_bottom_navigation_active_text_size:选中时文字大小,默认14sp
- design_bottom_navigation_margin:图片与文字的间距,默认8dp
所以我们可以在自己的dimens文件中将design_bottom_navigation_text_size和design_bottom_navigation_active_text_size设置同样大小即可。
在这里贴出源码中tab item所使用到的布局文件design_bottom_navigation_item.xml。
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/design_bottom_navigation_margin"
android:layout_marginBottom="@dimen/design_bottom_navigation_margin"
android:duplicateParentState="true" />
<android.support.design.internal.BaselineLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:clipToPadding="false"
android:paddingBottom="10dp"
android:duplicateParentState="true">
<TextView
android:id="@+id/smallLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/design_bottom_navigation_text_size"
android:singleLine="true"
android:duplicateParentState="true" />
<TextView
android:id="@+id/largeLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
android:textSize="@dimen/design_bottom_navigation_active_text_size"
android:singleLine="true"
android:duplicateParentState="true" />
</android.support.design.internal.BaselineLayout>
</merge>
在源码BottomNavigationMenuView.java中还有另外一个参数值需要注意,design_bottom_navigation_height用来设置默认的底部导航栏的高度,默认值为56dp。