[Android] Material Design 2 BottomNavigationView use

In the morning, I recorded the use of TabLayout, which simply realized a top slidable navigation effect. I suddenly thought of another BottomNavigationView of Material Design, which can achieve the effect of the bottom navigation bar like Taobao, WeChat, QQ, and Jingdong. Let's introduce it. Use BottomNavigationView to achieve the effect of the bottom navigation bar.

Using this control also requires the addition of Material Design's dependencies: (the runtime environment is in Android Studio 3.0)

implementation 'com.android.support:design:28.0.0-alpha1'

Because the BottomNavigationView control uses the app:menu property to specify the element for the bottom navigation bar in the form of Menu, the first step is to create a new menu xml file, and create a new bottom_navigation_view.xml in the menu folder. The layout is as follows:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:id="@+id/item_home"
        android:icon="@drawable/ic_home_grey"
        android:title="首页"/>

    <item android:id="@+id/item_music"
        android:icon="@drawable/ic_music_grey"
        android:title="音乐"/>

    <item android:id="@+id/item_find"
        android:icon="@drawable/ic_find_grey"
        android:title="发现"/>
    
</menu>

BottomNavigationView is also generally used with ViewPager+Fragment, so the second step is to write the layout file.

The activity_bottom_navigation_view.xml layout is as follows:

<?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">

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">
    </android.support.v4.view.ViewPager>

    <View
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#6b6b6b"/>
    
    <android.support.design.widget.BottomNavigationView
        android:id="@+id/bottom_navigation_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        app:menu="@menu/bottom_navigation_view">

    </android.support.design.widget.BottomNavigationView>
</LinearLayout>

Create a BottonNaviFragment inherited from the Fragment, its layout file fragment_bottom_navi.xml file is as follows:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="30sp"
        android:textColor="#000000"
        android:id="@+id/tv_content"/>
</LinearLayout>

The BottonNaviFragment.java file is as follows:

@SuppressLint("ValidFragment")
public class BottonNaviFragment extends Fragment {

    private TextView textView;
    private String title;

    public BottonNaviFragment(String title) {
        this.title = title;
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view=inflater.inflate(R.layout.fragment_bottom_navi,container,false);
        textView=view.findViewById(R.id.tv_content);
        return view;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        textView.setText(title);
    }
}

Create the Fragment adapter file FragmentAdapter.java file as follows:

public class FragmentAdapter extends FragmentPagerAdapter {

    private List<Fragment> list;     //存放ViewPager中要填充的Fragment

    public FragmentAdapter(FragmentManager fm,List<Fragment> list) {
        super(fm);
        this.list=list;
    }


    @Override
    public Fragment getItem(int i) {
        return list.get(i);
    }

    @Override
    public int getCount() {
        return list.size();
    }
}

The BottomNavigationViewActivity.java file is as follows:

public class BottomNavigationViewActivity extends AppCompatActivity {

    private ViewPager viewPager;
    private BottomNavigationView bottomNavigationView;
    private MenuItem menuItem;  //菜单子项

    private List<Fragment> list; 
    private FragmentAdapter fragmentAdapter;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bottom_navigation_view);

        initView();
        initData();
    }

    private void initView() {
        viewPager=findViewById(R.id.viewpager);
        bottomNavigationView=findViewById(R.id.bottom_navigation_view);
        
        //viewPager滑动监听
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            }

            @Override
            public void onPageSelected(int position) {
                if(menuItem!=null){
                    menuItem.setChecked(false);
                }else{
                    bottomNavigationView.getMenu().getItem(0).setChecked(false);
                }
                menuItem=bottomNavigationView.getMenu().getItem(position);
                menuItem.setChecked(true);
            }

            @Override
            public void onPageScrollStateChanged(int state) {
            }
        });

        //bottmNavigationView菜单选择监听
        bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                switch(item.getItemId()){
                    case R.id.item_home:
                        viewPager.setCurrentItem(0);
                        break;
                    case R.id.item_music:
                        viewPager.setCurrentItem(1);
                        break;
                    case R.id.item_find:
                        viewPager.setCurrentItem(2);
                        break;
                }
                return false;
            }
        });
    }

    private void initData() {
        list=new ArrayList<>();
        list.add(new BottonNaviFragment("首页"));
        list.add(new BottonNaviFragment("音乐"));
        list.add(new BottonNaviFragment("发现"));

        fragmentAdapter=new FragmentAdapter(getSupportFragmentManager(),null,list);
        viewPager.setAdapter(fragmentAdapter);
    }
}

