自学HarmonyOS应用开发(67)- 自定义布局(2)
布局文件示例
接下来使用一个实际的布局为例,介绍动态调整组件高度的实现方法。布局内容如下:
<xwg.filebrowser.DynamicLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:background_element="$graphic:main_ability_title_background"
ohos:orientation="vertical">
<include
ohos:id="$id:app_bar"
ohos:height="match_content"
ohos:width="match_parent"
ohos:layout="$layout:app_bar_layout"/>
<Text
ohos:id="$+id:text1"
ohos:width="match_parent"
ohos:height="0"
ohos:weight = "200"
ohos:text="Text1"
ohos:text_size="28fp"
ohos:text_color="#0000FF"
ohos:right_padding="15vp"
ohos:left_padding="15vp"
ohos:background_element="#FFFFDF"/>
<xwg.filebrowser.LayoutSeparator
ohos:id="$+id:seperator"
ohos:height="20vp"
ohos:width="match_parent"
ohos:background_element="#EFEFEF"/>
<Text
ohos:id="$+id:text2"
ohos:width="match_parent"
ohos:height="0"
ohos:weight = "300"
ohos:text="Text2"
ohos:text_size="28fp"
ohos:text_color="#0000FF"
ohos:right_padding="15vp"
ohos:left_padding="15vp"
ohos:background_element="#DFFFFF"/>
<xwg.filebrowser.LayoutSeparator
ohos:id="$+id:seperator2"
ohos:height="20vp"
ohos:width="match_parent"
ohos:background_element="#EFEFEF"/>
<Text
ohos:id="$+id:text3"
ohos:width="match_parent"
ohos:height="0"
ohos:weight = "300"
ohos:text="Text3"
ohos:text_size="28fp"
ohos:text_color="#0000FF"
ohos:right_padding="15vp"
ohos:left_padding="15vp"
ohos:background_element="#FFEFEF"/>
</xwg.filebrowser.DynamicLayout>
DynamicLayout正常动作需要几个必要条件:
需要进行调整的组件通过高度固定的LayoutSeparator组件分隔。这里高度固定是一个必要条件。
LayoutSeparator上下组件高度的指定方式必须相同:要么都是直接用height属性指定高度,要么都是用weight指定占比。
除了这两个限制之外,调整对象组件的类型/个数,分隔的组件的高度都可以任意指定。够灵活了吧。
LayoutSeparator组件
public class LayoutSeparator extends Component {
public LayoutSeparator(Context context, AttrSet attrSet) {
super(context, attrSet);
}
}
这个组件没有特别实现任何功能,只是获得了一个用于表明拖动对象的类型。它会在后面的说明中用到。
处理拖动动作
下面是DynamicLayout中实现的DraggedListener:
onDragDown方法用于处理拖动按下操作,内容是找到按下动作对象的LayoutSeparator并改变其颜色,如果按下对象不是LayoutSeparator,就当什么也没发生。
onDragStart方法调用DynamicLayout的onSeparatorDragStart方法并记录拖动的开始位置。接下来的onDragUpdate会在调用DynamicLayout的onSeparatorDragUpdate时使用这个开始位置信息。
Component.DraggedListener dragListener = new Component.DraggedListener(){
Point dragStart = null;
LayoutSeparator draggedSeparator = null;
public void onDragDown(Component component, DragInfo dragInfo) {
//HiLog.info(LABEL, "DynamicLayout.onDragDown!");
draggedSeparator = null;
dragStart = null;
for (int idx = 1; idx < getChildCount()-1; idx++) {
Component childView = DynamicLayout.this.getComponentAt(idx);
if (childView instanceof LayoutSeparator) {
LayoutSeparator separator = (LayoutSeparator) childView;
Rect visibleRect = new Rect(separator.getLeft(), separator.getTop(),
separator.getRight(), separator.getBottom());
if(visibleRect.isInclude(dragInfo.downPoint)){
draggedSeparator = separator;
ShapeElement bg = new ShapeElement();
bg.setRgbColor(RgbPalette.GREEN);
bg.setShape(ShapeElement.RECTANGLE);
draggedSeparator.setBackground(bg);
}
}
}
}
public void onDragStart(Component component, DragInfo dragInfo) {
if(draggedSeparator != null){
DynamicLayout.this.onSeparatorDragStart(draggedSeparator);
dragStart = dragInfo.startPoint;
}
}
public void onDragUpdate(Component component, DragInfo dragInfo) {
if(draggedSeparator != null) {
Size offset = new Size((int) (dragInfo.updatePoint.getPointX() - dragStart.getPointX()),
(int) (dragInfo.updatePoint.getPointY() - dragStart.getPointY()));
DynamicLayout.this.onSeparatorDragUpdate(draggedSeparator, offset);
}
}
public void onDragEnd(Component component, DragInfo dragInfo) {
//HiLog.info(LABEL, "DynamicLayout.onDragEnd!");
if(draggedSeparator != null){
ShapeElement bg = new ShapeElement();
bg.setRgbColor(RgbPalette.LIGHT_GRAY);
bg.setShape(ShapeElement.RECTANGLE);
draggedSeparator.setBackground(bg);
}
draggedSeparator = null;
}
public void onDragCancel(Component component, DragInfo dragInfo) {
//HiLog.info(LABEL, "DynamicLayout.onDragCancel!");
draggedSeparator = null;
invalidate();
}
public boolean onDragPreAccept(Component component, int dragDirection) {
return true;
}
};
onDragEnd负责最后恢复LayoutSeparator的颜色。
处理开始拖动动作
以下是开始拖动时的处理:
public void onSeparatorDragStart(LayoutSeparator separator){
up_height = -1;
down_height = -1;
up_weight = -1;
down_weight = -1;
for (int idx = 1; idx < getChildCount()-1; idx++) {
Component childView = getComponentAt(idx);
= separator) { =
Component comp_up = getComponentAt(idx - 1);
lc_up = (DynamicLayout.LayoutConfig)comp_up.getLayoutConfig();
Component comp_down = getComponentAt(idx + 1);
lc_down = (DynamicLayout.LayoutConfig)comp_down.getLayoutConfig();
>= 0 && lc_down.height >= 0) {
up_height = comp_up.getHeight();
down_height = comp_down.getHeight();
}
up_weight = lc_up.weight;
down_weight = lc_down.weight;
}
}
}
内容很简单:就是找到LayoutSeparator上下都组件并记录它们的高度信息。
处理拖动过程
拖动过程中就是根据DraggedListener传来的拖动距离信息调整LayoutSeparator相邻组件的高度。无论是使用height属性还是weight属性表示高度,都会保证调整前后的合计值不变。这样可以保证位置调整不会影响其他组件。
public void onSeparatorDragUpdate(LayoutSeparator separator, Size offset){
//HiLog.info(LABEL, "DynamicLayout.onSeparatorDragUpdate!offset.height=%{public}d", offset.height);
if((up_height > 0 && (up_height + offset.height) >= 0)
&& (down_height >0 && (down_height - offset.height) >= 0)) {
for (int idx = 1; idx < getChildCount() - 1; idx++) {
Component childView = getComponentAt(idx);
if (childView == separator) {
adjustHeight(getComponentAt(idx - 1), getComponentAt(idx + 1), offset.height);
break;
}
}
}
}
void adjustHeight(Component up, Component down, int offset){
DynamicLayout.LayoutConfig lc_up = (DynamicLayout.LayoutConfig)up.getLayoutConfig();
DynamicLayout.LayoutConfig lc_down = (DynamicLayout.LayoutConfig)down.getLayoutConfig();
if(lc_up.height > 0 && lc_down.height > 0){
lc_up.height = up_height + offset;
lc_down.height = down_height - offset;
}
else if(lc_up.height == 0 && lc_down.height==0 && weight_rate > 0){
offset = (int)(offset / weight_rate);
lc_up.weight = up_weight + offset;
lc_down.weight = down_weight - offset;
}
else{
//do nothing.
}
arrange();
}
arrange的功能是主动发起布局调整,这个方法使用了之前的布局计算过程中预留的数据:
public void arrange(){
onEstimateSize(lastConfigWidth, lastConfigHeight);
onArrange(layoutLeft, layoutTop, layoutWidth, layoutHeight);
}
作者没有找到更简单的发起布局计算的方法,先用这种方法将就一下吧。
参考资料
自定义布局
https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ui-java-custom-layouts-0000001092683918
参考代码
完整代码可以从以下链接下载:
https://github.com/xueweiguo/Harmony/tree/master/FileBrowser
作者著作介绍
《实战Python设计模式》是作者去年3月份出版的技术书籍,该书利用Python 的标准GUI 工具包tkinter,通过可执行的示例对23 个设计模式逐个进行说明。这样一方面可以使读者了解真实的软件开发工作中每个设计模式的运用场景和想要解决的问题;另一方面通过对这些问题的解决过程进行说明,让读者明白在编写代码时如何判断使用设计模式的利弊,并合理运用设计模式。
对设计模式感兴趣而且希望随学随用的读者通过本书可以快速跨越从理解到运用的门槛;希望学习Python GUI 编程的读者可以将本书中的示例作为设计和开发的参考;使用Python 语言进行图像分析、数据处理工作的读者可以直接以本书中的示例为基础,迅速构建自己的系统架构。
觉得本文有帮助?请分享给更多人。
关注微信公众号【面向对象思考】轻松学习每一天!
面向对象开发,面向对象思考!