鴻蒙HarmonyOS三方件開發(fā)指南(17)-BottomNavigationBar
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
引言
BottomNavigationBar底部導(dǎo)航欄,可以說所有的app是這樣的頁面架構(gòu),原因很簡單,操作簡單,模塊化清晰,頁面切換流暢,而且每頁都可以展示不同的風(fēng)格。相信開發(fā)者已經(jīng)很熟悉Android的底部導(dǎo)航欄的開發(fā)以及開發(fā)流程,那么接下來將對比Android來講解鴻蒙的底部導(dǎo)航欄的實(shí)現(xiàn)步驟。
功能介紹
鴻蒙BottomNavigationBar底部導(dǎo)航欄,根據(jù)所需要底部button的數(shù)量,動態(tài)生成對應(yīng)的底部button,并且可以設(shè)置默認(rèn)字體顏色,選中字體顏色,默認(rèn)icon,選中icon屬性。模擬器效果圖如下:
看了效果圖,是不是都想知道在實(shí)際工作中,是如何使用的呢?接下來給大家詳細(xì)介紹下BottomNavigationBar如何使用。
BottomNavigationBar使用指南
Ø 新建工程, 添加組件Har包依賴
在應(yīng)用模塊中添加HAR,只需要將mylibrarybottom-debug.har復(fù)制到entry\libs目錄下即可。
Ø 修改相關(guān)文件
1. 修改主頁面的布局文件ability_main.xml:
2. 修改MainAbilitySlice代碼:
3. 修改BaseAbilitySlinct代碼:
4. MainAbility的代碼:
配置好1-4步,接下來就看如何給對應(yīng)的底部導(dǎo)航欄添加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;
- // 將每個條目的數(shù)據(jù)放入到集合
- bottomInfoList.add(homeInfo);
- bottomInfoList.add(favoriteInfo);
- bottomInfoList.add(categoryInfo);
- bottomInfoList.add(profileInfo);
- // 設(shè)置底部導(dǎo)航欄的透明度
- tabBottomLayout.setBarBottomAlpha(0.85f);
- // 初始化所有的條目
- tabBottomLayout.initInfo(bottomInfoList);
- initFractionBarComponent();
- tabBottomLayout.addBarSelectedChangeListener((index, prevInfo, nextInfo) ->
- // 顯示fraction
- mFractionBarComponent.setCurrentItem(index));
- // 設(shè)置默認(rèn)選中的條目,該方法一定要在最后調(diào)用
- tabBottomLayout.defaultSelected(homeInfo);
2. 創(chuàng)建fraction類,繼承BaseFraction
(1)引入需要展示頁面的布局文件
- @Override
- blic int getUIComponent() {
- return ResourceTable.Layout_layout_fraction_home;
(2)操作布局文件中的控件
- @Override
- public void initComponent(Component component) {
- text = (Text) component.findComponentById(ResourceTable.Id_text);
- }
BottomNavigationBar開發(fā)指南
底部導(dǎo)航欄,在應(yīng)用中真的非常常見,核心思想就是底部有幾個選項(xiàng),然后點(diǎn)擊其中任意一個,切換至對應(yīng)的頁面。接下來主要介紹下核心實(shí)現(xiàn)步驟。
主要封裝的原則是,動態(tài)的,通過外界傳遞,固定過的則封裝起來。其中底部導(dǎo)航欄的圖片、文字、文字的顏色是變的,其它的可以封裝起來,外界只需要把每個條目的圖片、文字以及文字的顏色傳入進(jìn)來即可,內(nèi)部來實(shí)現(xiàn)底部導(dǎo)航欄。在封裝的時(shí)候,需要面向接口編程,同時(shí)使用泛型。
定義接口IBarLayout
1、定義一個IBarLayout接口,第一個泛型就是底部導(dǎo)航欄中的每個條目,第二個泛型是每個條目的數(shù)據(jù)。在接口里面提供一些方法,可以根據(jù)數(shù)據(jù)查找條目,可以添加監(jiān)聽,可以設(shè)置默認(rèn)選中的條目,可以初始化所有的條目,當(dāng)某個條目被選中后需要通過回調(diào)方法。
代碼如下:
- public interface IBarLayout<Bar extends ComponentContainer, D> {
- /**
- * 根據(jù)數(shù)據(jù)查找條目
- *
- * @param info 數(shù)據(jù)
- * @return 條目
- */
- Bar findBar(D info);
- /**
- * 添加監(jiān)聽
- *
- * @param listener
- */
- void addBarSelectedChangeListener(OnBarSelectedListener<D> listener);
- /**
- * 默認(rèn)選中的條目
- *
- * @param defaultInfo
- */
- void defaultSelected(D defaultInfo);
- /**
- * 初始化所有的條目
- *
- * @param infoList
- */
- void initInfo(List<D> infoList);
- interface OnBarSelectedListener<D> {
- /**
- * 當(dāng)某個條目被選中后的回調(diào),該方法會被調(diào)用多次
- *
- * @param index 點(diǎn)擊后選中條目的下標(biāo)
- * @param preInfo 點(diǎn)擊前選中的條目
- * @param nextInfo 點(diǎn)擊后選中的條目
- */
- void onBarSelectedChange(int index, D preInfo, D nextInfo);
- }
- }
2、再定義一個單個條目的接口IBar,泛型就是每個條目的數(shù)據(jù),接口里面定義方法,可以設(shè)置條目的數(shù)據(jù),可以動態(tài)修改某個條目的大小
代碼如下:
- /**
- * 單個條目的接口
- */
- public interface IBar<D> extends IBarLayout.OnBarSelectedListener<D> {
- /**
- * 設(shè)置條目的數(shù)據(jù)
- *
- * @param data
- */
- void setBarInfo(D data);
- /**
- * 動態(tài)修改某個條目的大小
- *
- * @param height
- */
- void resetHeight(int height);
- }
每個條目所對應(yīng)的實(shí)體類BottomBarInfo
每個條目都有自己的圖片、文字、文字的顏色,我們把這些屬性定義在一個實(shí)體類中。由于顏色可以是整型,也可以是字符串,這里定義泛型,泛型就是文字的顏色。具體是哪種類型的顏色,由調(diào)用者來決定。
注意下BarType這個枚舉,我們的底部導(dǎo)航欄支持兩種類型,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,繼承相對布局,實(shí)現(xiàn)之前定義的IBar接口,泛型就是每個條目所對應(yīng)的實(shí)體類,由于目前并不知道泛型的具體類型,所以泛型直接使用問號來代替。BottomBar就是單個條目。
我們需要將component對象放入到BottomBar中,所以第二個參數(shù)傳this,第三個參數(shù)為true。
- public class BottomBar extends DependentLayout implements IBar<BottomBarInfo<?>> {
- /**
- * 當(dāng)前條目所對應(yīng)的數(shù)據(jù)
- */
- 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);
- }
- /**
- * 設(shè)置條目的數(shù)據(jù)
- *
- * @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)) {
- // 設(shè)置條目的名稱
- 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;
- }
- }
- /**
- * 動態(tài)修改某個tab的高度
- *
- * @param height tab的高度
- */
- @Override
- public void resetHeight(int height) {
- ComponentContainer.LayoutConfig config = getLayoutConfig();
- config.height = height;
- setLayoutConfig(config);
- mTabName.setVisibility(HIDE);
- }
- /**
- * 當(dāng)某個條目被選中后的回調(diào),該方法會被調(diào)用多次
- *
- * @param index 點(diǎn)擊后選中條目的下標(biāo)
- * @param preInfo 點(diǎn)擊前選中的條目
- * @param nextInfo 點(diǎn)擊后選中的條目
- */
- @Override
- public void onBarSelectedChange(int index, BottomBarInfo<?> preInfo, BottomBarInfo<?> nextInfo) {
- if (nextInfo.tabType == BottomBarInfo.BarType.IMAGE) {
- // 當(dāng)前條目的類型是IMAGE類型,則不做任何處理
- return;
- }
- if (preInfo == nextInfo) {
- // 假設(shè)當(dāng)前選中的是條目1,同時(shí)點(diǎn)擊的也是條目1,那就不需要做任何操作了
- return;
- }
- if (preInfo != tabInfo && nextInfo != tabInfo) {
- /**
- * 假設(shè)有三個條目,條目1、條目2、條目3,preInfo是條目1,nextInfo是條目3,tabInfo是條目2,
- * 點(diǎn)擊前選中的是條目1,點(diǎn)擊后選中的條目3,此時(shí)條目2就不需要做任何操作了
- */
- return;
- }
- if (preInfo == tabInfo) {
- // 將點(diǎn)擊前的條目反選
- inflateInfo(false, false);
- } else {
- // 選中被點(diǎn)擊的條目
- inflateInfo(true, false);
- }
- }
- public BottomBarInfo<Color> getTabInfo() {
- return tabInfo;
- }
- public Text getTabName() {
- return mTabName;
- }
- public Image getImage() {
- return mTabImage;
- }
- }
底部導(dǎo)航欄的封裝
定義BottomNavigationBar,繼承棧布局。第一個泛型就是底部導(dǎo)航欄的條目,第二個泛型就是每個條目的數(shù)據(jù)。
- public class BottomNavigationBar extends StackLayout implements IBarLayout<BottomBar, BottomBarInfo<?>> {
- private static final int ID_TAB_BOTTOM = 0XFF;
- /**
- * 事件監(jiān)聽的集合
- */
- private List<OnBarSelectedListener<BottomBarInfo<?>>> tabSelectedListeners = new ArrayList<>();
- /**
- * 當(dāng)前選中的條目
- */
- private BottomBarInfo<?> selectedInfo;
- /**
- * 底部導(dǎo)航欄的透明度
- */
- private float barBottomAlpha = 1;
- /**
- * 底部導(dǎo)航欄的高度
- */
- private float barBottomHeight = 50;
- /**
- * 底部導(dǎo)航欄線條的高度
- */
- private float barBottomLineHeight = 0.5f;
- /**
- * 底部導(dǎo)航欄線條的顏色
- */
- 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);
- }
- /**
- * 根據(jù)數(shù)據(jù)查找條目
- *
- * @param info 條目的數(shù)據(jù)
- * @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;
- }
- /**
- * 添加監(jiān)聽
- *
- * @param listener 監(jiān)聽
- */
- @Override
- public void addBarSelectedChangeListener(OnBarSelectedListener<BottomBarInfo<?>> listener) {
- tabSelectedListeners.add(listener);
- }
- /**
- * 默認(rèn)選中的條目
- *
- * @param defaultInfo 默認(rèn)選中條目的信息
- */
- @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;
- // 移除之前已經(jīng)添加的組件,防止重復(fù)添加
- removeComponent();
- selectedInfo = null;
- // 添加背景
- addBackground();
- // 添加條目
- addBottomBar();
- // 添加線條
- addBottomLine();
- }
- /**
- * 添加線條
- */
- private void addBottomLine() {
- Component line = new Component(getContext());
- // 目前不支持直接設(shè)置背景顏色,只能通過Element來設(shè)置背景
- 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() {
- // 每個條目的寬度就是屏幕寬度除以條目的總個數(shù)
- int width = DisplayUtils.getDisplayWidthInPx(getContext()) / infoList.size();
- // 高度是固定的值,這里需要做屏幕適配,將vp轉(zhuǎn)換成像素
- 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);
- // 創(chuàng)建布局配置對象
- LayoutConfig config = new LayoutConfig(width, height);
- // 設(shè)置底部對齊
- config.alignment = LayoutAlignment.BOTTOM;
- // 設(shè)置左邊距
- config.setMarginLeft(i * width);
- BottomBar bottomBar = new BottomBar(getContext());
- tabSelectedListeners.add(bottomBar);
- // 初始化每個條目
- bottomBar.setBarInfo(info);
- // 添加條目
- stackLayout.addComponent(bottomBar, config);
- // 設(shè)置點(diǎn)擊事件
- bottomBar.setClickedListener(component -> onSelected(info));
- }
- LayoutConfig layoutConfig = new LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT,
- ComponentContainer.LayoutConfig.MATCH_CONTENT);
- layoutConfig.alignment = LayoutAlignment.BOTTOM;
- addComponent(stackLayout, layoutConfig);
- }
- /**
- * 點(diǎn)擊條目后給外界回調(diào)
- *
- * @param nextInfo 點(diǎn)擊后需要選中的條目
- */
- 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());
- // 目前還不能直接設(shè)置背景顏色,只能通過Element來設(shè)置背景
- 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);
- }
- /**
- * 移除之前已經(jīng)添加的組件,防止重復(fù)添加
- */
- private void removeComponent() {
- for (int i = getChildCount() - 1; i > 0; i--) {
- removeComponentAt(i);
- }
- tabSelectedListeners.removeIf(listener ->
- listener instanceof BottomBar);
- }
- /**
- * 設(shè)置底部導(dǎo)航欄的透明度
- *
- * @param barBottomAlpha 底部導(dǎo)航欄的透明度
- */
- public void setBarBottomAlpha(float barBottomAlpha) {
- this.barBottomAlpha = barBottomAlpha;
- }
- /**
- * 設(shè)置底部導(dǎo)航欄的高度
- *
- * @param barBottomHeight 底部導(dǎo)航欄的高度
- */
- public void setBarBottomHeight(float barBottomHeight) {
- this.barBottomHeight = barBottomHeight;
- }
- /**
- * 設(shè)置底部導(dǎo)航欄線條的高度
- *
- * @param barBottomLineHeight 底部導(dǎo)航欄線條的高度
- */
- public void setBarBottomLineHeight(float barBottomLineHeight) {
- this.barBottomLineHeight = barBottomLineHeight;
- }
- /**
- * 設(shè)置底部導(dǎo)航欄線條的顏色
- *
- * @param barBottomLineColor 底部導(dǎo)航欄線條的顏色
- */
- public void setBarBottomLineColor(RgbColor barBottomLineColor) {
- this.barBottomLineColor = barBottomLineColor;
- }
- }
initInfo(List<BottomBarInfo<?>> infoList)該方法由外界調(diào)用,外界將所有的條目信息傳遞過來,我們將條目添加到底部導(dǎo)航欄。首先移除之前已經(jīng)添加的組件,防止重復(fù)添加,然后添加背景,添加條目,添加線條。
更多原創(chuàng),請關(guān)注:軟通動力HarmonyOS學(xué)院https://harmonyos.51cto.com/column/30
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)