1. 程式人生 > >【Android】Material Design 之二 BottomNavigationView使用

【Android】Material Design 之二 BottomNavigationView使用

上午記錄了TabLayout的使用,簡單實現了一個頂部可滑動的導航效果,突然想到Material Design的另一個控制元件BottomNavigationView,可以實現類似淘寶、微信、QQ、京東的底部導航欄的效果,下面就來介紹一下使用BottomNavigationView來實現底部導航欄的效果。

使用該控制元件同樣需要新增Material Design的依賴:(執行環境是在Android Studio 3.0)

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

因為BottomNavigationView控制元件是通過app:menu屬性,使用Menu的形式為底部導航欄指定元素的,所以第一步就要新建一個選單xml檔案,在menu資料夾下新建bottom_navigation_view.xml,佈局內容如下:

<?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一般也是和ViewPager+Fragment搭配使用,所以第二步就寫下佈局檔案,

activity_bottom_navigation_view.xml佈局內容如下:

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

 建立BottonNaviFragment繼承自Fragment,其佈局檔案fragment_bottom_navi.xml檔案如下:

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

BottonNaviFragment.java檔案如下: 

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

 建立Fragment介面卡檔案FragmentAdapter.java檔案如下:

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

 BottomNavigationViewActivity.java檔案如下:

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

到此執行下專案,效果如圖:

 預設元素選中時圖示、文字的顏色為@color/colorPrimary,如果我們想改變導航欄中圖示、文字在選中和未選中時的顏色,可以通過BottomNavigationView控制元件的兩個屬性去實現,分別是

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

 為了方便效果展示,在這裡我們設定圖示、文字在選中時顏色為紅色,未選中時為黑色,涉及到顏色選擇,需要在color資料夾下新建一個顏色選擇器bottomnavigation_select.xml檔案,內容如下:

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

 然後在activity_bottom_navigation_view.xml中的BottomNavigationView控制元件下,增加屬性:

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

執行一下,效果如圖:

 

 以上是導航欄只有3個元素時效果,下面將元素增加到4個,執行效果如圖:

從圖中可以發現,當導航欄中元素增加到4個時,效果就不一樣了,只有當元素選中以及滑動到對應元素時,文字才會出現,未選中時,文字是隱藏的。 這是因為官方的BottomNavigationView預設有個放大的ShiftingMode效果,但是尚未支援程式碼層級的切換。在3個元素及以下時是預設關閉的,而到了4個及以上時就會開啟ShiftingMode效果,並且沒有任何屬性和方法去修改ShiftingMode,此時我們只能通過反射來修改:

新建一個BottomNavigationView的幫助者類BottomNavigationViewHelper.java,程式碼如下:

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

 然後在 BottomNavigationViewActivity.java中呼叫BottomNavigationViewHelper的靜態方法disableShiftMode()即可。

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

當你的build.gradle中 依賴庫 'com.android.support:appcompat-v7:X.0.0-rc02' 中X小於28時,以上程式碼是沒有問題的,當X等於28時(我使用的是Android Studio 3.0,依賴包是implementation 'com.android.support:appcompat-v7:28.0.0-rc02'),item.setShiftingMode(false)就會報Cannot resolve method 'setShiftingMode(Boolean)'的錯誤,借鑑了

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

 同樣的,然後在 BottomNavigationViewActivity.java中呼叫BottomNavigationViewHelper的靜態方法removeNavigationShiftMode()

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

 一番修改之後,再執行下專案,看下效果:

 此時,4個元素的效果和3個元素的效果就一樣啦。

最後關於BottomNavigationView控制元件作點補充:

1、如果要設定底部導航欄的背景顏色,可以通過BottomNavigationView的屬性app:itemBackground來設定,預設是當前主題的背景色,白色or黑色。

2、官方建議導航欄元素item個數為3-5個 ,最多5個,如果設定6個會直接報錯。設定2個則不會報錯,但是如果是2個的話不建議使用該控制元件。

3、如果想實現元素是不帶文字的圖示,可以不設定選單的title值,例如,不設定“發現”的元素文字,執行後效果如下方:

好啦,關於BottomNavigationView實現底部導航欄的使用就介紹到這裡啦。