HarmonyOS学习路之开发篇—Java UI框架(自定义组件与布局 二)

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

自定义布局

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

常用接口

Component类相关接口

image.png

ComponentContainer类相关接口


接口名称


作用


setArrangeListener


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


onArrange


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


如何实现自定义布局

使用自定义布局,实现子组件自动换行功能。


自定义布局的使用效果

18e219ab98969f89be49450491e68103.png 

1. 创建自定义布局的类,并继承ComponentContainer,添加构造方法。

public class CustomLayout extends ComponentContainer {
    public CustomLayout(Context context) {
        this(context, null);
    }
    //如需支持xml创建自定义布局,必须添加该构造方法
    public CustomLayout(Context context, AttrSet attrSet) {
        super(context, attrSet);
    }
}

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

public class CustomLayout extends ComponentContainer
    implements ComponentContainer.EstimateSizeListener {
    ...
    public CustomLayout(Context context, AttrSet attrSet) {
        ...
        setEstimateSizeListener(this);
    }
    @Override
    public boolean onEstimateSize(int widthEstimatedConfig, int heightEstimatedConfig) {
        invalidateValues();
        //通知子组件进行测量
        measureChildren(widthEstimatedConfig, heightEstimatedConfig);
        //关联子组件的索引与其布局数据
        for (int idx = 0; idx < getChildCount(); idx++) {
            Component childView = getComponentAt(idx);
            addChild(childView, idx, EstimateSpec.getSize(widthEstimatedConfig));
        }
        //测量自身
        measureSelf(widthEstimatedConfig, heightEstimatedConfig);
        return true;
    }
    private void measureChildren(int widthEstimatedConfig, int heightEstimatedConfig) {
        for (int idx = 0; idx < getChildCount(); idx++) {
            Component childView = getComponentAt(idx);
            if (childView != null) {
                LayoutConfig lc = childView.getLayoutConfig();
                int childWidthMeasureSpec;
                int childHeightMeasureSpec;
                if (lc.width == LayoutConfig.MATCH_CONTENT) {
                    childWidthMeasureSpec = EstimateSpec.getSizeWithMode(lc.width, EstimateSpec.NOT_EXCEED);
                } else if (lc.width == LayoutConfig.MATCH_PARENT) {
                    int parentWidth = EstimateSpec.getSize(widthEstimatedConfig);
                    int childWidth = parentWidth - childView.getMarginLeft() - childView.getMarginRight();
                    childWidthMeasureSpec = EstimateSpec.getSizeWithMode(childWidth, EstimateSpec.PRECISE);
                } else {
                    childWidthMeasureSpec = EstimateSpec.getSizeWithMode(lc.width, EstimateSpec.PRECISE);
                }
                if (lc.height == LayoutConfig.MATCH_CONTENT) {
                    childHeightMeasureSpec = EstimateSpec.getSizeWithMode(lc.height, EstimateSpec.NOT_EXCEED);
                } else if (lc.height == LayoutConfig.MATCH_PARENT) {
                    int parentHeight = EstimateSpec.getSize(heightEstimatedConfig);
                    int childHeight = parentHeight - childView.getMarginTop() - childView.getMarginBottom();
                    childHeightMeasureSpec = EstimateSpec.getSizeWithMode(childHeight, EstimateSpec.PRECISE);
                } else {
                    childHeightMeasureSpec = EstimateSpec.getSizeWithMode(lc.height, EstimateSpec.PRECISE);
                }
                childView.estimateSize(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }
    private void measureSelf(int widthEstimatedConfig, int heightEstimatedConfig) {
        int widthSpce = EstimateSpec.getMode(widthEstimatedConfig);
        int heightSpce = EstimateSpec.getMode(heightEstimatedConfig);
        int widthConfig = 0;
        switch (widthSpce) {
            case EstimateSpec.UNCONSTRAINT:
            case EstimateSpec.PRECISE:
                int width = EstimateSpec.getSize(widthEstimatedConfig);
                widthConfig = EstimateSpec.getSizeWithMode(width, EstimateSpec.PRECISE);
                break;
            case EstimateSpec.NOT_EXCEED:
                widthConfig = EstimateSpec.getSizeWithMode(maxWidth, EstimateSpec.PRECISE);
                break;
            default:
                break;
        }
        int heightConfig = 0;
        switch (heightSpce) {
            case EstimateSpec.UNCONSTRAINT:
            case EstimateSpec.PRECISE:
                int height = EstimateSpec.getSize(heightEstimatedConfig);
                heightConfig = EstimateSpec.getSizeWithMode(height, EstimateSpec.PRECISE);
                break;
            case EstimateSpec.NOT_EXCEED:
                heightConfig = EstimateSpec.getSizeWithMode(maxHeight, EstimateSpec.PRECISE);
                break;
            default:
                break;
        }
        setEstimatedSize(widthConfig, heightConfig);
    }
}

注意:

  1. 1.容器类组件在自定义测量过程不仅要测量自身,也要递归的通知各子组件进行测量。
  2. 2.测量出的大小需通过setEstimatedSize通知组件,并且必须返回true使测量值生效。

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

    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 + component.getMarginRight());
        maxHeight = Math.max(maxHeight, layout.positionY + layout.height + component.getMarginBottom());
    }

4. 实现ComponentContainer.ArrangeListener接口,在onArrange方法中排列子组件。

public class CustomLayout extends ComponentContainer
    implements ComponentContainer.EstimateSizeListener,
    ComponentContainer.ArrangeListener {
    ...
    public CustomLayout(Context context
, AttrSet attrSet
) {
        ...
        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;
    }
}

5. 在xml文件中创建此布局,并添加若干子组件。

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:orientation="vertical">
    <!--请根据实际包名与文件路径引入-->
    <com.huawei.harmonyosdemo.custom.CustomLayout
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:background_element="#555555">
        <Text
            ohos:height="200"
            ohos:width="match_parent"
            ohos:background_element="#727272"
            ohos:margin="10"
            ohos:text="match_parent * 200"
            ohos:text_alignment="center"
            ohos:text_color="white"
            ohos:text_size="40"/>
        <Text
            ohos:height="100"
            ohos:width="300"
            ohos:background_element="#727272"
            ohos:margin="10"
            ohos:text="item2"
            ohos:text_alignment="center"
            ohos:text_color="white"
            ohos:text_size="40"/>
        <Text
            ohos:height="100"
            ohos:width="300"
            ohos:background_element="#727272"
            ohos:margin="10"
            ohos:text="item3"
            ohos:text_alignment="center"
            ohos:text_color="white"
            ohos:text_size="40"/>
        <Text
            ohos:height="100"
            ohos:width="300"
            ohos:background_element="#727272"
            ohos:margin="10"
            ohos:text="item4"
            ohos:text_alignment="center"
            ohos:text_color="white"
            ohos:text_size="40"/>
        <Text
            ohos:height="100"
            ohos:width="500"
            ohos:background_element="#727272"
            ohos:margin="10"
            ohos:text="500 * 100"
            ohos:text_alignment="center"
            ohos:text_color="white"
            ohos:text_size="40"/>
        <Text
            ohos:height="100"
            ohos:width="300"
            ohos:background_element="#727272"
            ohos:margin="10"
            ohos:text="item6"
            ohos:text_alignment="center"
            ohos:text_color="white"
            ohos:text_size="40"/>
        <Text
            ohos:height="600"
            ohos:width="600"
            ohos:background_element="#727272"
            ohos:margin="10"
            ohos:text="600 * 600"
            ohos:text_alignment="center"
            ohos:text_color="white"
            ohos:text_size="40"/>
        <Text
            ohos:height="100"
            ohos:width="300"
            ohos:background_element="#727272"
            ohos:margin="10"
            ohos:text="item8"
            ohos:text_alignment="center"
            ohos:text_color="white"
            ohos:text_size="40"/>
    </com.huawei.harmonyosdemo.custom.CustomLayout>
</DirectionalLayout>
相关文章
|
18天前
|
存储 开发者 容器
鸿蒙 HarmonyOS NEXT星河版APP应用开发-ArkTS面向对象及组件化UI开发使用实例
本文介绍了ArkTS语言中的Class类、泛型、接口、模块化、自定义组件及状态管理等核心概念,并结合代码示例讲解了对象属性、构造方法、继承、静态成员、访问修饰符等内容,同时涵盖了路由管理、生命周期和Stage模型等应用开发关键知识点。
149 0
鸿蒙 HarmonyOS NEXT星河版APP应用开发-ArkTS面向对象及组件化UI开发使用实例
|
4月前
|
索引 容器
120. [HarmonyOS NEXT 实战案例:教育应用] 基础篇 - 垂直分割布局打造课程学习平台
在本案例中,我们将使用以下HarmonyOS NEXT组件: | 组件名称 | 功能描述 | | ------------- | -------------------------------------- | | `ColumnSplit` | 垂直分割布局容器,将界面分为上下两部分 | | `Column` | 垂直布局容器,用于垂直排列子组件 | | `Row` | 水平布局容器,用于水平排列子组件 | | `Scroll` | 滚动容器,用于在有限空间内展
80 3
120. [HarmonyOS NEXT 实战案例:教育应用] 基础篇 - 垂直分割布局打造课程学习平台
|
4月前
|
UED 容器
122.[HarmonyOS NEXT 实战案例:教育应用] 高级篇 - 课程学习平台的高级布局与自适应设计
在前两篇教程中,我们学习了如何使用HarmonyOS NEXT的`ColumnSplit`组件构建课程学习平台的基本布局,以及如何添加交互功能和状态管理。本篇教程将进一步深入,讲解课程学习平台的高级布局技巧和自适应设计,使应用能够在不同尺寸的设备上提供一致且优质的用户体验。
83 1
122.[HarmonyOS NEXT 实战案例:教育应用] 高级篇 - 课程学习平台的高级布局与自适应设计
|
4月前
|
定位技术 UED
70. [HarmonyOS NEXT 实战案例九] 旅游景点网格布局(下)
在上一篇教程中,我们学习了如何使用GridRow和GridCol组件实现基本的旅游景点网格布局。本篇教程将在此基础上,深入探讨如何优化布局、添加交互功能,以及实现更多高级特性,打造一个功能完善的旅游景点应用。
90 1
|
4月前
|
容器
69.[HarmonyOS NEXT 实战案例九] 旅游景点网格布局(上)
本教程将详细讲解如何使用HarmonyOS NEXT中的GridRow和GridCol组件实现旅游景点网格布局。通过网格布局,我们可以以美观、规整的方式展示各种旅游景点信息,为用户提供良好的浏览体验。
89 1
|
4月前
|
UED
68.[HarmonyOS NEXT 实战案例八] 电影票务网格布局(下)
在上一篇教程中,我们学习了如何使用GridRow和GridCol组件实现基本的电影票务网格布局。本篇教程将在此基础上,深入探讨如何优化布局、添加交互功能,以及实现更多高级特性,打造一个功能完善的电影票务应用。
84 1
|
4月前
|
开发者 容器
67.[HarmonyOS NEXT 实战案例八] 电影票务网格布局(上)
在移动应用开发中,电影票务应用是一个常见的场景,用户可以通过应用查看正在热映的电影信息,并进行选座购票等操作。本教程将详细讲解如何使用HarmonyOS NEXT的GridRow和GridCol组件实现电影票务应用中的电影列表网格布局,帮助开发者掌握网格布局的基本用法和实现技巧。
99 1
|
4月前
|
UED
66.[HarmonyOS NEXT 实战案例七] 健身课程网格布局(下)
在上一篇教程中,我们学习了如何使用GridRow和GridCol组件实现基本的健身课程网格布局。本篇教程将在此基础上,深入探讨如何优化布局、添加交互功能,以及实现更多高级特性,打造一个功能完善的健身课程应用。
84 1
|
4月前
|
设计模式 UED
65. [HarmonyOS NEXT 实战案例七] 健身课程网格布局(上)
本教程将介绍如何使用HarmonyOS NEXT的GridRow和GridCol组件实现健身课程的网格布局展示。健身课程网格布局是一种常见的UI设计模式,适用于展示各种健身课程信息,包括课程名称、教练信息、课程时长、难度级别等。通过网格布局,用户可以快速浏览多个课程,并根据自己的需求选择合适的课程。
82 1

热门文章

最新文章