当前位置 博文首页 > 程序员漫话编程的博客:鸿蒙应用开发 | 最牛逼的 自定义 布局

    程序员漫话编程的博客:鸿蒙应用开发 | 最牛逼的 自定义 布局

    作者:[db:作者] 时间:2021-09-18 19:02

    大家好,我是你们的朋友 朋哥,今天聊聊自定义布局,自定义布局就是当前系统布局不满足时的布局方式。

    上一篇原创文章?解读了?鸿蒙开发布局的 自适应盒子布局(AdaptiveBoxLayout),通过设置布局规则来调整布局,到目前,布局开发基本完成了。


    有点激动接下来的组件,目录已经准备好,大家跟进步伐。

    布局完成肯定要总结一下,其实布局是开发ui的关键,需要开发者做到,看到界面就能明白需要用上面布局做开发。
    ?

    为了更好的理解布局的作用,今天来开发一下自定义布局。

    简介:

    来看看官方对自定义的说明。

    HarmonyOS提供了一套复杂且强大的Java UI框架,其中ComponentContainer作为容器容纳Component或ComponentContainer对象,并对它们进行布局。

    Java UI框架也提供了一部分Component和ComponentContainer的具体子类,即常用的组件(比如:Text、Button、Image等)和常用的布局(比如:DirectionalLayout、DependentLayout等)。如果现有的组件和布局无法满足设计需求,例如仿遥控器的圆盘按钮、可滑动的环形控制器等,可以通过自定义组件和自定义布局来实现。

    自定义布局是由开发者定义的具有特定布局规则的容器类组件,通过扩展ComponentContainer或其子类实现,可以将各子组件摆放到指定的位置,也可响应用户的滑动、拖拽等事件。

    总结起来就是:当Java UI框架提供的布局无法满足设计需求时,可以创建自定义布局,根据需求自定义布局规则。

    相关接口

    Component类相关接口

    接口名称

    作用

    setEstimateSizeListener

    设置测量组件的侦听器。

    onEstimateSize

    测量组件的大小以确定宽度和高度。

    setEstimatedSize

    将测量的宽度和高度设置给组件。

    EstimateSpec.getChildSizeWithMode

    基于指定的大小和模式为子组件创建度量规范。

    EstimateSpec.getSize

    从提供的度量规范中提取大小。

    EstimateSpec.getMode

    获取该组件的显示模式。

    arrange

    相对于容器组件设置组件的位置和大小。


    ComponentContainer类相关接口

    接口名称

    作用

    setArrangeListener

    设置容器组件布局子组件的侦听器。

    onArrange

    通知容器组件在布局时设置子组件的位置和大小。

    学习开发动手敲一下代码,才能理解的更深入。
    ?

    1,创建项目

    首先创建一个鸿蒙项目(Java版),关于创建项目 有新手不知道怎么创建的,可以查看第一篇文章??鸿蒙开发入门?。

    2,创建自定义布局的类,并继承ComponentContainer

    实现ComponentContainer.EstimateSizeListener接口,在onEstimateSize方法中进行测量。

    代码如下:

    package?com.example.customlayout;
    import ohos.agp.components.Component;
    import ohos.agp.components.ComponentContainer;
    import ohos.app.Context;
    import java.util.HashMap;
    import java.util.Map;
    
    public class CustomLayout extends ComponentContainer implements ComponentContainer.EstimateSizeListener
    ,ComponentContainer.ArrangeListener{
        private int xx = 0;
    
        private int yy = 0;
    
        private int maxWidth = 0;
    
        private int maxHeight = 0;
    
        private int lastHeight = 0;
    
        // 子组件索引与其布局数据的集合
        private final Map<Integer, Layout> axis = new HashMap<>();
    
        public CustomLayout(Context context) {
            super(context);
            setEstimateSizeListener(this);// 设置监听
            setArrangeListener(this);// 设置子组件排列监听
        }
        private static class Layout {
            int positionX = 0;
            int positionY = 0;
            int width = 0;
            int height = 0;
        }
    
        private void invalidateValues() {
            xx = 0;
            yy = 0;
            maxWidth = 0;
            maxHeight = 0;
            axis.clear();
        }
    
        // 实现ComponentContainer.EstimateSizeListener接口,在onEstimateSize方法中进行测量
        @Override
        public boolean onEstimateSize(int widthEstimatedConfig, int heightEstimatedConfig) {
    
            // 通知子组件进行测量
            measureChildren(widthEstimatedConfig, heightEstimatedConfig);
            int width = Component.EstimateSpec.getSize(widthEstimatedConfig);
    
            // 关联子组件的索引与其布局数据
            for (int idx = 0; idx < getChildCount(); idx++) {
                Component childView = getComponentAt(idx);
                addChild(childView, idx, width);
            }
    
            setEstimatedSize(
                    Component.EstimateSpec.getChildSizeWithMode(maxWidth, widthEstimatedConfig, 0),
                    Component.EstimateSpec.getChildSizeWithMode(maxHeight, heightEstimatedConfig, 0));
            return true;
        }
    
        private void measureChildren(int widthEstimatedConfig, int heightEstimatedConfig) {
            for (int idx = 0; idx < getChildCount(); idx++) {
                Component childView = getComponentAt(idx);
                if (childView != null) {
                    measureChild(childView, widthEstimatedConfig, heightEstimatedConfig);
                }
            }
        }
    
        private void measureChild(Component child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
            ComponentContainer.LayoutConfig lc = child.getLayoutConfig();
            int childWidthMeasureSpec = EstimateSpec.getChildSizeWithMode(
                    lc.width, parentWidthMeasureSpec, EstimateSpec.UNCONSTRAINT);
            int childHeightMeasureSpec = EstimateSpec.getChildSizeWithMode(
                    lc.height, parentHeightMeasureSpec, EstimateSpec.UNCONSTRAINT);
            child.estimateSize(childWidthMeasureSpec, childHeightMeasureSpec);
    ????}
        // 测量时,需要确定每个子组件大小和位置的数据,并保存这些数据
        private void addChild(Component component, int id, int layoutWidth) {
            Layout layout = new Layout();
            layout.positionX = xx + component.getMarginLeft();
            layout.positionY = yy + component.getMarginTop();
            layout.width = component.getEstimatedWidth();
            layout.height = component.getEstimatedHeight();
            if ((xx + layout.width) > layoutWidth) {
                xx = 0;
                yy += lastHeight;
                lastHeight = 0;
                layout.positionX = xx + component.getMarginLeft();
                layout.positionY = yy + component.getMarginTop();
            }
            axis.put(id, layout);
            lastHeight = Math.max(lastHeight, layout.height + component.getMarginBottom());
            xx += layout.width + component.getMarginRight();
            maxWidth = Math.max(maxWidth, layout.positionX + layout.width);
            maxHeight = Math.max(maxHeight, layout.positionY + layout.height);
    ????}
        // 实现ComponentContainer.ArrangeListener接口,在onArrange方法中排列子组件
        @Override
        public boolean onArrange(int left, int top, int width, int height) {
    
            // 对各个子组件进行布局
            for (int idx = 0; idx < getChildCount(); idx++) {
                Component childView = getComponentAt(idx);
                Layout layout = axis.get(idx);
                if (layout != null) {
                    childView.arrange(layout.positionX, layout.positionY, layout.width, layout.height);
                }
            }
            return true;
        }
    }

    ??????
    说明:
    1,实现ComponentContainer.EstimateSizeListener接口,在onEstimateSize方法中进行测量

    2,注意事项:

    容器类组件在自定义测量过程不仅要测量自身,也要递归的通知各子组件进行测量。

    测量出的大小需通过setEstimatedSize设置给组件,并且必须返回true使测量值生效。

    测量时,需要确定每个子组件大小和位置的数据,并保存这些数据

    3,实现ComponentContainer.ArrangeListener接口,在onArrange方法中排列子组件
    4,自定义布局功能就是实现组件的自适应显示,按照线性布局排列。
    ?

    3,自定义布局的使用

    package com.example.customlayout;
    
    import ohos.aafwk.ability.Ability;
    import ohos.aafwk.content.Intent;
    import ohos.agp.colors.RgbColor;
    import ohos.agp.components.Button;
    import ohos.agp.components.Component;
    import ohos.agp.components.DirectionalLayout;
    import ohos.agp.components.element.ShapeElement;
    import ohos.agp.utils.Color;
    
    public class MainAbility extends Ability {
        @Override
        public void onStart(Intent intent) {
            DirectionalLayout myLayout = new DirectionalLayout(getContext());
            CustomLayout customLayout = new CustomLayout(this);// 自定义布局 设置线性布局,多个组件的宽度大于屏幕的宽度会自动换行显示
            for (int idx = 0; idx < 6; idx++) {
                customLayout.addComponent(getComponent(idx + 1));
            }
    ????????// 自定义布局背景颜色
            ShapeElement shapeElement = new ShapeElement();
            shapeElement.setRgbColor(RgbColor.fromArgbInt(Color.getIntColor("#4169E1")));
            customLayout.setBackground(shapeElement);
    ????????myLayout.addComponent(customLayout);?//自定义布局添加到布局DirectionalLayout中
    ????????super.setUIContent(myLayout);//?将布局添加到?根布局中显示
        }
    
        //创建动态子组件
        private Component getComponent(int idx) {
            Button button = new Button(getContext()); // 创建个按钮
    ????????//?设置按钮颜色
            ShapeElement shapeElement = new ShapeElement();
            shapeElement.setRgbColor(RgbColor.fromArgbInt(Color.getIntColor("#FF1493")));
            button.setBackground(shapeElement); 
            button.setTextColor(Color.WHITE);// 按钮字体颜色
            button.setTextSize(40); // 设置字体大小
    ????????//?设置每个按钮布局,包括大小和内容
           DirectionalLayout.LayoutConfig layoutConfig = new DirectionalLayout.LayoutConfig(300, 100);
            if (idx == 1) {
                layoutConfig = new DirectionalLayout.LayoutConfig(1080, 200);
                button.setText("自定义组件1");
            } else if (idx == 2) {
                layoutConfig = new DirectionalLayout.LayoutConfig(500, 200);
                button.setText("自定义组件2");
            } else if (idx == 3) {
                layoutConfig = new DirectionalLayout.LayoutConfig(400, 400);
                button.setText("自定义组件3");
            }  else if (idx == 4) {
                layoutConfig = new DirectionalLayout.LayoutConfig(50, 200);
                button.setText("自定义组件4");
            }  else if (idx == 5) {
                layoutConfig = new DirectionalLayout.LayoutConfig(100, 200);
                button.setText("自定义组件5");
            } else {
                button.setText("自定义组件6");
            }
            layoutConfig.setMargins(10, 10, 10, 10);
            button.setLayoutConfig(layoutConfig);
            return button;
        }
    }

    1,在 MainAbility中实现自定义布局的调用,并且创建动态组件,显示组件的大小和样式。

    自定义布局是因为当前布局不能满足需要或者为了统一布局?从新做的布局规则。
    需要重写?ComponentContainer?实现需要的接口完成布局的设置。

    运行效果图:
    ?

    图片
    ?

    老规矩 代码不能少,要不然小伙伴该说我小气了。
    代码连接:?https://gitee.com/codegrowth/haomony-develop.git
    ?

    关注公众号【程序员漫话编程】,后台回复【鸿蒙】,即可获取上千鸿蒙开源组件~

    原创不易,有用就关注一下。要是帮到了你 就给个三连吧,多谢支持。
    ?

    觉得不错的小伙伴,记得帮我?点个赞和关注哟,笔芯笔芯~**

    作者:码工
    ?

    有问题请留言或者私信,可以 微信搜索:程序员漫话编程,关注公众号获得更多免费学习资料。

    cs