百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

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

suiw9 2025-04-06 20:50 9 浏览 0 评论

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

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

01功能介绍

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

模拟器效果图如下:

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

02BottomNavigationBar 使用指南

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

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

②修改相关文件

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

修改 MainAbilitySlice 代码:

修改 BaseAbilitySlinct 代码:

MainAbility 的代码:

配置好 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 homeInfo = new BottomBarInfo<>(home,
            ResourceTable.Media_category_norma1,
            ResourceTable.Media_category_norma2,
            defaultColor, tintColor);
    homeInfo.fraction = HomeFraction.class;
    // 收藏
    BottomBarInfo favoriteInfo = new BottomBarInfo<>(favorite,
            ResourceTable.Media_category_norma1,
            ResourceTable.Media_category_norma2,
            defaultColor, tintColor);
    favoriteInfo.fraction = SecondFraction.class;
    // 分类
    BottomBarInfo categoryInfo = new BottomBarInfo<>(category,
            ResourceTable.Media_category_norma1,
            ResourceTable.Media_category_norma2,
            defaultColor, tintColor);
    categoryInfo.fraction = ThirdFraction.class;
    // 我的
    BottomBarInfo 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。

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

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

操作布局文件中的控件:

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

03BottomNavigationBar 开发指南

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

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

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

①定义接口 IBarLayout

定义一个 IBarLayout 接口,第一个泛型就是底部导航栏中的每个条目,第二个泛型是每个条目的数据。

在接口里面提供一些方法,可以根据数据查找条目,可以添加监听,可以设置默认选中的条目,可以初始化所有的条目,当某个条目被选中后需要通过回调方法。

代码如下:

public interface IBarLayout {

    /**
     * 根据数据查找条目
     *
     * @param info 数据
     * @return 条目
     */
    Bar findBar(D info);

    /**
     * 添加监听
     *
     * @param listener
     */
    void addBarSelectedChangeListener(OnBarSelectedListener listener);

    /**
     * 默认选中的条目
     *
     * @param defaultInfo
     */
    void defaultSelected(D defaultInfo);

    /**
     * 初始化所有的条目
     *
     * @param infoList
     */
    void initInfo(List infoList);

    interface OnBarSelectedListener {

        /**
         * 当某个条目被选中后的回调,该方法会被调用多次
         *
         * @param index 点击后选中条目的下标
         * @param preInfo 点击前选中的条目
         * @param nextInfo 点击后选中的条目
         */
        void onBarSelectedChange(int index, D preInfo, D nextInfo);
    }
}

再定义一个单个条目的接口 IBar,泛型就是每个条目的数据,接口里面定义方法,可以设置条目的数据,可以动态修改某个条目的大小。

代码如下:

/**
 * 单个条目的接口
 */
public interface IBar extends IBarLayout.OnBarSelectedListener {

    /**
     * 设置条目的数据
     *
     * @param data
     */
    void setBarInfo(D data);

    /**
     * 动态修改某个条目的大小
     *
     * @param height
     */
    void resetHeight(int height);
}

②每个条目所对应的实体类 BottomBarInfo

每个条目都有自己的图片、文字、文字的颜色,我们把这些属性定义在一个实体类中。

由于颜色可以是整型,也可以是字符串,这里定义泛型,泛型就是文字的颜色。具体是哪种类型的颜色,由调用者来决定。

注意下 BarType 这个枚举,我们的底部导航栏支持两种类型,IMAGE 代表下图,某个条目只显示图片,也可以让某个条目凸出来,只需要将条目的高度变高即可。

public class BottomBarInfo extends TopBottomBarInfo {

    public enum BarType {
        /**
         * 显示图片和文案
         */
        IMAGE_TEXT,
        /**
         * 只显示图片
         */
        IMAGE
    }

    /**
     * 条目的名称
     */
    public String name;
    public BarType tabType;
    public Class 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 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) 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 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

相关推荐

C和Java效率对比试验和编译器优化影响

首先得承认这不是一个好例子,逻辑过于简单,受环境的干扰也特别大。不能作为评价一门语言综合效率的用例,仅仅是基于个人兴趣的小实验的记录。C语言版本1#include...

Java 代码执行原理(java代码是如何执行的)

专注于Java领域优质技术,欢迎关注作者|Alan来源|cnblogs.com/wangjiming/p/10455993.html对于任何一门语言,要想达到精通的水平,研究它的执行原理(或者...

JVM底层原理之如何选用C1、C2编译器?它们有什么区别?

JVM底层原理之如何选用C1、C2编译器?它们有什么区别?关于JVM底层的C1、C2编译器,很多人不知道它的具体概念,本篇我们详细的讲解一下。...

阿里巴巴Java性能调优实战:深入JVM即时编译器JIT,优化Java编译

深入JVM即时编译器JIT,优化Java编译然而许多Java开发人员对JIT编译器的了解并不多,不深挖其工作原理,也不深究如何检测应用程序的即时编译情况,线上发生问题后很难做到从容应对。类编...

JPHP--一款基于JVM的新PHP编译器(php 编译)

JPHP是一款基于Java语言编写的PHP编译器以及新运行时库,支持多线程、unicode字符串(UTF-16)、GUI、Android开发以及嵌入式Web应用。JPHP可以将PHP源码编译成JVM字...

Java:开源Java编译器的下一个前沿——实时编译即服务

  对于Java开发人员来说,实时(JIT)编译器是提高性能的关键。然而,在容器世界中,由于CPU和内存消耗的限制,性能的提高经常被抵消。为了帮助解决这个问题,EclipseOpenJ9JVM提供...

45张图带你从入门到精通学习WireShark

你好,这里是网络技术联盟站。...

Linux(麒麟)下如果使用wireshark抓包工具

Linux(麒麟)下如果使用wireshark抓包工具一、安装wireshark1、通过命令将麒麟系统iso文件挂载到/mnt2、进入到/mnt/KYLIN3、通过yuminstall安装两个安装...

强烈推荐APP破解常用工具集合(破解软件推荐)

抓包...

wireshark长时间抓包分多个文件(wireshark 长时间抓包)

http://www.cnblogs.com/wangqiguo/p/5068602.html推荐理由一般使用wireshark不需要长时间抓包的,但是有时候遇到网络通信中非常棘手的问题,例如一个小...

wireshark抓包教程详解(wireshark抓包全集)

如果本文对你有帮助,欢迎关注、讨论、点赞、收藏、转发给朋友,让我有持续创作的动力,让我们一起相互学习共同进步。...

收藏!Wireshark最好用的抓包命令都在这了!

号主:老杨丨11年资深网络工程师,更多网工提升干货,请关注公众号:网络工程师俱乐部下午好,我的网工朋友。在现在,网络流量的监控与分析变得尤为重要。无论是排查网络故障、优化性能还是确保网络安全,都需要精...

Wireshark快捷键大全,记住后抓包效率杠杠的

不管你是网络管理员、程序员还是安全工程师,Wireshark都是你的得力助手,用来排查问题、分析流量,甚至学习网络协议。...

渗透测试抓包工具-wireshark(渗透测试脚本)

wireshark功能介绍1.抓包嗅探协议分析2.安全专家必备的技能...

wireshark网络抓包详解(利用wireshark抓包工具抓取网络数据包)

一、简介...

取消回复欢迎 发表评论: