美文网首页
鸿蒙系统—打造通用的底部导航栏

鸿蒙系统—打造通用的底部导航栏

作者: 裴云飞 | 来源:发表于2020-12-23 01:54 被阅读0次

    一、效果图

    二、设计方案

    • 首先来看不合理的方案,我们可以直接在布局文件里面使用横向的线性布局,每个条目使用纵向的线性布局,然后使用abilityslice里面切换选中或者未选中的图片。这种方案缺点在于没有任何的封装,我们需要些大量的重复的代码。
    • 接着来看合理的方案,依据封装的原则,变的由外界传过来,不变的封装起来。我们发现底部导航栏的图片、文字、文字的颜色是变的,其它的可以封装起来,外界只需要把每个条目的图片、文字以及文字的颜色传入进来即可,内部来实现底部导航栏。在封装的时候,需要面向接口编程,同时使用泛型。

    三、定义接口

    1、定义一个IBarLayout接口,第一个泛型就是底部导航栏中的每个条目,第二个泛型是每个条目的数据。在接口里面提供一些方法,可以根据数据查找条目,可以添加监听,可以设置默认选中的条目,可以初始化所有的条目,当某个条目被选中后需要告知外界。

    /**
     * 外层容器的接口,第一个泛型就是底部导航栏中的每个条目,第二个泛型是每个条目的数据
     *
     * @author 裴云飞
     * @date 2020/12/22
     */
    public interface IBarLayout<Bar extends ComponentContainer, D> {
    
        /**
         * 根据数据查找条目
         *
         * @param data 数据
         * @return 条目
         */
        Bar findBar(D data);
    
        /**
         * 添加监听
         *
         * @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、再定义一个单个条目的接口,泛型就是每个条目的数据,接口里面定义方法,可以设置条目的数据,可以动态修改某个条目的大小

    /**
     * 单个条目的接口
     *
     * @author 裴云飞
     * @date 2020/12/22
     */
    public interface IBar<D> extends IBarLayout.OnBarSelectedListener<D> {
    
        /**
         * 设置条目的数据
         *
         * @param data
         */
        void setBarInfo(D data);
    
        /**
         * 动态修改某个条目的大小
         *
         * @param height
         */
        void resetHeight(int height);
    }
    

    三、每个条目所对应的实体类

    每个条目都有自己的图片、文字、文字的颜色,我们把这些属性定义在一个实体类中。由于颜色可以是整型,也可以是字符串,这里定义泛型,泛型就是文字的颜色。具体是哪种类型的颜色,由调用者来决定。
    注意下BarType这个枚举,我们的底部导航栏支持两种类型,IMAGE代表下图,某个条目只显示图片,也可以让某个条目凸出来,只需要将条目的高度变高即可。

    image.png

    IMAGE_TEXT代表条目显示图片和文字。如下图


    image.png
    /**
     * 每个条目的数据,泛型对应的是文字的颜色,颜色可以是整型,也可以是字符串,具体是哪种类型的颜色,由调用者来决定
     *
     * @author 裴云飞
     * @date 2020/12/22
     */
    public class BottomBarInfo<Color> {
    
        public enum BarType {
            /**
             * 显示图片和文案
             */
            IMAGE_TEXT,
            /**
             * 只显示图片
             */
            IMAGE
        }
    
        /**
         * 默认的图片
         */
        public int defaultImage;
        /**
         * 选中后的图片
         */
        public int selectedImage;
        /**
         * 默认的文字颜色
         */
        public Color defaultColor;
        /**
         * 选中后的文字颜色
         */
        public Color tintColor;
        /**
         * 条目的名称
         */
        public String name;
        public BarType tabType;
    
        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;
        }
    }
    

    四、单个条目的布局

    1、在布局文件实现单个条目的布局,很简单,使用相对布局,文字位于屏幕的底部,图片位于文字的上面。

    <?xml version="1.0" encoding="utf-8"?>
    <DependentLayout
        xmlns:ohos="http://schemas.huawei.com/res/ohos"
        ohos:height="match_parent"
        ohos:orientation="vertical"
        ohos:width="match_parent">
    
        <Image
            ohos:top_margin="4vp"
            ohos:bottom_margin="4vp"
            ohos:height="match_content"
            ohos:horizontal_center="1"
            ohos:id="$+id:image"
            ohos:above="$id:name"
            ohos:width="match_content"/>
    
        <Text
            ohos:id="$+id:name"
            ohos:align_parent_bottom="1"
            ohos:height="match_content"
            ohos:text="首页"
            ohos:text_size="10vp"
            ohos:bottom_margin="5vp"
            ohos:width="match_content"
            ohos:horizontal_center="1"/>
    </DependentLayout>
    

    五、单个条目的封装

    1、定义BottomBar,继承相对布局,实现之前定义的IBar接口,泛型就是每个条目所对应的实体类,由于目前并不知道泛型的具体类型,所以泛型直接使用问号来代替。BottomBar就是单个条目。

    /**
     * 单个条目的封装,由于目前并不知道泛型的具体类型,所以泛型直接使用问号来代替
     *
     * @author 裴云飞
     * @date 2020/12/22
     */
    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_tab_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 = data;
            inflateInfo(false, true);
        }
    
        /**
         * 初始化条目
         *
         * @param selected true 选中
         * @param init true 初始化
         */
        private void inflateInfo(boolean selected, boolean init) {
            if (tabInfo.tabType == BottomBarInfo.TabType.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.TabType.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.TabType.IMAGE) {
                return;
            }
            if (preInfo == nextInfo) {
                // 假设当前选中的是tab1,同时点击的也是tab1,那就不需要做任何操作了
                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;
        }
    }
    
    • 构造方法
      重写三个构造方法,一个参数的构造方法调用两个参数构造方法,两个参数的构造方法调用三个参数的构造方法。一般我们手动创建对象的时候会调用一个参数的构造方法。如果在布局文件中使用,系统会使用反射来调用两个参数的方法。如果还定义了样式,就需要定义三个参数的构造方法。
      在三个参数的构造方法里面使用LayoutScatter来把布局文件转换成组件对象,需要注意的是,LayoutScatter的parse方法有三个参数,第一个是布局文件的id,第二个是父组件,第三个是布尔值。第一个参数是布局文件的id,系统首先会解析布局文件,使用反射来将布局里面的组件创建出来,解析完成后就会返回一个component对象,第二个和第三个参数是结合在一起使用,如果第二个参数不为空并且第三个参数为true,系统就会把刚刚返回的component对象放入到父组件中,最后将父组件返回。如果第二个参数为空或者第三个参数为false,系统就不会把component对象放入到父组件中,仅仅是将component对象返回。
      我们需要将component对象放入到BottomBar中,所以第二个参数传this,第三个参数为true。
    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_tab_bottom, this, true);
        mTabImage = (Image) component.findComponentById(ResourceTable.Id_image);
        mTabName = (Text) component.findComponentById(ResourceTable.Id_name);
        mTabImage.setScaleMode(Image.ScaleMode.INSIDE);
    }
    
    • setBarInfo
      setBarInfo方法由外界调用,在这个方法完成条目的初始化,根据不同类型来初始化。设置选中和未选中的图片以及文字的颜色。
    /**
     * 设置条目的数据
     *
     * @param data
     */
    @Override
    public void setBarInfo(BottomBarInfo<?> data) {
        tabInfo = data;
        inflateInfo(false, true);
    }
    
    • resetHeight
      该方法用于修改某个条目的高度,获取布局配置对象,将高度修改为外界传入的高度。
        /**
         * 动态修改某个tab的高度
         *
         * @param height tab的高度
         */
        @Override
        public void resetHeight(int height) {
            ComponentContainer.LayoutConfig config = getLayoutConfig();
            config.height = height;
            setLayoutConfig(config);
            mTabName.setVisibility(HIDE);
        }
    
    • onBarSelectedChange
      当某个条目被点击的时候,该方法会回调,该方法会被回调多次。这里主要是将点击前的条目反选,将点击后的条目选中。
        /**
         * 当某个条目被选中后的回调,该方法会被调用多次
         *
         * @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);
            }
        }
    

    六、底部导航栏的封装。

    定义BottomNavigationBar,继承栈布局。第一个泛型就是底部导航栏的条目,第二个泛型就是每个条目的数据

    /**
     * 底部导航栏的封装,第一个泛型就是底部导航栏的条目,第二个泛型就是每个条目的数据
     *
     * @author 裴云飞
     * @date 2020/12/20
     */
    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();
            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);
        }
    
        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
      该方法由外界调用,外界将所有的条目信息传递过来,我们将条目添加到底部导航栏。首先移除之前已经添加的组件,防止重复添加,然后添加背景,添加条目,添加线条。
        private void removeComponent() {
            for (int i = getChildCount() - 1; i > 0; i--) {
                removeComponentAt(i);
            }
            tabSelectedListeners.removeIf(listener ->
                    listener instanceof BottomBar);
        }
    移除之前已经添加的组件,防止重复添加
    
        /**
         * 添加背景
         */
        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);
        }
    目前还不能直接设置背景颜色,只能通过Element来设置背景。在添加组件之前,需要创建布局配置对象,布局配置对象里面有组件的宽度、高度、对齐方式等。
    
        /**
         * 添加条目
         */
        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;
            }
        }
    

    七、外界调用

    至此,底部导航栏已经封装完成,外界如何使用呢?
    1、在布局文件中添加BottomNavigationBar,这个就是我们封装好的底部导航栏

    <?xml version="1.0" encoding="utf-8"?>
    <com.pyf.ui.bar.bottom.BottomNavigationBar
        xmlns:ohos="http://schemas.huawei.com/res/ohos"
        ohos:id="$+id:bottom_navigationBar"
        ohos:height="match_parent"
        ohos:orientation="vertical"
        ohos:width="match_parent">
    
    </com.pyf.ui.bar.bottom.BottomNavigationBar>
    

    2、在AlibitySlice里面使用,这里使用MVP模式,将业务逻辑放入presenter层中。
    先定义接口

    /**
     * @author 裴云飞
     * @date 2020/12/22
     */
    /**
     * AbilitySlice实现该接口
     *
     * @author 裴云飞
     * @date 2020/12/22
     */
    public interface AbilitySliceProvider {
    
        /**
         * 系统已有的方法,不需要单独实现
         *
         * @param id
         * @return
         */
        Component findComponentById(int id);
    
        /**
         * 系统已有的方法,不需要单独实现
         *
         * @return
         */
        ResourceManager getResourceManager();
    
        /**
         * 获取string.json文件中定义的字符串
         *
         * @param resId
         * @return
         */
        String getString(int resId);
    
        /**
         * 获取color.json文件中定义的颜色值
         *
         * @param colorId
         * @return
         */
        int getColor(int colorId);
    
        /**
         * 获取上下文,系统已有的方法,不需要单独实现
         *
         * @return
         */
        Context getContext();
    }
    

    3、MainAbilitySlice实现AbilitySliceProvider接口,getContext、getResourceManager、findComponentById这三个方法AbilitySlice的父类已经帮我们实现了,这些方法其实就是系统的方法。getString和getColor需要我们自己来实现,我们在BaseAbilitySlice中手动实现这两个方法。在MainAbilitySlice的onStart方法中创建MainAbilitySlicePresenter对象,这样就能在MainAbilitySlicePresenter处理具体的业务逻辑。

    public class MainAbilitySlice extends BaseAbilitySlice implements AbilitySliceProvider {
    
        @Override
        public void onStart(Intent intent) {
            super.onStart(intent);
            super.setUIContent(ResourceTable.Layout_ability_main);
            // 使用MVP模式,将业务逻辑放到presenter中
            new MainAbilitySlicePresenter(this);
        }
    
        @Override
        public void onActive() {
            super.onActive();
        }
    
        @Override
        public void onForeground(Intent intent) {
            super.onForeground(intent);
        }
    
    }
    

    4、MainAbilitySlicePresenterr处理具体的业务逻辑

    /**
     * 业务逻辑放到这个类来做
     *
     * @author 裴云飞
     * @date 2020/12/22
     */
    public class MainAbilitySlicePresenter {
    
        private AbilitySliceProvider mAbilitySliceProvider;
        private List<BottomBarInfo<?>> infoList;
        private BottomNavigationBar tabBottomLayout;
        private Context mContext;
        public MainAbilitySlicePresenter(AbilitySliceProvider abilitySliceProvider) {
            mAbilitySliceProvider = abilitySliceProvider;
            mContext = abilitySliceProvider.getContext();
            initBottom();
        }
    
        private void initBottom() {
            tabBottomLayout = (BottomNavigationBar) mAbilitySliceProvider.findComponentById(ResourceTable.Id_bottom_navigationBar);
            infoList = new ArrayList<>();
            // 获取color.json文件中定义的颜色值
            int defaultColor = mAbilitySliceProvider.getColor(ResourceTable.Color_default_color);
            int tintColor = mAbilitySliceProvider.getColor(ResourceTable.Color_tint_color);
            // 获取string.json文件中定义的字符串
            String home = mAbilitySliceProvider.getString(ResourceTable.String_home);
            String favorite = mAbilitySliceProvider.getString(ResourceTable.String_favorite);
            String category = mAbilitySliceProvider.getString(ResourceTable.String_category);
            String recommend = mAbilitySliceProvider.getString(ResourceTable.String_recommend);
            String profile = mAbilitySliceProvider.getString(ResourceTable.String_profile);
            // 首页
            BottomBarInfo<Integer> honeInfo = new BottomBarInfo<>(home,
                    ResourceTable.Media_home_normal,
                    ResourceTable.Media_home_selected,
                    defaultColor, tintColor);
            // 收藏
            BottomBarInfo<Integer> favoriteInfo = new BottomBarInfo<>(favorite,
                    ResourceTable.Media_favorite_normal,
                    ResourceTable.Media_favorite_selected,
                    defaultColor, tintColor);
            // 分类
            BottomBarInfo<Integer> categoryInfo = new BottomBarInfo<>(category,
                    ResourceTable.Media_category_normal,
                    ResourceTable.Media_category_selected,
                    defaultColor, tintColor);
            // 推荐
            BottomBarInfo<Integer> recommendInfo = new BottomBarInfo<>(recommend,
                    ResourceTable.Media_recommend_normal,
                    ResourceTable.Media_recommend_selected,
                    defaultColor, tintColor);
            // 我的
            BottomBarInfo<Integer> profileInfo = new BottomBarInfo<>(profile,
                    ResourceTable.Media_profile_normal,
                    ResourceTable.Media_profile_selected,
                    defaultColor, tintColor);
            // 将每个条目的数据放入到集合
            infoList.add(honeInfo);
            infoList.add(favoriteInfo);
            infoList.add(categoryInfo);
            infoList.add(recommendInfo);
            infoList.add(profileInfo);
            // 设置底部导航栏的透明度
            tabBottomLayout.setBarBottomAlpha(0.85f);
            // 初始化所有的条目
            tabBottomLayout.initInfo(infoList);
            tabBottomLayout.addBarSelectedChangeListener((index, prevInfo, nextInfo) ->
                            // 点击条目后给出提示信息
                            Toast.show(mContext, nextInfo.name));
            // 设置默认选中的条目,该方法一定要在最后调用
            tabBottomLayout.defaultSelected(honeInfo);
            // 如果想让某个条目凸出来,可以按照下面的方式
            // 创建凸出来的条目,这张凸出来的图片肯定是要比不凸出来的图片大点
    //        BottomBarInfo<Integer> message = new BottomBarInfo<>(recommend,
    //                ResourceTable.Media_recommend_normal,
    //                ResourceTable.Media_recommend_selected);
    //        // 将条目添加集合
    //        infoList.add(message);
    //        // 在底部导航栏中查找该条目
    //        BottomBar bottomBar = tabBottomLayout.findBar(message);
    //        // 修改该条目的高度
    //        bottomBar.resetHeight(DisplayUtils.vp2px(mContext, 66));
        }
    }
    

    5、如果想让某个条目凸出来,可以按照下面的方式

            // 创建凸出来的条目,这张凸出来的图片肯定是要比不凸出来的图片大点
            BottomBarInfo<Integer> message = new BottomBarInfo<>(recommend,
                    ResourceTable.Media_recommend_normal,
                    ResourceTable.Media_recommend_normal);
            // 将条目添加集合
            infoList.add(message);
            // 在底部导航栏中查找该条目
            BottomBar bottomBar = tabBottomLayout.findBar(message);
            // 修改该条目的高度
            bottomBar.resetHeight(DisplayUtils.vp2px(mContext, 66));
    

    最后附上源码
    注:项目用到的图片全部来源于阿里矢量图库,如果缺少图片,可以阿里矢量图里面找图片。

    相关文章

      网友评论

          本文标题:鸿蒙系统—打造通用的底部导航栏

          本文链接:https://www.haomeiwen.com/subject/sxxenktx.html