专栏首页C++核心准则原文翻译自学HarmonyOS应用开发(67)- 自定义布局(2)

自学HarmonyOS应用开发(67)- 自定义布局(2)

布局文件示例

接下来使用一个实际的布局为例,介绍动态调整组件高度的实现方法。布局内容如下:

<?xml version="1.0" encoding="utf-8"?>
<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正常动作需要几个必要条件:

  1. 需要进行调整的组件通过高度固定的LayoutSeparator组件分隔。这里高度固定是一个必要条件。
  2. 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;
    @Override
    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);
                }
            }
        }
    }
    @Override
    public void onDragStart(Component component, DragInfo dragInfo) {
        if(draggedSeparator != null){
            DynamicLayout.this.onSeparatorDragStart(draggedSeparator);
            dragStart = dragInfo.startPoint;
        }
    }
    @Override
    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);
        }
    }
    @Override
    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;
    }

    @Override
    public void onDragCancel(Component component, DragInfo dragInfo) {
        //HiLog.info(LABEL, "DynamicLayout.onDragCancel!");
        draggedSeparator = null;
        invalidate();
    }

    @Override
    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);
        if(childView == separator) {
            Component comp_up = getComponentAt(idx - 1);
            DynamicLayout.LayoutConfig lc_up = (DynamicLayout.LayoutConfig)comp_up.getLayoutConfig();
            Component comp_down = getComponentAt(idx + 1);
            DynamicLayout.LayoutConfig lc_down = (DynamicLayout.LayoutConfig)comp_down.getLayoutConfig();
            if(lc_up.height >= 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 语言进行图像分析、数据处理工作的读者可以直接以本书中的示例为基础,迅速构建自己的系统架构。

本文分享自微信公众号 - 面向对象思考(OOThinkingDalian),作者:面向对象思考

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-08-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 自学HarmonyOS应用开发(66)- 自定义布局(1)

    Harmony应用开发文档中为Java开发者提供了6种UI布局,可以满足开发者的大部分需求。但是有一个问题是:这些布局一旦显示,用户便无法进行调整。我们开发一个...

    面向对象思考
  • 自学HarmonyOS应用开发(47)- 自定义switch组件

    HarmonyOS应用开发都会用到各种各样的UI组件,开发者可以根据需求在布局文件设定UI组件各种属性。但是需求是多种多样

    面向对象思考
  • 自学鸿蒙应用开发(33)- 在布局中使用自定义UI组件

    组建容器类ArcProgressBarContainer负责协调每个ArcProgressBar的描画动作。

    面向对象思考
  • 自学HarmonyOS应用开发(54)- 校正定位偏差

    经过一番调查,结论是gps信号使用的是WGS-84坐标系,而高德地图使用的是GCJ-02火星坐标系,只有经过坐标变换才能显示正确的位置。这方面的文章网上有很多,...

    面向对象思考
  • 自学鸿蒙应用开发(26)- 自定义CommonDialog

    上一篇文章中说过,直接使用鸿蒙系统中的CommonDialog大致是下面的效果:

    面向对象思考
  • 自学鸿蒙应用开发(27)- 自定义ListDialog

    这个效果过于简陋,无法用于实际的产品开发。本文介绍如何定制自己的ListDialog。还是先看演示视频:

    面向对象思考
  • 【HarmonyOS 专题】02 搭建简单登录页面

    和尚在搭建完 HarmonyOS 环境之后,有很长时间没有研究过 HarmonyOS,DevEco Studio 已经更新了多个版本,和尚在升级完 ID...

    阿策小和尚
  • 技术分析 | HarmonyOS到底是不是Android套皮?

    最近鸿蒙系统关注度好高,支持与反对、看好和看衰、「自主的全场景分布式系统」和「Android套壳」各执一词,吵的不可开交。

    刘盼
  • 自学HarmonyOS应用开发(48)- Tablist组件进阶

    但是有一个问题是这篇文章,包括HarmonyOS应用开发的官方文档都只是实现了Tab切换的基本功能,对于每个Tab页内组件的处理没有详细说明。本文就来补上这个短...

    面向对象思考
  • 自学鸿蒙应用开发(4)- 画面布局

    在原有布局基础上,增加另外的DirectionalLayout管理图像和文字,并增加三处Component用于调整个要素之间的间隔。

    面向对象思考
  • 程序员看华为HarmonyOS首发

    HarmonyOS代码正式开源,9月10日下午朋友圈散布着这条消息,科技圈炸锅了。各种声音的都有,我也挺好奇的,目前Android、iOS一统江湖,Harmon...

    马上就说
  • 自学HarmonyOS应用开发(64)- 处理屏幕旋转

    旋转屏幕是手机用户的一个日常操作,本文介绍如何在屏幕旋转时自动调整屏幕布局的方法。效果如下:

    面向对象思考
  • 自学HarmonyOS应用开发(59)- 处理拖动事件

    在Harmony应用中通过实现Component.DraggedListener接口处理拖动事件,这个接口的方法一共有6个,这里我们只是用其中的3个:

    面向对象思考
  • 自学HarmonyOS应用开发(57)- 与Service进行交互

    构建自己的Connection类 StopWatchServiceConnection类的主要功能有两个:一是接受连接成功通知并获取服务端传过来的用于通信的IR...

    面向对象思考
  • 自学HarmonyOS应用开发(49)- 引入地图功能

    秒表应用的功能就是计时,其中有一种情况就是计算地图上两点之间移动的时间。但是作者在实际使用这个应用的时候,经常会忘了在预定地点开始和停止计时。解决这个问题的想法...

    面向对象思考
  • 自学HarmonyOS应用开发(53)- 获取当前位置

    在registerLocationEvent方法用来注册一个单次定位事件请求;在定位事件响应对象中我们将获得的位置信息通知给地图对象。

    面向对象思考
  • Android 自定义流布局。使用开源库SimpleFlowLayout

    实际项目中需要实现一个 热门搜索 的栏目,类似下图: 由于 子项(子view) 中的文字是可变的,一行能显示的 子项 的个数也无法确定。需要支持自动换行和计算...

    zhangyunfeiVir
  • 自学鸿蒙应用开发(30)- 自定义UI组件(1)

    任何一种开发工具也不可能为开发者提供所有的组件,根据现有组件定义自己的组件也就成为必需。接下来的几篇文章我们定义一个多层圆弧形进度条。本文是第一篇。

    面向对象思考
  • 【第22期】HarmonyOS应用开发(基础篇)

    这不就是说,以后华为手机都是鸿蒙系统了嘛?鸿蒙还发出了一条视频,视频中显示2021年6月2号将开启鸿蒙操作系统及华为全场景新品发布会。预计现在支持EMUI11升...

    siberiawolf

扫码关注云+社区

领取腾讯云代金券