大家好,我是你们的朋友 朋哥,今天开始朋哥开始研究鸿蒙了,定时会写一些文章分享给大家,希望多多提意见。
上一篇原创文章 解读了 鸿蒙开发布局的 自适应盒子布局(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