android 底部导航栏(鸿蒙底部导航栏 vs 安卓底部导航栏)

BottomNavigationBar 底部导航栏,可以说所有的 App 是这样的页面架构,原因很简单,操作简单,模块化清晰,页面切换流畅,而且每页都可以展示不同的风格。 相信开发者已经很熟悉 Android 的...

BottomNavigationBar 底部导航栏,可以说所有的 App 是这样的页面架构,原因很简单,操作简单,模块化清晰,页面切换流畅,而且每页都可以展示不同的风格。

鸿蒙底部导航栏 vs 安卓底部导航栏

相信开发者已经很熟悉 Android 的底部导航栏的开发以及开发流程,那么接下来将对比 Android 来讲解鸿蒙的底部导航栏的实现步骤。

01功能介绍

鸿蒙 BottomNavigationBar 底部导航栏,根据所需要底部 button 的数量,动态生成对应的底部 button,并且可以设置默认字体颜色,选中字体颜色,默认 icon,选中 icon 属性。

模拟器效果图如下:

鸿蒙底部导航栏 vs 安卓底部导航栏

看了效果图,是不是都想知道在实际工作中,是如何使用的呢?接下来给大家详细介绍下 BottomNavigationBar 如何使用。

02BottomNavigationBar 使用指南

①新建工程, 添加组件 Har 包依赖

在应用模块中添加 HAR,只需要将 mylibrarybottom-debug.har 复制到 entry\libs 目录下即可。

②修改相关文件

修改主页面的布局文件 ability_main.xml:

鸿蒙底部导航栏 vs 安卓底部导航栏

修改 MainAbilitySlice 代码:

鸿蒙底部导航栏 vs 安卓底部导航栏

修改 BaseAbilitySlinct 代码:

鸿蒙底部导航栏 vs 安卓底部导航栏

MainAbility 的代码:

鸿蒙底部导航栏 vs 安卓底部导航栏

配置好 1-4 步,接下来就看如何给对应的底部导航栏添加 Fraction。

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

创建 fraction 类,继承 BaseFraction。

引入需要展示页面的布局文件:

@Overridepublic int getUIComponent() { return ResourceTable.Layout_layout_fraction_home;}

操作布局文件中的控件:

@Overridepublic void initComponent(Component component) { text = (Text) component.findComponentById(ResourceTable.Id_text);}

03BottomNavigationBar 开发指南

底部导航栏,在应用中真的非常常见,核心思想就是底部有几个选项,然后点击其中任意一个,切换至对应的页面。接下来主要介绍下核心实现步骤。

主要封装的原则是,动态的,通过外界传递,固定过的则封装起来。

其中底部导航栏的图片、文字、文字的颜色是变的,其它的可以封装起来,外界只需要把每个条目的图片、文字以及文字的颜色传入进来即可,内部来实现底部导航栏。在封装的时候,需要面向接口编程,同时使用泛型。

①定义接口 IBarLayout

定义一个 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); }}

再定义一个单个条目的接口 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)该方法由外界调用,外界将所有的条目信息传递过来,我们将条目添加到底部导航栏。

作者: 软通田可辉

原文链接:https://mp.weixin.qq.com/s/696CZDjHG4_2h8NGXC6ReA

  • 发表于 2022-10-31 13:32
  • 阅读 ( 64 )
  • 分类:互联网

0 条评论

请先 登录 后评论
李富强
李富强

695 篇文章

你可能感兴趣的文章

相关问题