Material Design系列之BottomNavigationView詳解
Material Design官方文檔Bottom navigation的介紹
簡介
BottomNavigationView實現(xiàn)的效果就是常見的app底部導航欄的效果。
Bottom navigation bars make it easy for users to explore and switch between top-level views in a single tap. It should be used when application has three to five top-level destinations.
適用于3到5個tab的情況下懒叛。
使用
compile 'com.android.support:design:26.0.0-alpha1'
<android.support.design.widget.BottomNavigationView
android:id="@+id/bottomnavigationview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemIconTint="@drawable/selector_tab_color"
app:itemTextColor="@drawable/selector_tab_color"
app:menu="@menu/bottom_navigation_tab">
</android.support.design.widget.BottomNavigationView>
屬性:
itemIconTint:icon圖片的顏色
itemTextColor:文本的顏色
menu:tab的布局
selector_tab_color:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:color="@color/colorAccent"/>
<item android:state_checked="false" android:color="@color/colorPrimary"/>
</selector>
一般來說圖片的顏色是和文字的顏色是一致的腺阳。
菜單的布局:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/tab_one"
android:icon="@drawable/icon_one_selected"
android:title="首頁"/>
<item
android:id="@+id/tab_two"
android:icon="@drawable/icon_two_selected"
android:title="消息"/>
<item
android:id="@+id/tab_three"
android:icon="@drawable/icon_three_selected"
android:title="訂單"/>
<item
android:id="@+id/tab_four"
android:icon="@drawable/icon_four_selected"
android:title="我的"/>
</menu>
這種情況只適用于圖片是純色且選中和未選中時的圖片是一樣的。如果是不同的圖片需要新建一個selector文件答姥,
設置選中時的圖片和未選中時的圖片什燕,并且不設置itemIconTint屬性吏祸。
完整的activity代碼:
public class BottomNavigationViewActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener, ViewPager.OnPageChangeListener {
ViewPager viewPager;
BottomNavigationView bottomNavigationView;
private MenuItem menuItem;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bottom_navigation_view);
viewPager = (ViewPager) findViewById(R.id.viewpager);
bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottomnavigationview);
disableShiftMode(bottomNavigationView);
bottomNavigationView.setOnNavigationItemSelectedListener(this);
viewPager.addOnPageChangeListener(this);
bottomNavigationView.setSelectedItemId(R.id.tab_two);
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager()));
}
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
int itemId = item.getItemId();
switch (itemId){
case R.id.tab_one:
viewPager.setCurrentItem(0);
break;
case R.id.tab_two:
viewPager.setCurrentItem(1);
break;
case R.id.tab_three:
viewPager.setCurrentItem(2);
break;
case R.id.tab_four:
viewPager.setCurrentItem(3);
break;
}
return false;
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
menuItem = bottomNavigationView.getMenu().getItem(position);
menuItem.setChecked(true);
}
@Override
public void onPageScrollStateChanged(int state) {
}
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();
}
}
class ViewPagerAdapter extends FragmentPagerAdapter {
private Fragment[] mFragments = new Fragment[]{new OneFragment(), new TwoFragment(), new ThreeFragment(),new FourFragment()};
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
return mFragments[position];
}
@Override
public int getCount() {
return 4;
}
}
}
注意
1捺宗、BottomNavigationView只適用于3到5個的導航欄柱蟀;
2、當tab個數(shù)大余3個時蚜厉,BottomNavigationView不會均分寬度长已,一般來說我們都是需要均分寬度。
解決方案:disableShiftMode(bottomNavigationView);
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();
}
}
來看下關于mShiftingMode這個變量的源碼昼牛,在BottomNavigationMenuView中:
mShiftingMode = mMenu.size() > 3;//當大于3時為true
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);
}
在執(zhí)行onMeasure方法時:
if (mShiftingMode) {
final int inactiveCount = count - 1;
final int activeMaxAvailable = width - inactiveCount * mInactiveItemMinWidth;
final int activeWidth = Math.min(activeMaxAvailable, mActiveItemMaxWidth);
final int inactiveMaxAvailable = (width - activeWidth) / inactiveCount;
final int inactiveWidth = Math.min(inactiveMaxAvailable, mInactiveItemMaxWidth);
int extra = width - activeWidth - inactiveWidth * inactiveCount;
for (int i = 0; i < count; i++) {
mTempChildWidths[i] = (i == mSelectedItemPosition) ? activeWidth : inactiveWidth;
if (extra > 0) {
mTempChildWidths[i]++;
extra--;
}
}
} else {
final int maxAvailable = width / (count == 0 ? 1 : count);
final int childWidth = Math.min(maxAvailable, mActiveItemMaxWidth);
int extra = width - childWidth * count;
for (int i = 0; i < count; i++) {
mTempChildWidths[i] = childWidth;
if (extra > 0) {
mTempChildWidths[i]++;
extra--;
}
}
}
下圖是mShiftingMode為true的情況下debug拿到的數(shù)據(jù)术瓮,再結(jié)合效果圖,即可分析出:
inactiveCount為閑置的個數(shù)匾嘱,即沒有被選中的menuItem的個數(shù)斤斧,選中的寬度activeWidth和未選中的寬度inactiveWidth不一致早抠。
當mShiftingMode為false執(zhí)行的代碼很容易看出寬度是均分計算的霎烙。
其他
源碼里面的各個屬性的設置:
<dimen name="design_bottom_navigation_active_item_max_width">168dp</dimen>//選中時的最大寬度
<dimen name="design_bottom_navigation_active_text_size">14sp</dimen>//選中時的字體大小
<dimen name="design_bottom_navigation_elevation">8dp</dimen>//陰影的大小
<dimen name="design_bottom_navigation_height">56dp</dimen>//高度
<dimen name="design_bottom_navigation_item_max_width">96dp</dimen>//未選中的最大寬度
<dimen name="design_bottom_navigation_item_min_width">56dp</dimen>//未選中的最小的寬度
<dimen name="design_bottom_navigation_margin">8dp</dimen>//icon與文本之間的間距
<dimen name="design_bottom_navigation_shadow_height">1dp</dimen>//陰影高度
<dimen name="design_bottom_navigation_text_size">12sp</dimen>//未選中時的字體大小
如果要修改這些屬性值,在自己項目的dimens定義相同的名字蕊连,重新賦值