自定义布局
当Java UI框架提供的布局无法满足需求时,可以创建自定义布局,根据需求自定义布局规则
常用接口
Component类相关接口
ComponentContainer类相关接口
接口名称
作用
setArrangeListener
设置容器组件布局子组件的侦听器
onArrange
通知容器组件在布局时设置子组件的位置和大小
如何实现自定义布局
使用自定义布局,实现子组件自动换行功能。
自定义布局的使用效果
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.容器类组件在自定义测量过程不仅要测量自身,也要递归的通知各子组件进行测量。
- 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>