JAVA自定义布局 原创 精华
介绍
自定义布局有两种:继承原有布局拓展、自我设置布局。前一种比较简单,这篇文章主要讲的是自我设置布局。这个设置的是一个靠左纵向展示子组件的布局,比较特殊的是如果父布局有可展示空间,子组件的高度比自身高度高,布局本身会使用子组件的高度。
实现步骤
1、继承布局类,实现构造方法
2、测量布局,这里不仅要对容器组件进行测量,还需要对其子组件进行测量
3、根据测量结果进行子组件位置排放
布局基类继承
继承布局的基类ComponentContainer,这里如果有自定义属性的设置,参照上一篇文章“JAVA自定义组件”中的自定义属性,这里不做描述。具体代码如下:
public LinearLayout(Context context) {
super(context,null);
}
public LinearLayout(Context context, AttrSet attrSet) {
super(context, attrSet);
setEstimateSizeListener(this);
setArrangeListener(this);
}
布局及其子组件测量
这一步我们需要弄懂三种测试模式,不同测量模式,测量结果也不同:
1、UNCONSTRAINT,父组件对子组件没有约束,表示子组件可以任意大小。例如,带滚动条的父布局,组件设置多大就是多大。
2、PRECISE,父组件已确定子组件的大小。例如,组件设置match_parent。
3、NOT_EXCEED,已为子组件确定了最大大小,子组件不能超过指定大小。例如,组件设置match_content,此时使用组件默认大小。
我们需要继承Component.EstimateSizeListener接口,实现onEstimateSize方法来完成测量,并把测量结果使用setEstimatedSize对组件进行设置。
1、获取xml配置的宽高、模式。
2、对子组件进行测量,将子组件测量结果使用setEstimatedSize对组件进行设置。
3、将子组件的测试结果记录下来,用于后面的位置排放。
4、根据子组件测试结果,改变布局组件的xml配置,并使用setEstimatedSize对布局组件进行设置。
具体代码如下:
public boolean onEstimateSize(int widthEstimateSize, int heightEstimateSize) {
//获取xml配置的宽高、模式
width = EstimateSpec.getSize(widthEstimateSize);
height = EstimateSpec.getSize(heightEstimateSize);
int widthMode = EstimateSpec.getMode(widthEstimateSize);
int heightMode = EstimateSpec.getMode(heightEstimateSize);
//对子组件进行测量,将子组件测量结果使用setEstimatedSize对组件进行设置
estimateSizeChildren();
//将子组件的测试结果记录下来,用于后面的位置排放
addView();
//根据子组件测试结果,改变布局组件的xml配置
if(heightMode == EstimateSpec.NOT_EXCEED || height<childrenHeight){
height = childrenHeight;
}
setEstimatedSize(EstimateSpec.getSizeWithMode(width,widthMode),EstimateSpec.getSizeWithMode(height,heightMode));
return true;
}
子组件位置排放
我们需要继承ComponentContainer.ArrangeListener接口,实现onArrange方法来完成子组件位置排放。这里使用了上面测量的结果childrenView(子组件位置、宽高信息)来排放子组件的位置。
具体代码如下:
public boolean onArrange(int i, int i1, int i2, int i3) {
for(int count=0;count<getChildCount();count++){
Component component = getComponentAt(count);
Lay lay = childrenView.get(count);
if(component != null && lay != null) {
component.arrange(lay.x,lay.y,lay.width,lay.height);
}
}
return true;
}
总结
实现自定义布局,测量组件及其子组件宽高、根据测量宽高来排放子组件的位置这两步,均需要在构造函数添加其监听。
完整的java代码如下:
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.app.Context;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import java.util.ArrayList;
import java.util.List;
public class LinearLayout extends ComponentContainer implements Component.EstimateSizeListener,ComponentContainer.ArrangeListener {
private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "Demo");
private int width = 0;
private int height = 0;
private int heightMode = 0;
private int childrenHeight = 0;
private List<Lay> childrenView = new ArrayList<>();
private class Lay{
int x = 0;
int y = 0;
int width = 0;
int height = 0;
}
public LinearLayout(Context context) {
super(context,null);
}
public LinearLayout(Context context, AttrSet attrSet) {
super(context, attrSet);
setEstimateSizeListener(this);
setArrangeListener(this);
}
@Override
public boolean onEstimateSize(int widthEstimateSize, int heightEstimateSize) {
//获取xml配置的宽高、模式
width = EstimateSpec.getSize(widthEstimateSize);
height = EstimateSpec.getSize(heightEstimateSize);
int widthMode = EstimateSpec.getMode(widthEstimateSize);
int heightMode = EstimateSpec.getMode(heightEstimateSize);
//对子组件进行测量,将子组件测量结果使用setEstimatedSize对组件进行设置
estimateSizeChildren();
//将子组件的测试结果记录下来,用于后面的位置排放
addView();
//根据子组件测试结果,改变布局组件的xml配置
if(heightMode == EstimateSpec.NOT_EXCEED || height<childrenHeight){
height = childrenHeight;
}
setEstimatedSize(EstimateSpec.getSizeWithMode(width,widthMode),EstimateSpec.getSizeWithMode(height,heightMode));
return true;
}
private void addView(){
childrenView.clear();
childrenHeight = 0;
for(int i = 0;i < getChildCount();i++) {
Component view = getComponentAt(i);
Lay lay = new Lay();
lay.x = view.getMarginLeft();
lay.y = childrenHeight+view.getMarginTop();
lay.width = view.getEstimatedWidth();
lay.height = view.getEstimatedHeight();
childrenView.add(lay);
childrenHeight += lay.height +view.getMarginBottom()+view.getMarginTop();
}
}
public void estimateSizeChildren(){
for(int i=0;i<getChildCount();i++){
Component view = getComponentAt(i);
if(view != null ) {
int childWidth = 0;
int childHeight = 0;
LayoutConfig config = view.getLayoutConfig();
if (config.width == LayoutConfig.MATCH_PARENT) {
childWidth = EstimateSpec.getSizeWithMode(width-view.getMarginLeft()-view.getMarginRight(),EstimateSpec.PRECISE);
} else if (config.width == LayoutConfig.MATCH_CONTENT) {
childWidth = EstimateSpec.getSizeWithMode(config.width,EstimateSpec.NOT_EXCEED);
} else {
childWidth = EstimateSpec.getSizeWithMode(config.width,EstimateSpec.PRECISE);
}
if (config.height == LayoutConfig.MATCH_PARENT) {
childHeight = EstimateSpec.getSizeWithMode(height-view.getMarginTop()-view.getMarginBottom(),EstimateSpec.PRECISE);
} else if (config.height == LayoutConfig.MATCH_CONTENT) {
childHeight = EstimateSpec.getSizeWithMode(config.height,EstimateSpec.NOT_EXCEED);
} else {
childHeight = EstimateSpec.getSizeWithMode(config.height,EstimateSpec.PRECISE);
}
view.estimateSize(childWidth,childHeight);
}
}
}
@Override
public boolean onArrange(int i, int i1, int i2, int i3) {
for(int count=0;count<getChildCount();count++){
Component component = getComponentAt(count);
Lay lay = childrenView.get(count);
if(component != null && lay != null) {
component.arrange(lay.x,lay.y,lay.width,lay.height);
}
}
return true;
}
}
完整的xml代码如下:
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:orientation="vertical">
<com.example.javacustomcomponent.component.LinearLayout
ohos:height="200vp"
ohos:width="match_parent"
ohos:background_element="#d2691e">
<Text
ohos:height="100vp"
ohos:width="100vp"
ohos:text="11111111122"
ohos:text_color="#000000"
ohos:text_size="50fp"
ohos:background_element="#ffffff"></Text>
<Text
ohos:height="500vp"
ohos:width="120vp"
ohos:text="11111111122"
ohos:text_color="#ffffff"
ohos:background_element="#000000"
ohos:text_size="50fp"></Text>
</com.example.javacustomcomponent.component.LinearLayout>
</DirectionalLayout>