目錄:
引言
BottomNavigationBar底部導航欄,可以說所有的app是這樣的頁面架構,原因很簡單,操作簡單,模塊化清晰,頁面切換流暢,而且每頁都可以展示不同的風格。相信開發者已經很熟悉Android的底部導航欄的開發以及開發流程,那么接下來將對比Android來講解鴻蒙的底部導航欄的實現步驟。
功能介紹
鴻蒙BottomNavigationBar底部導航欄,根據所需要底部button的數量,動態生成對應的底部button,並且可以設置默認字體顏色,選中字體顏色,默認icon,選中icon屬性。模擬器效果圖如下:

看了效果圖,是不是都想知道在實際工作中,是如何使用的呢?接下來給大家詳細介紹下BottomNavigationBar如何使用。
BottomNavigationBar使用指南
Ø 新建工程, 添加組件Har包依賴
在應用模塊中添加HAR,只需要將mylibrarybottom-debug.har復制到entry\libs目錄下即可。
Ø 修改相關文件
1. 修改主頁面的布局文件ability_main.xml:

2. 修改MainAbilitySlice代碼:

3. 修改BaseAbilitySlinct代碼:

4. MainAbility的代碼:

配置好1-4步,接下來就看如何給對應的底部導航欄添加Fraction
1. initBottom 方法如下:
private void initBottom() {
tabBottomLayout = (BottomNavigationBar) mAbilitySliceProvider.findComponentById(ResourceTable.Id_bottom_navigation_bar);
bottomInfoList = new ArrayList<>();
// 獲取string.json文件中定義的字符串
String home = mAbilitySliceProvider.getString(ResourceTable.String_home);
String favorite = mAbilitySliceProvider.getString(ResourceTable.String_favorite);
String category = mAbilitySliceProvider.getString(ResourceTable.String_category);
String profile = mAbilitySliceProvider.getString(ResourceTable.String_mine);
// 首頁
BottomBarInfo<Integer> homeInfo = new BottomBarInfo<>(home, ResourceTable.Media_category_norma1, ResourceTable.Media_category_norma2, defaultColor, tintColor); homeInfo.fraction = HomeFraction.class; // 收藏 BottomBarInfo<Integer> favoriteInfo = new BottomBarInfo<>(favorite, ResourceTable.Media_category_norma1, ResourceTable.Media_category_norma2, defaultColor, tintColor); favoriteInfo.fraction = SecondFraction.class; // 分類 BottomBarInfo<Integer> categoryInfo = new BottomBarInfo<>(category, ResourceTable.Media_category_norma1, ResourceTable.Media_category_norma2, defaultColor, tintColor); categoryInfo.fraction = ThirdFraction.class; // 我的 BottomBarInfo<Integer> profileInfo = new BottomBarInfo<>(profile, ResourceTable.Media_category_norma1, ResourceTable.Media_category_norma2, defaultColor, tintColor); profileInfo.fraction = MineFraction.class; // 將每個條目的數據放入到集合 bottomInfoList.add(homeInfo); bottomInfoList.add(favoriteInfo); bottomInfoList.add(categoryInfo); bottomInfoList.add(profileInfo); // 設置底部導航欄的透明度 tabBottomLayout.setBarBottomAlpha(0.85f); // 初始化所有的條目 tabBottomLayout.initInfo(bottomInfoList); initFractionBarComponent(); tabBottomLayout.addBarSelectedChangeListener((index, prevInfo, nextInfo) -> // 顯示fraction mFractionBarComponent.setCurrentItem(index)); // 設置默認選中的條目,該方法一定要在最后調用 tabBottomLayout.defaultSelected(homeInfo);
2. 創建fraction類,繼承BaseFraction
1. 引入需要展示頁面的布局文件
@Override
public int getUIComponent() {
return ResourceTable.Layout_layout_fraction_home;
}
2. 操作布局文件中的控件
@Override
public void initComponent(Component component) {
text = (Text) component.findComponentById(ResourceTable.Id_text);
}
BottomNavigationBar開發指南
底部導航欄,在應用中真的非常常見,核心思想就是底部有幾個選項,然后點擊其中任意一個,切換至對應的頁面。接下來主要介紹下核心實現步驟。
主要封裝的原則是,動態的,通過外界傳遞,固定過的則封裝起來。其中底部導航欄的圖片、文字、文字的顏色是變的,其它的可以封裝起來,外界只需要把每個條目的圖片、文字以及文字的顏色傳入進來即可,內部來實現底部導航欄。在封裝的時候,需要面向接口編程,同時使用泛型。
定義接口IBarLayout
1、定義一個IBarLayout接口,第一個泛型就是底部導航欄中的每個條目,第二個泛型是每個條目的數據。在接口里面提供一些方法,可以根據數據查找條目,可以添加監聽,可以設置默認選中的條目,可以初始化所有的條目,當某個條目被選中后需要通過回調方法。
代碼如下:
public interface IBarLayout<Bar extends ComponentContainer, D> { /** * 根據數據查找條目 * * @param info 數據 * @return 條目 */ Bar findBar(D info); /** * 添加監聽 * * @param listener */ void addBarSelectedChangeListener(OnBarSelectedListener<D> listener); /** * 默認選中的條目 * * @param defaultInfo */ void defaultSelected(D defaultInfo); /** * 初始化所有的條目 * * @param infoList */ void initInfo(List<D> infoList); interface OnBarSelectedListener<D> { /** * 當某個條目被選中后的回調,該方法會被調用多次 * * @param index 點擊后選中條目的下標 * @param preInfo 點擊前選中的條目 * @param nextInfo 點擊后選中的條目 */ void onBarSelectedChange(int index, D preInfo, D nextInfo); } }
2、再定義一個單個條目的接口IBar,泛型就是每個條目的數據,接口里面定義方法,可以設置條目的數據,可以動態修改某個條目的大小
代碼如下:
/**
* 單個條目的接口
*/
public interface IBar<D> extends IBarLayout.OnBarSelectedListener<D> { /** * 設置條目的數據 * * @param data */ void setBarInfo(D data); /** * 動態修改某個條目的大小 * * @param height */ void resetHeight(int height); }
每個條目所對應的實體類BottomBarInfo
每個條目都有自己的圖片、文字、文字的顏色,我們把這些屬性定義在一個實體類中。由於顏色可以是整型,也可以是字符串,這里定義泛型,泛型就是文字的顏色。具體是哪種類型的顏色,由調用者來決定。
注意下BarType這個枚舉,我們的底部導航欄支持兩種類型,IMAGE代表下圖,某個條目只顯示圖片,也可以讓某個條目凸出來,只需要將條目的高度變高即可。
public class BottomBarInfo<Color> extends TopBottomBarInfo { public enum BarType { /** * 顯示圖片和文案 */ IMAGE_TEXT, /** * 只顯示圖片 */ IMAGE } /** * 條目的名稱 */ public String name; public BarType tabType; public Class<? extends Fraction> fraction; public BottomBarInfo(String name, int defaultImage, int selectedImage) { this.name = name; this.defaultImage = defaultImage; this.selectedImage = selectedImage; this.tabType = BarType.IMAGE; } public BottomBarInfo(String name, int defaultImage, int selectedImage, Color defaultColor, Color tintColor) { this.name = name; this.defaultImage = defaultImage; this.selectedImage = selectedImage; this.defaultColor = defaultColor; this.tintColor = tintColor; this.tabType = BarType.IMAGE_TEXT; } }
單個條目的封裝
定義BottomBar,繼承相對布局,實現之前定義的IBar接口,泛型就是每個條目所對應的實體類,由於目前並不知道泛型的具體類型,所以泛型直接使用問號來代替。BottomBar就是單個條目。
我們需要將component對象放入到BottomBar中,所以第二個參數傳this,第三個參數為true。
public class BottomBar extends DependentLayout implements IBar<BottomBarInfo<?>> { /** * 當前條目所對應的數據 */ private BottomBarInfo<Color> tabInfo; private Text mTabName; private Image mTabImage; public BottomBar(Context context) { this(context, null); } public BottomBar(Context context, AttrSet attrSet) { this(context, attrSet, ""); } public BottomBar(Context context, AttrSet attrSet, String styleName) { super(context, attrSet, styleName); Component component = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_layout_bar_bottom, this, true); mTabImage = (Image) component.findComponentById(ResourceTable.Id_image); mTabName = (Text) component.findComponentById(ResourceTable.Id_name); mTabImage.setScaleMode(Image.ScaleMode.INSIDE); } /** * 設置條目的數據 * * @param data */ @Override public void setBarInfo(BottomBarInfo<?> data) { tabInfo = (BottomBarInfo<Color>) data; inflateInfo(false, true); } /** * 初始化條目 * * @param selected true 選中 * @param init true 初始化 */ private void inflateInfo(boolean selected, boolean init) { if (tabInfo.tabType == BottomBarInfo.BarType.IMAGE_TEXT) { if (init) { // 圖片和名稱都可見 mTabName.setVisibility(VISIBLE); mTabImage.setVisibility(VISIBLE); if (!TextUtils.isEmpty(tabInfo.name)) { // 設置條目的名稱 mTabName.setText(tabInfo.name); } } if (selected) { // 顯示選中的圖片 mTabImage.setPixelMap(tabInfo.selectedImage); mTabName.setTextColor(new Color(parseColor(tabInfo.tintColor))); } else { // 顯示未選中的圖片 mTabImage.setPixelMap(tabInfo.defaultImage); mTabName.setTextColor(new Color(parseColor(tabInfo.defaultColor))); } } else if (tabInfo.tabType == BottomBarInfo.BarType.IMAGE) { if (init) { // 僅僅顯示圖片,將名稱隱藏 mTabName.setVisibility(HIDE); mTabImage.setVisibility(VISIBLE); } if (selected) { // 顯示選中的圖片 mTabImage.setPixelMap(tabInfo.selectedImage); } else { // 顯示未選中的圖片 mTabImage.setPixelMap(tabInfo.defaultImage); } } } private int parseColor(Object color) { if (color instanceof String) { return Color.getIntColor((String) color); } else { return (int) color; } } /** * 動態修改某個tab的高度 * * @param height tab的高度 */ @Override public void resetHeight(int height) { ComponentContainer.LayoutConfig config = getLayoutConfig(); config.height = height; setLayoutConfig(config); mTabName.setVisibility(HIDE); } /** * 當某個條目被選中后的回調,該方法會被調用多次 * * @param index 點擊后選中條目的下標 * @param preInfo 點擊前選中的條目 * @param nextInfo 點擊后選中的條目 */ @Override public void onBarSelectedChange(int index, BottomBarInfo<?> preInfo, BottomBarInfo<?> nextInfo) { if (nextInfo.tabType == BottomBarInfo.BarType.IMAGE) { // 當前條目的類型是IMAGE類型,則不做任何處理 return; } if (preInfo == nextInfo) { // 假設當前選中的是條目1,同時點擊的也是條目1,那就不需要做任何操作了 return; } if (preInfo != tabInfo && nextInfo != tabInfo) { /** * 假設有三個條目,條目1、條目2、條目3,preInfo是條目1,nextInfo是條目3,tabInfo是條目2, * 點擊前選中的是條目1,點擊后選中的條目3,此時條目2就不需要做任何操作了 */ return; } if (preInfo == tabInfo) { // 將點擊前的條目反選 inflateInfo(false, false); } else { // 選中被點擊的條目 inflateInfo(true, false); } } public BottomBarInfo<Color> getTabInfo() { return tabInfo; } public Text getTabName() { return mTabName; } public Image getImage() { return mTabImage; } }
底部導航欄的封裝
定義BottomNavigationBar,繼承棧布局。第一個泛型就是底部導航欄的條目,第二個泛型就是每個條目的數據。
public class BottomNavigationBar extends StackLayout implements IBarLayout<BottomBar, BottomBarInfo<?>> { private static final int ID_TAB_BOTTOM = 0XFF; /** * 事件監聽的集合 */ private List<OnBarSelectedListener<BottomBarInfo<?>>> tabSelectedListeners = new ArrayList<>(); /** * 當前選中的條目 */ private BottomBarInfo<?> selectedInfo; /** * 底部導航欄的透明度 */ private float barBottomAlpha = 1; /** * 底部導航欄的高度 */ private float barBottomHeight = 50; /** * 底部導航欄線條的高度 */ private float barBottomLineHeight = 0.5f; /** * 底部導航欄線條的顏色 */ private RgbColor barBottomLineColor = new RgbColor(223, 224, 225); /** * 所有的tab */ private List<BottomBarInfo<?>> infoList; public BottomNavigationBar(Context context) { this(context, null); } public BottomNavigationBar(Context context, AttrSet attrSet) { this(context, attrSet, ""); } public BottomNavigationBar(Context context, AttrSet attrSet, String styleName) { super(context, attrSet, styleName); } /** * 根據數據查找條目 * * @param info 條目的數據 * @return 條目 */ @Override public BottomBar findBar(BottomBarInfo<?> info) { ComponentContainer componentContainer = (ComponentContainer) findComponentById(ID_TAB_BOTTOM); for (int i = 0; i < componentContainer.getChildCount(); i++) { Component component = componentContainer.getComponentAt(i); if (component instanceof BottomBar) { BottomBar bottomBar = (BottomBar) component; if (bottomBar.getTabInfo() == info) { return bottomBar; } } } return null; } /** * 添加監聽 * * @param listener 監聽 */ @Override public void addBarSelectedChangeListener(OnBarSelectedListener<BottomBarInfo<?>> listener) { tabSelectedListeners.add(listener); } /** * 默認選中的條目 * * @param defaultInfo 默認選中條目的信息 */ @Override public void defaultSelected(BottomBarInfo<?> defaultInfo) { onSelected(defaultInfo); } /** * 初始化所有的條目 * * @param infoList 所有條目的信息 */ @Override public void initInfo(List<BottomBarInfo<?>> infoList) { if (infoList == null || infoList.isEmpty()) { return; } this.infoList = infoList; // 移除之前已經添加的組件,防止重復添加 removeComponent(); selectedInfo = null; // 添加背景 addBackground(); // 添加條目 addBottomBar(); // 添加線條 addBottomLine(); } /** * 添加線條 */ private void addBottomLine() { Component line = new Component(getContext()); // 目前不支持直接設置背景顏色,只能通過Element來設置背景 ShapeElement element = new ShapeElement(); element.setShape(ShapeElement.RECTANGLE); element.setRgbColor(barBottomLineColor); line.setBackground(element); LayoutConfig config = new LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT, DisplayUtils.vp2px(getContext(), barBottomLineHeight)); // 位於底部 config.alignment = LayoutAlignment.BOTTOM; config.setMarginBottom(DisplayUtils.vp2px(getContext(), barBottomHeight - barBottomLineHeight)); line.setAlpha(barBottomAlpha); addComponent(line, config); } /** * 添加條目 */ private void addBottomBar() { // 每個條目的寬度就是屏幕寬度除以條目的總個數 int width = DisplayUtils.getDisplayWidthInPx(getContext()) / infoList.size(); // 高度是固定的值,這里需要做屏幕適配,將vp轉換成像素 int height = DisplayUtils.vp2px(getContext(), barBottomHeight); StackLayout stackLayout = new StackLayout(getContext()); stackLayout.setId(ID_TAB_BOTTOM); for (int i = 0; i < infoList.size(); i++) { BottomBarInfo<?> info = infoList.get(i); // 創建布局配置對象 LayoutConfig config = new LayoutConfig(width, height); // 設置底部對齊 config.alignment = LayoutAlignment.BOTTOM; // 設置左邊距 config.setMarginLeft(i * width); BottomBar bottomBar = new BottomBar(getContext()); tabSelectedListeners.add(bottomBar); // 初始化每個條目 bottomBar.setBarInfo(info); // 添加條目 stackLayout.addComponent(bottomBar, config); // 設置點擊事件 bottomBar.setClickedListener(component -> onSelected(info)); } LayoutConfig layoutConfig = new LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_CONTENT); layoutConfig.alignment = LayoutAlignment.BOTTOM; addComponent(stackLayout, layoutConfig); } /** * 點擊條目后給外界回調 * * @param nextInfo 點擊后需要選中的條目 */ private void onSelected(BottomBarInfo<?> nextInfo) { for (OnBarSelectedListener<BottomBarInfo<?>> listener : tabSelectedListeners) { listener.onBarSelectedChange(infoList.indexOf(nextInfo), selectedInfo, nextInfo); } if (nextInfo.tabType == BottomBarInfo.BarType.IMAGE_TEXT) { selectedInfo = nextInfo; } } /** * 添加背景 */ private void addBackground() { Component component = new Component(getContext()); // 目前還不能直接設置背景顏色,只能通過Element來設置背景 ShapeElement element = new ShapeElement(); element.setShape(ShapeElement.RECTANGLE); RgbColor rgbColor = new RgbColor(255, 255, 255); element.setRgbColor(rgbColor); component.setBackground(element); component.setAlpha(barBottomAlpha); LayoutConfig config = new LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT, DisplayUtils.vp2px(getContext(), barBottomHeight)); config.alignment = LayoutAlignment.BOTTOM; addComponent(component, config); } /** * 移除之前已經添加的組件,防止重復添加 */ private void removeComponent() { for (int i = getChildCount() - 1; i > 0; i--) { removeComponentAt(i); } tabSelectedListeners.removeIf(listener -> listener instanceof BottomBar); } /** * 設置底部導航欄的透明度 * * @param barBottomAlpha 底部導航欄的透明度 */ public void setBarBottomAlpha(float barBottomAlpha) { this.barBottomAlpha = barBottomAlpha; } /** * 設置底部導航欄的高度 * * @param barBottomHeight 底部導航欄的高度 */ public void setBarBottomHeight(float barBottomHeight) { this.barBottomHeight = barBottomHeight; } /** * 設置底部導航欄線條的高度 * * @param barBottomLineHeight 底部導航欄線條的高度 */ public void setBarBottomLineHeight(float barBottomLineHeight) { this.barBottomLineHeight = barBottomLineHeight; } /** * 設置底部導航欄線條的顏色 * * @param barBottomLineColor 底部導航欄線條的顏色 */ public void setBarBottomLineColor(RgbColor barBottomLineColor) { this.barBottomLineColor = barBottomLineColor; } }
initInfo(List<BottomBarInfo<?>> infoList)該方法由外界調用,外界將所有的條目信息傳遞過來,我們將條目添加到底部導航欄。首先移除之前已經添加的組件,防止重復添加,然后添加背景,添加條目,添加線條。
更多原創,請關注:軟通動力HarmonyOS學院https://harmonyos.51cto.com/column/30
作者:軟通田可輝
想了解更多內容,請訪問51CTO和華為合作共建的鴻蒙社區:https://harmonyos.51cto.com/
