前言
如果你之前沒(méi)接觸過(guò)的話搬泥,建議先下載demo,對(duì)比本文進(jìn)行學(xué)習(xí)小腊;
如果你向我一樣,總是忘記久窟,那就動(dòng)動(dòng)小手收藏一下吧~~~
demo: https://github.com/liuleshuai/NavigationMenuBar
干貨
現(xiàn)在的App底部一般都有一個(gè)可切換的菜單秩冈,通常使用有以下5種方式實(shí)現(xiàn):
- TabHost(過(guò)時(shí))
- Radiobutton(自定義)
- FragmentTabHost
- TabLayout(5.0新增)
- BottomNavigationView(5.0新增)
1.TabHost
它添加的是Activity而不像上面那些全部使用Fragment來(lái)顯示內(nèi)容。
過(guò)時(shí)斥扛,不推薦使用漩仙,略...
2.Radiobutton
原理就是通過(guò)設(shè)置button的樣式來(lái)模擬菜單操作,可以參考這篇文章:
https://blog.csdn.net/duolaimila/article/details/51315623
不推薦犹赖,略...
3.FragmentTabHost
依賴
- 添加相關(guān)的依賴
com.android.support:support-v4:xx.x.x
布局
- 底部菜單欄的布局一般如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/realcontent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</FrameLayout>
<!--<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />-->
<android.support.v4.app.FragmentTabHost
android:id="@android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ffffff" >
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0" >
</FrameLayout>
</android.support.v4.app.FragmentTabHost></LinearLayout>
- FragmentTabHost的id是需要使用安卓自帶的id队他,必須設(shè)置為
@android:id/tabhost
。- FragmentTabHost中的FrameLayout也需要使用安卓自帶的id峻村,
@android:id/tabcontent
麸折。
另外,需要添加一個(gè)菜單Item的布局粘昨。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tab_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:gravity="center"
android:textColor="#ffffff"
android:textSize="12sp" />
</LinearLayout>
代碼
- 綁定
mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
//綁定Fragment
mTabHost.setup(this, getSupportFragmentManager(), R.id.realcontent);
//綁定viewpager
// mTabHost.setup(this, getSupportFragmentManager(), R.id.pager);
- 設(shè)置菜單選項(xiàng)卡
for (int i = 0; i < count; i++) {
// 給每個(gè)Tab按鈕設(shè)置標(biāo)簽垢啼、圖標(biāo)和文字
TabHost.TabSpec tabSpec = mTabHost.newTabSpec(textViewArray[i])
.setIndicator(getItemView(i));
// 將Tab按鈕添加進(jìn)Tab選項(xiàng)卡中,并綁定Fragment
mTabHost.addTab(tabSpec, fragmentArray[i], null);
//設(shè)置Tab被選中的時(shí)候顏色改變
mTabHost.getTabWidget().getChildAt(i)
.setBackgroundResource(R.drawable.selector_tab_background);
}
注意:
上述代碼中的.newTabSpec(textViewArray[i])
是為菜單設(shè)置String類型的Tag標(biāo)識(shí)张肾,不一定要設(shè)置為菜單標(biāo)題芭析,但是一定不能重復(fù),推薦設(shè)置成菜單的名稱吞瞪,肯定沒(méi)人會(huì)設(shè)置兩個(gè)一樣的菜單吧 :)
private View getItemView(int i) {
View view = LayoutInflater.from(this).inflate(R.layout.fragment_tab_host_item, null, false);
TextView tv = view.findViewById(R.id.tab_textview);
tv.setText(textViewArray[i]);
Drawable drawable = ContextCompat.getDrawable(this,R.mipmap.ic_launcher);
drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight());
tv.setCompoundDrawables(null, drawable, null, null);
return view;
}
- 其它設(shè)置
//去掉分割線 null 或 LinearLayout.SHOW_DIVIDER_NONE
mTabHost.getTabWidget().setDividerDrawable(null);
//設(shè)置默認(rèn)第一個(gè)
mTabhost.setCurrentTab(0);
如果綁定的是ViewPager馁启,還需要對(duì)頁(yè)面切換進(jìn)行處理
/*為了當(dāng)點(diǎn)擊下面菜單時(shí),上面的ViewPager能滑動(dòng)到對(duì)應(yīng)的Fragment*/
mTabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
@Override
public void onTabChanged(String tabId) {
int position = mTabHost.getCurrentTab();
vp.setCurrentItem(position);
}
});
//設(shè)置頁(yè)面切換時(shí)的監(jiān)聽器
vp.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
mTabHost.setCurrentTab(arg0);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
不用擔(dān)心上述二者的監(jiān)聽造成循環(huán)調(diào)用,VP和FragmentTabHost內(nèi)部已經(jīng)為我們做好了處理芍秆,即如果目標(biāo)位置和當(dāng)前位置相同惯疙,不會(huì)觸發(fā)
OnPageChangeListener
和OnTabChangeListener
回調(diào)。
源碼如下:
4.TabLayout
依賴
compile 'com.android.support:support-v4:xx.x.x'
compile 'com.android.support:design:xx.x.x'
布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
tools:context=".MainActivity">
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="100p"
//導(dǎo)航欄背景顏色
android:background="#ffff00"
//指示器顏色
app:tabIndicatorColor="#66ff33"
//指示器高度
app:tabIndicatorHeight="20p"
//普通狀態(tài)下文字的顏色
app:tabTextColor="@color/colorPrimary"
//選中時(shí)文字的顏色
app:tabSelectedTextColor="#CC33FF"
//是否可滑動(dòng):fixed:固定妖啥;scrollable:可滑動(dòng)
app:tabMode="fixed"
//居中模式:fill:填充; center:居中
app:tabGravity="fill"
//設(shè)置選項(xiàng)卡的背景
app:tabBackground="@drawable/selected"
//設(shè)置字體大忻沟摺:此處要寫一個(gè)style)
app:tabTextAppearance="@style/MyTabLayoutTextAppearance"/>
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>
注意:
上述TabLayout
布局中,android:layout_height="100p"
這里需要注意荆虱,不能設(shè)置成wrap_content
蒿偎,必須設(shè)置具體數(shù)值,否則會(huì)出現(xiàn)無(wú)法顯示菜單標(biāo)題的問(wèn)題怀读。切記K呶弧!愿吹!切記2淮印!犁跪!切記4幌ⅰ4踉!
style.xml
<style name="MyTabLayoutTextAppearance" parent="TextAppearance.AppCompat.Widget.ActionBar.Title">
<item name="android:textSize">6sp</item>
<item name="android:textColor">@color/gary</item>
</style>
代碼
//綁定VP
tabHost.setupWithViewPager(pager);
- 通過(guò)指定Tab的方式設(shè)置菜單
//指定Tab的位置
Tab one = mTabLayout.getTabAt(0);
Tab two = mTabLayout.getTabAt(1);
Tab three = mTabLayout.getTabAt(2);
Tab four = mTabLayout.getTabAt(3);
//設(shè)置Tab的圖標(biāo)寝优,假如不需要?jiǎng)t把下面的代碼刪去
one.setIcon(R.mipmap.ic_launcher);
two.setIcon(R.mipmap.ic_launcher);
three.setIcon(R.mipmap.ic_launcher);
four.setIcon(R.mipmap.ic_launcher);
綁定好VP和TabLayout后条舔,菜單的tv內(nèi)容默認(rèn)是從適配器中getPageTitle
獲得。
public class MyFragmentPagerAdapter extends FragmentPagerAdapter {
... ...
//PageTitle就是Tab的Text
@Override
public CharSequence getPageTitle(int position) {
return mTitles[position];
}
}
- 你還可以通過(guò)
setCustomView
的方式對(duì)菜單進(jìn)行設(shè)置:
//自定義菜單
for (int i = 0; i < adapter.getCount(); i++) {
TabLayout.Tab tab = tabHost.getTabAt(i);
if (tab != null) {
tab.setCustomView(adapter.getTabView(i));
}
}
//在適配器中乏矾,返回自定義View
public View getTabView(int position) {
View view = LayoutInflater.from(context).inflate(R.layout.tab_layout, null);
ImageView iv = view.findViewById(R.id.iv);
TextView tv = view.findViewById(R.id.tv);
iv.setImageResource(images.get(position));
tv.setText(title.get(position));
return view;
}
通過(guò)這種方式設(shè)置的好處是孟抗,不用關(guān)心適配器中getPageTitle
的返回值啦!所有的設(shè)置都在view內(nèi)钻心。
5.BottomNavigationView
依賴
- 官方
compile 'com.android.support:support-v4:xx.x.x'
compile 'com.android.support:design:xx.x.x' - 也可以使用第三方庫(kù)BottomNavigationBar
compile 'com.ashokvarma.android:bottom-navigation-bar:2.0.4'
使用方法:https://github.com/Ashok-Varma/BottomNavigation/wiki
本文用的是 官方凄硼!官方!官方捷沸!
布局
<android.support.design.widget.CoordinatorLayout 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:id="@+id/activity_main2"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.activity.MainActivity">
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.BottomNavigationView
android:id="@+id/navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@drawable/black_line_white_inner"
app:itemIconTint="@drawable/nav_item_color_state"
app:itemTextColor="@drawable/nav_item_color_state"
app:menu="@menu/bottom_navigation_view" />
</android.support.design.widget.CoordinatorLayout>
注意:
app:menu="@menu/bottom_navigation_view"
是必須設(shè)置的摊沉,這個(gè)布局里面是導(dǎo)航菜單的內(nèi)容。
bottom_navigation_view.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/wechat"
android:icon="@mipmap/icon_project_not_selected"
android:title="微信"
app:showAsAction="always" />
<item
android:id="@+id/communcation"
android:icon="@mipmap/icon_knowledge_hierarchy_not_selected"
android:title="通訊錄"
app:showAsAction="always" />
<item
android:id="@+id/find"
android:icon="@mipmap/icon_navigation_not_selected"
android:title="發(fā)現(xiàn)"
app:showAsAction="always" />
<item
android:id="@+id/mine"
android:icon="@mipmap/icon_home_pager_not_selected"
android:title="我"
app:showAsAction="always" />
</menu>
代碼
//監(jiān)聽VP痒给,觸發(fā)BNV
mViewPager.addOnPageChangeListener(onPageListener);
private ViewPager.OnPageChangeListener onPageListener = new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
bottomNavigationView.getMenu().getItem(position).setChecked(true);
}
@Override
public void onPageScrollStateChanged(int state) {
}
};
//監(jiān)聽BNV说墨,觸發(fā)VP
bottomNavigationView.setOnNavigationItemSelectedListener(onItemSelectedListener);
private BottomNavigationView.OnNavigationItemSelectedListener onItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
resetToDefaultIcon();//重置到默認(rèn)不選中圖片
switch (item.getItemId()) {
case R.id.wechat:
mViewPager.setCurrentItem(Constants.FIRST);
item.setIcon(R.mipmap.icon_project_not_selected);
break;
case R.id.communcation:
mViewPager.setCurrentItem(Constants.SECOND);
break;
case R.id.find:
mViewPager.setCurrentItem(Constants.THIRD);
break;
case R.id.mine:
mViewPager.setCurrentItem(Constants.FOURTH);
break;
default:
break;
}
return true;
}
};
//解決 BottomNavigationView 圖標(biāo)位移非等分問(wèn)題(使用的時(shí)候 item 數(shù)大于 3 個(gè)時(shí)會(huì)有位移)
BottomNavigationViewHelper.disableShiftMode(bottomNavigationView);
BottomNavigationViewHelper .java
public class BottomNavigationViewHelper {
@SuppressLint("RestrictedApi")
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);
}
}
}
補(bǔ)充
如果想禁用選中菜單時(shí)放大的動(dòng)畫效果,只需要在資源文件dimen.xml
中苍柏,如下設(shè)置:
<!--BottomNavigationView 有文字-->
<dimen name="design_bottom_navigation_active_text_size">10sp</dimen>
<dimen name="design_bottom_navigation_text_size">10sp</dimen>
<!--BottomNavigationView 只放圖標(biāo)尼斧,無(wú)文字-->
<dimen name="design_bottom_navigation_active_text_size">0sp</dimen>
<dimen name="design_bottom_navigation_text_size">0sp</dimen>
<dimen name="design_bottom_navigation_margin">16dp</dimen>