Run the next project here, the effect is as follows:

When the default element is selected, the color of the icon and text is @color/colorPrimary. If we want to change the color of the icon and text in the navigation bar when selected and unselected, we can use the two properties of the BottomNavigationView control to achieve

app:itemTextColor=""
app:itemIconTint=""

In order to facilitate the display of the effect, here we set the icon, the text is red when selected, black when unchecked, related to color selection, you need to create a new color selector bottomnavigation_select.xml file in the color folder, the content is as follows:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:color="#ff2200" android:state_checked="true"/>
    <item android:color="#000000"/>

</selector>

Then add the properties under the BottomNavigationView control in activity_bottom_navigation_view.xml:

app:itemTextColor="@color/bottomnavigation_select"
app:itemIconTint="@color/bottomnavigation_select"

Run it, the effect is as follows:

 

The above is the effect when the navigation bar has only 3 elements. The following increases the number of elements to 4, and the running effect is as follows:

As you can see from the figure, when the number of elements in the navigation bar is increased to four, the effect will be different. Only when the element is selected and swiped to the corresponding element will the text appear. When unchecked, the text is hidden. This is because the official BottomNavigationView has an enlarged ShiftingMode effect by default, but code level switching is not yet supported. It is turned off by default when it is 3 elements or less, and ShiftingMode is turned on when it is 4 or more, and there is no attribute or method to modify ShiftingMode. At this time, we can only modify it by reflection:

Create a new BottomNavigationView helper class BottomNavigationViewHelper.java, the code is as follows:

import android.support.design.internal.BottomNavigationItemView;
import android.support.design.internal.BottomNavigationMenuView;
import android.support.design.widget.BottomNavigationView;
import android.util.Log;

import java.lang.reflect.Field;

/**
 * 新建一个BottomNavigationview帮助者类,
 * 通过反射来修改ShiftingMode
 */
public class BottomNavigationViewHelper {
    public static void disableShiftMode(BottomNavigationView view) {
        BottomNavigationMenuView menuView = (BottomNavigationMenuView) view.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 item = (BottomNavigationItemView) menuView.getChildAt(i);
                //noinspection RestrictedApi
                item.setShiftingMode(false);
                // set once again checked value, so view will be updated
                //noinspection RestrictedApi
                item.setChecked(item.getItemData().isChecked());
            }
        } catch (NoSuchFieldException e) {
            Log.e("BNVHelper", "Unable to get shift mode field", e);
        } catch (IllegalAccessException e) {
            Log.e("BNVHelper", "Unable to change value of shift mode", e);
        }
    }
}

Then call BottomNavigationViewHelper's static method disableShiftMode() in BottomNavigationViewActivity.java.

bottomNavigationView=findViewById(R.id.bottom_navigation_view);
BottomNavigationViewHelper.disableShiftMode(bottomNavigationView);

When your build.gradle depends on the library 'com.android.support:appcompat-v7:X.0.0-rc02' where X is less than 28, the above code is no problem, when X is equal to 28 (I am using Android) Studio 3.0, the dependency package is implementation 'com.android.support:appcompat-v7:28.0.0-rc02'), item.setShiftingMode(false) will report Cannot resolve method 'setShiftingMode(Boolean)' error, borrowed

public class BottomNavigationViewHelper {
    @SuppressLint("RestrictedApi")
    public static void removeNavigationShiftMode(BottomNavigationView view) {
        BottomNavigationMenuView menuView = (BottomNavigationMenuView) view.getChildAt(0);
        menuView.setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_LABELED);
        menuView.buildMenuView();
    }
}

Similarly, then call BottomNavigationViewHelper's static method removeNavigationShiftMode() in BottomNavigationViewActivity.java

bottomNavigationView=findViewById(R.id.bottom_navigation_view);
BottomNavigationViewHelper.removeNavigationShiftMode(bottomNavigationView);

After some modifications, run the next project and see the effect:

At this point, the effect of the four elements is the same as the effect of the three elements.

Finally, add some points to the BottomNavigationView control:

1. If you want to set the background color of the bottom navigation bar, you can set it by the property of the BottomNavigationView app: itemBackground. The default is the background color of the current theme, white or black.

2, the official suggestion that the number of items in the navigation bar element is 3-5, up to 5, if you set 6 will report directly. Setting 2 will not give an error, but if it is 2, it is not recommended.

3, if you want to achieve the element is an icon without text, you can not set the title value of the menu, for example, do not set the "find" element text, the effect is as follows:

Ok, the use of BottomNavigationView to implement the bottom navigation bar is here.