鸿蒙应用开发 | 最牛逼的 自定义 布局

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

上一篇原创文章 解读了 鸿蒙开发布局的 自适应盒子布局(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
 

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

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

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

作者:码工
 

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沉默的闪客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值