4.10【HarmonyOS鸿蒙开发】自定义布局

作者:韩茹

公司:程序咖(北京)科技有限公司

鸿蒙巴士专栏作家

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

一、常用接口

Component类相关接口

接口名称作用
setEstimateSizeListener设置测量组件的侦听器。
onEstimateSize测量组件的大小以确定宽度和高度。
setEstimatedSize将测量的宽度和高度设置给组件。
EstimateSpec.getChildSizeWithMode基于指定的大小和模式为子组件创建度量规范。
EstimateSpec.getSize从提供的度量规范中提取大小。
EstimateSpec.getMode获取该组件的显示模式。
arrange相对于容器组件设置组件的位置和大小。

ComponentContainer类相关接口

接口名称作用
setArrangeListener设置容器组件布局子组件的侦听器。
onArrange通知容器组件在布局时设置子组件的位置和大小。

二、如何实现自定义布局

使用自定义布局,将各子组件摆放到指定的位置。

  1. 创建自定义布局的类,并继承ComponentContainer,添加构造方法。
public class CustomLayout extends ComponentContainer {
    public CustomLayout(Context context) {
        super(context);
    }
}
  1. 实现ComponentContainer.EstimateSizeListener接口,在onEstimateSize方法中进行测量。
public class CustomLayout extends ComponentContainer
    implements ComponentContainer.EstimateSizeListener {

    ...

    public CustomLayout(Context context) {

        ...
        setEstimateSizeListener(this);
    }

    @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);
    }
}
  • 注意事项

    1. 容器类组件在自定义测量过程不仅要测量自身,也要递归的通知各子组件进行测量。
    2. 测量出的大小需通过setEstimatedSize设置给组件,并且必须返回true使测量值生效。
  1. 测量时,需要确定每个子组件大小和位置的数据,并保存这些数据。
    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<>();

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

    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);
    }
  1. 实现ComponentContainer.ArrangeListener接口,在onArrange方法中排列子组件。
public class CustomLayout extends ComponentContainer
    implements ComponentContainer.EstimateSizeListener,
    ComponentContainer.ArrangeListener {

    ...
    
    public CustomLayout(Context context) {

        ...
        setArrangeListener(this);
    }
    @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. 在onStart方法中添加此布局,在布局中添加若干子组件,并在界面中显示。
package com.example.hanrucustomlayout.slice;

import ohos.aafwk.ability.AbilitySlice;
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 MainAbilitySlice extends AbilitySlice {


    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
//        super.setUIContent(ResourceTable.Layout_ability_main);

        DirectionalLayout myLayout= new DirectionalLayout(getContext());
        DirectionalLayout.LayoutConfig config = new DirectionalLayout.LayoutConfig(
                DirectionalLayout.LayoutConfig.MATCH_PARENT, DirectionalLayout.LayoutConfig.MATCH_PARENT);
        myLayout.setLayoutConfig(config);
        CustomLayout customLayout = new CustomLayout(this);
        for (int idx = 0; idx < 15; idx++) {
            System.out.println("--->"+idx);
            customLayout.addComponent(getComponent(idx + 1));
        }
        ShapeElement shapeElement = new ShapeElement();
        RgbColor COLOR_LAYOUT_BG = new RgbColor(85,85,85);
        shapeElement.setRgbColor(COLOR_LAYOUT_BG);
        customLayout.setBackground(shapeElement);
        DirectionalLayout.LayoutConfig layoutConfig = new DirectionalLayout.LayoutConfig(DirectionalLayout.LayoutConfig.MATCH_PARENT,
                DirectionalLayout.LayoutConfig.MATCH_CONTENT);
        customLayout.setLayoutConfig(layoutConfig);
        myLayout.addComponent(customLayout);
        super.setUIContent(myLayout);
    }
    //创建子组件
    private Component getComponent(int idx) {
        Button button = new Button(getContext());
        ShapeElement shapeElement = new ShapeElement();
        RgbColor COLOR_BTN_BG = new RgbColor(114,114,114);
        shapeElement.setRgbColor(COLOR_BTN_BG);
        button.setBackground(shapeElement);
        button.setTextColor(Color.WHITE);
        DirectionalLayout.LayoutConfig layoutConfig = new DirectionalLayout.LayoutConfig(300, 100);
        if (idx == 1) {
            layoutConfig = new DirectionalLayout.LayoutConfig(1080, 200);
            button.setText("1080 * 200");
        } else if (idx == 6) {
            layoutConfig = new DirectionalLayout.LayoutConfig(500, 100);
            button.setText("500 * 100");
        } else if (idx == 8) {
            layoutConfig = new DirectionalLayout.LayoutConfig(600, 600);
            button.setText("600 * 600");
        } else {
            button.setText("Item" + idx);
        }
        layoutConfig.setMargins(10, 10, 10, 10);
        button.setLayoutConfig(layoutConfig);
        return button;
    }
}

效果图:

WX20210702-175926@2x

更多内容:

1、社区:鸿蒙巴士https://www.harmonybus.net/

2、公众号:HarmonyBus

3、技术交流QQ群:714518656

4、视频课:https://www.chengxuka.com


茹茹
10 声望20 粉丝

嗯嗯,还好。。