HarmonyOS学习路之开发篇—Java UI框架(自定义组件与布局 一)

HarmonyOS提供了一套复杂且强大的Java UI框架,其中Component提供内容显示,是界面中所有组件的基类。ComponentContainer作为容器容纳Component或ComponentContainer对象,并对它们进行布局。

Java UI框架也提供了一部分Component和ComponentContainer的具体子类,即常用的组件(比如:Text、Button、Image等)和常用的布局(比如:DirectionalLayout、DependentLayout等)。如果现有的组件和布局无法满足设计需求,例如仿遥控器的圆盘按钮、可滑动的环形控制器等,可以通过自定义组件和自定义布局来实现。

自定义组件是由开发者定义的具有一定特性的组件,通过扩展Component或其子类实现,可以精确控制屏幕元素的外观,也可响应用户的点击、触摸、长按等操作。

自定义布局是由开发者定义的具有特定布局规则的容器类组件,通过扩展ComponentContainer或其子类实现,可以将各子组件摆放到指定的位置,也可响应用户的滑动、拖拽等事件。

自定义组件

当Java UI框架提供的组件无法满足设计需求时,可以创建自定义组件,根据设计需求添加绘制任务,并定义组件的属性及事件响应,完成组件的自定义。

常用接口

接口名

作用

setEstimateSizeListener

设置测量组件的侦听器。

setEstimatedSize

设置测量的宽度和高度。

onEstimateSize

测量组件的大小以确定宽度和高度。

EstimateSpec.getChildSizeWithMode

基于指定的大小和模式为子组件创建度量规范。

EstimateSpec.getSize

从提供的度量规范中提取大小。

EstimateSpec.getMode

获取该组件的显示模式。

addDrawTask

添加绘制任务。

onDraw

通过绘制任务更新组件时调用。

如何实现自定义组件

下面以自定义圆环组件为例,介绍自定义组件的通用配置方法:在屏幕中绘制圆环,并实现点击改变圆环颜色的功能。

在界面中显示的自定义圆环组件

1. 创建自定义组件的类,并继承Component或其子类,添加构造方法。

示例代码如下

public class CustomComponent extends Component{public CustomComponent(Context context) {this(context, null);}//如需支持xml创建自定义组件,必须添加该构造方法public CustomComponent(Context context, AttrSet attrSet) {super(context, attrSet);}
}

2. 实现Component.EstimateSizeListener接口,在onEstimateSize方法中进行组件测量,并通过setEstimatedSize方法通知组件。

示例代码如下:

public class CustomComponent extends Component implements Component.EstimateSizeListener {//240为组件默认大小public int width = 240;public int height = 240;public CustomComponent(Context context, AttrSet attrSet) {...// 设置测量组件的侦听器setEstimateSizeListener(this);}...@Overridepublic boolean onEstimateSize(int widthEstimateConfig, int heightEstimateConfig) {int widthSpce = EstimateSpec.getMode(widthEstimateConfig);int heightSpce = EstimateSpec.getMode(heightEstimateConfig);int widthConfig = 0;switch (widthSpce) {case EstimateSpec.UNCONSTRAINT:case EstimateSpec.PRECISE:width = EstimateSpec.getSize(widthEstimateConfig);widthConfig = EstimateSpec.getSizeWithMode(width, EstimateSpec.PRECISE);break;case EstimateSpec.NOT_EXCEED:widthConfig = EstimateSpec.getSizeWithMode(width, EstimateSpec.PRECISE);break;default:break;}int heightConfig = 0;switch (heightSpce) {case EstimateSpec.UNCONSTRAINT:case EstimateSpec.PRECISE:height = EstimateSpec.getSize(heightEstimateConfig);heightConfig = EstimateSpec.getSizeWithMode(height, EstimateSpec.PRECISE);break;case EstimateSpec.NOT_EXCEED:heightConfig = EstimateSpec.getSizeWithMode(height, EstimateSpec.PRECISE);break;default:break;}setEstimatedSize(widthConfig, heightConfig);return true;}
}
  • 注意事项
    1. 自定义组件测量出的大小需通过setEstimatedSize通知组件,并且必须返回true使测量值生效。
    2. setEstimatedSize方法的入参携带模式信息,可使用Component.EstimateSpec.getSizeWithMode方法进行拼接。
  • 测量模式

    测量组件的宽高需要携带模式信息,不同测量模式下的测量结果也不相同,需要根据实际需求选择适合的测量模式。

        测量模式信息

模式

作用

UNCONSTRAINT

父组件对子组件没有约束,表示子组件可以任意大小。

PRECISE

父组件已确定子组件的大小。

NOT_EXCEED

已为子组件确定了最大大小,子组件不能超过指定大小。

3. 自定义xml属性,通过构造方法中携带的参数attrSet,可以获取到在xml中配置的属性值,并应用在该自定义组件中。

示例代码如下:

public class CustomComponent extends Component implements Component.EstimateSizeListener {private static final String ATTR_RING_WIDTH = "ring_width";private static final String ATTR_RING_RADIUS = "ring_radius";private static final String ATTR_DEFAULT_COLOR = "default_color";private static final String ATTR_PRESSED_COLOR = "pressed_color";public float ringWidth = 20f; //圆环宽度public float ringRadius = 100f; //圆环半径public Color defaultColor = Color.YELLOW; //默认颜色public Color pressedColor = Color.CYAN; //按压态颜色   public CustomComponent(Context context, AttrSet attrSet) {...//初始化xml属性initAttrSet(attrSet);}private void initAttrSet(AttrSet attrSet) {if (attrSet == null) return;if (attrSet.getAttr(ATTR_DEFAULT_COLOR).isPresent()) {defaultColor = attrSet.getAttr(ATTR_DEFAULT_COLOR).get().getColorValue();}if (attrSet.getAttr(ATTR_RING_WIDTH).isPresent()) {ringWidth = attrSet.getAttr(ATTR_RING_WIDTH).get().getDimensionValue();}if (attrSet.getAttr(ATTR_RING_RADIUS).isPresent()) {ringRadius = attrSet.getAttr(ATTR_RING_RADIUS).get().getDimensionValue();}if (attrSet.getAttr(ATTR_PRESSED_COLOR).isPresent()) {pressedColor = attrSet.getAttr(ATTR_PRESSED_COLOR).get().getColorValue();}}   
}

4. 实现Component.DrawTask接口,在onDraw方法中执行绘制任务,该方法提供的画布Canvas,可以精确控制屏幕元素的外观。在执行绘制任务之前,需要定义画笔Paint。

示例代码如下:

public class CustomComponent extends Component implements Component.DrawTask,Component.EstimateSizeListener {// 绘制圆环的画笔private Paint circlePaint;    public CustomComponen(Context context, AttrSet attrSet) {...// 初始化画笔initPaint();// 添加绘制任务addDrawTask(this);}private void initPaint(){circlePaint = new Paint();circlePaint.setColor(defaultColor);circlePaint.setStrokeWidth(ringWidth);circlePaint.setStyle(Paint.Style.STROKE_STYLE);}@Overridepublic void onDraw(Component component, Canvas canvas) {int x = width / 2;int y = height / 2;canvas.drawCircle(x, y, ringRadius, circlePaint);}...
}

5. 实现Component.TouchEventListener或其他事件的接口,使组件可响应用户输入。

示例代码如下

public class CustomComponent extends Component implements Component.DrawTask, Component.EstimateSizeListener, Component.TouchEventListener {...public CustomComponent(Context context, AttrSet attrSet) {...// 设置TouchEvent响应事件setTouchEventListener(this);}...@Overridepublic boolean onTouchEvent(Component component, TouchEvent touchEvent) {switch (touchEvent.getAction()) {case TouchEvent.PRIMARY_POINT_DOWN:circlePaint.setColor(pressedColor);invalidate();break;case TouchEvent.PRIMARY_POINT_UP:circlePaint.setColor(defaultColor);invalidate();break;}return true;}
}

注意:

  1. 需要更新UI显示时,可调用invalidate()方法。
  2. 示例中展示TouchEventListener为响应触摸事件,除此之外还可实现ClickedListener响应点击事件、LongClickedListener响应长按事件等。

6. 在xml文件中创建并配置自定义组件

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayoutxmlns:ohos="http://schemas.huawei.com/res/ohos"xmlns:custom="http://schemas.huawei.com/res/custom"ohos:height="match_parent"ohos:width="match_parent"ohos:orientation="vertical"><!-- 请根据实际包名和文件路径引入--><com.huawei.harmonyosdemo.custom.CustomComponentohos:height="300vp"ohos:width="match_parent"ohos:background_element="black"ohos:clickable="true"custom:default_color="gray"custom:pressed_color="red"custom:ring_width="20vp"custom:ring_radius="120vp"/>
</DirectionalLayout>

场景示例

利用自定义组件,绘制环形进度控制器,可通过滑动改变当前进度,也可响应进度的改变,UI显示的样式也可通过设置属性进行调整。

自定义环形进度控制器

示例代码如下:

public class CustomControlBar extends Component implements Component.DrawTask,Component.EstimateSizeListener, Component.TouchEventListener {private final static String ATTR_UN_FILL_COLOR = "unfill_color";private final static String ATTR_FILL_COLOR = "fill_color";private final static String ATTR_CIRCLE_WIDTH = "circle_width";private final static String ATTR_COUNT = "count";private final static String ATTR_CURRENT_PROGRESS = "current_progress";private final static String ATTR_SPLIT_SIZE = "split_size";private final static String ATTR_CIRCLE_RADIUS = "circle_radius";private final static String ATTR_CENTER_PIXELMAP = "center_pixelmap";private final static float CIRCLE_ANGLE = 360.0f;private final static int DEF_UNFILL_COLOR = 0xFF808080;private final static int DEF_FILL_COLOR = 0xFF1E90FF;public int width = 240;public int height = 240;// 圆环轨道颜色private Color unFillColor = new Color(DEF_UNFILL_COLOR);// 圆环覆盖颜色private Color fillColor = new Color(DEF_FILL_COLOR);// 圆环宽度private int circleWidth = 30;// 画笔private final Paint paint;// 个数private int count = 10;// 当前进度private int currentCount = 0;// 间隙值private int splitSize = 10;// 内圆的正切方形private final RectFloat centerRectFloat = new RectFloat();// 中心绘制的图片private PixelMap image = null;private int radius = 100;// 原点坐标private Point centerPoint;// 进度改变的事件响应private ProgressChangeListener listener;public CustomControlBar(Context context) {this(context, null);}public CustomControlBar(Context context, AttrSet attrSet) {super(context, attrSet);paint = new Paint();initAttrSet(attrSet);setEstimateSizeListener(this);if (!isClickable()) setClickable(true);setTouchEventListener(this);addDrawTask(this);listener = null;}// 初始化属性值private void initAttrSet(AttrSet attrSet) {if (attrSet == null) return;if (attrSet.getAttr(ATTR_UN_FILL_COLOR).isPresent()) {unFillColor = attrSet.getAttr(ATTR_UN_FILL_COLOR).get().getColorValue();}if (attrSet.getAttr(ATTR_FILL_COLOR).isPresent()) {fillColor = attrSet.getAttr(ATTR_FILL_COLOR).get().getColorValue();}if (attrSet.getAttr(ATTR_CIRCLE_WIDTH).isPresent()) {circleWidth = attrSet.getAttr(ATTR_CIRCLE_WIDTH).get().getDimensionValue();}if (attrSet.getAttr(ATTR_COUNT).isPresent()) {count = attrSet.getAttr(ATTR_COUNT).get().getIntegerValue();}if (attrSet.getAttr(ATTR_CURRENT_PROGRESS).isPresent()) {currentCount = attrSet.getAttr(ATTR_CURRENT_PROGRESS).get().getIntegerValue();}if (attrSet.getAttr(ATTR_SPLIT_SIZE).isPresent()) {splitSize = attrSet.getAttr(ATTR_SPLIT_SIZE).get().getIntegerValue();}if (attrSet.getAttr(ATTR_CIRCLE_RADIUS).isPresent()) {radius = attrSet.getAttr(ATTR_CIRCLE_RADIUS).get().getDimensionValue();}if (attrSet.getAttr(ATTR_CENTER_PIXELMAP).isPresent()) {Element element = attrSet.getAttr(ATTR_CENTER_PIXELMAP).get().getElement();if (element instanceof PixelMapElement) {image = ((PixelMapElement) element).getPixelMap();}}}@Overridepublic boolean onEstimateSize(int widthEstimateConfig, int heightEstimateConfig) {int widthSpce = EstimateSpec.getMode(widthEstimateConfig);int heightSpce = EstimateSpec.getMode(heightEstimateConfig);int widthConfig = 0;switch (widthSpce) {case EstimateSpec.UNCONSTRAINT:case EstimateSpec.PRECISE:width = EstimateSpec.getSize(widthEstimateConfig);widthConfig = EstimateSpec.getSizeWithMode(width, EstimateSpec.PRECISE);break;case EstimateSpec.NOT_EXCEED:widthConfig = EstimateSpec.getSizeWithMode(width, EstimateSpec.PRECISE);break;default:break;}int heightConfig = 0;switch (heightSpce) {case EstimateSpec.UNCONSTRAINT:case EstimateSpec.PRECISE:height = EstimateSpec.getSize(heightEstimateConfig);heightConfig = EstimateSpec.getSizeWithMode(height, EstimateSpec.PRECISE);break;case EstimateSpec.NOT_EXCEED:heightConfig = EstimateSpec.getSizeWithMode(height, EstimateSpec.PRECISE);break;default:break;}System.out.println("WYT_width:" + width + "   height:" + height + "     width_spec:" + widthSpce + "     height_spec:" + heightSpce);setEstimatedSize(widthConfig, heightConfig);return true;}@Overridepublic void onDraw(Component component, Canvas canvas) {paint.setAntiAlias(true);paint.setStrokeWidth(circleWidth);paint.setStrokeCap(Paint.StrokeCap.ROUND_CAP);paint.setStyle(Paint.Style.STROKE_STYLE);int min = Math.min(width, height);radius = (min >> 1) - circleWidth;centerPoint = new Point(width >> 1, height >> 1);drawCount(canvas);if (image != null) {int inRadius = radius - (circleWidth >> 1);centerRectFloat.left = (float) (width / 2 - Math.sqrt(2) * inRadius);centerRectFloat.top = (float) (height / 2 - Math.sqrt(2) * inRadius);centerRectFloat.right = (float) (width / 2 + Math.sqrt(2) * inRadius);centerRectFloat.bottom = (float) (height / 2 + Math.sqrt(2) * inRadius);// 如果图片比较小,那么根据图片的尺寸放置到正中心Size imageSize = image.getImageInfo().size;if (imageSize.width < Math.sqrt(2) * inRadius) {centerRectFloat.left = (width - imageSize.width * 1.0f) / 2;centerRectFloat.top = (height - imageSize.height * 1.0f) / 2;centerRectFloat.right = (width + imageSize.width * 1.0f) / 2;centerRectFloat.bottom = (height + imageSize.height * 1.0f) / 2;}canvas.drawPixelMapHolderRect(new PixelMapHolder(image), centerRectFloat, paint);}}private void drawCount(Canvas canvas) {float itemSize = (CIRCLE_ANGLE - count * splitSize) / count;RectFloat oval = new RectFloat(centerPoint.getPointX() - radius, centerPoint.getPointY() - radius,centerPoint.getPointX() + radius, centerPoint.getPointY() + radius);paint.setColor(unFillColor);for (int i = 0; i < count; i++) {Arc arc = new Arc((i * (itemSize + splitSize)) - 90, itemSize, false);canvas.drawArc(oval, arc, paint);}paint.setColor(fillColor);for (int i = 0; i < currentCount; i++) {Arc arc = new Arc((i * (itemSize + splitSize)) - 90, itemSize, false);canvas.drawArc(oval, arc, paint);}}@Overridepublic boolean onTouchEvent(Component component, TouchEvent touchEvent) {switch (touchEvent.getAction()) {case TouchEvent.PRIMARY_POINT_DOWN:case TouchEvent.POINT_MOVE:MmiPoint absPoint = touchEvent.getPointerPosition(touchEvent.getIndex());Point point = new Point(absPoint.getX(), absPoint.getY());System.out.println("wyt_centerPoint:" + centerPoint + "   point:" + point);double angle = calcRotationAngleInDegrees(centerPoint, point);double multiple = angle / (CIRCLE_ANGLE / count);if ((multiple - (int) multiple) > 0.4) {currentCount = (int) multiple + 1;} else {currentCount = (int) multiple;}if (listener != null) {listener.onProgressChangeListener(currentCount);}invalidate();break;}return true;}public interface ProgressChangeListener {void onProgressChangeListener(int Progress);}// 计算centerPt到targetPt的夹角,单位为度。返回范围为[0, 360),顺时针旋转。private double calcRotationAngleInDegrees(Point centerPt, Point targetPt) {double theta = Math.atan2(targetPt.getPointY()- centerPt.getPointY(), targetPt.getPointX()- centerPt.getPointX());theta += Math.PI / 2.0;double angle = Math.toDegrees(theta);if (angle < 0) {angle += CIRCLE_ANGLE;}return angle;}public Color getUnFillColor() {return unFillColor;}public CustomControlBar setUnFillColor(Color unFillColor) {this.unFillColor = unFillColor;return this;}public Color getFillColor() {return fillColor;}public CustomControlBar setFillColor(Color fillColor) {this.fillColor = fillColor;return this;}public int getCircleWidth() {return circleWidth;}public CustomControlBar setCircleWidth(int circleWidth) {this.circleWidth = circleWidth;return this;}public int getCount() {return count;}public CustomControlBar setCount(int count) {this.count = count;return this;}public int getCurrentCount() {return currentCount;}public CustomControlBar setCurrentCount(int currentCount) {this.currentCount = currentCount;return this;}public int getSplitSize() {return splitSize;}public CustomControlBar setSplitSize(int splitSize) {this.splitSize = splitSize;return this;}public PixelMap getImage() {return image;}public CustomControlBar setImage(PixelMap image) {this.image = image;return this;}public void build() {invalidate();}public void setProgressChangerListener(ProgressChangeListener listener) {this.listener = listener;}
}

 在xml中创建该自定义组件,并设置其属性。

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayoutxmlns:ohos="http://schemas.huawei.com/res/ohos"xmlns:custom="http://schemas.huawei.com/res/custom"ohos:height="match_parent"ohos:width="match_parent"ohos:orientation="vertical"><!-- 请根据实际包名和文件路径引入--><com.huawei.harmonyosdemo.custom.CustomControlBarohos:id="$+id:custom_control_bar"ohos:height="200vp"ohos:width="match_parent"ohos:background_element="black"ohos:top_margin="50vp"custom:center_pixelmap="$media:icon"custom:circle_radius="80vp"custom:circle_width="15vp"custom:count="10"custom:current_progress="5"custom:fill_color="#1e90ff"custom:split_size="13"custom:unfill_color="gray"/>  
</DirectionalLayout>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/3107.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

帆软 FineReport 绘制漏斗图

七一建党节&#xff0c;祝党生日快乐&#xff01; 夏日炎炎&#xff0c;周末在家&#xff0c;想起在用帆软做页面展示的时候&#xff0c;使用到了漏斗图&#xff0c;记录下来&#xff0c;方便查看。 以订单销量变化为例&#xff0c;分为五个阶段&#xff0c;商品浏览人数&#…

leetcode526. 优美的排列(回溯算法-java)

优美的排列 leetcode526. 优美的排列题目描述接替思路代码演示: 动态规划专题 leetcode526. 优美的排列 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/beautiful-arrangement 题目描述 假设有从 1 到 n 的 n 个整数。用…

机器学习——决策树1(三种算法)

要开始了…内心还是有些复杂的 因为涉及到熵…单纯的熵&#xff0c;可以单纯 复杂的熵&#xff0c;如何能通俗理解呢… 我也没有底气&#xff0c;且写且思考吧 1. 决策树分类思想 首先&#xff0c;决策树的思想&#xff0c;有点儿像KNN里的KD树。 KNN里的KD树&#xff0c;是每…

React 简单实现 v-if和v-show的元素控制效果

react中并没有直接的想模板引擎那样的命令可以直接控制元素展示 但是 我们了解了 v-if和v-show之后 还是大有文章的 我们在 项目的 src下创建 components 文件夹 创建dom.jsx 编写代码如下 import React from "react" export default class dom extends React.Comp…

Blender骨骼绑定

演示视频参考连接:Blender骨骼绑定教程3&#xff1a;清除绑定 & Deform & 权重修改_哔哩哔哩_bilibili https://www.youtube.com/watch?vqz86PWlK_8s&ab_channelOutlawVideoProduction &#xff08;用Auto Rig Pro插件&#xff09; https://www.youtube.com/…

MyBatis-Plus 实现PostgreSQL数据库jsonb类型的保存

文章目录 在 handle 包下新建Jsonb处理类方式一方式二 PostgreSQL jsonb类型示例新建数据库表含有jsonb类型创建实体类创建Control 发起请求 在 handle 包下新建Jsonb处理类 方式一 import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFea…

【Python 随练】按键变色

题目&#xff1a; Press any key to change color, do you want to try it. Please hurry up! 简介&#xff1a; 在本篇博客中&#xff0c;我们将解决一个编程问题&#xff1a;按下任意键改变颜色。我们将提供一个完整的代码示例来实现这个功能&#xff0c;并鼓励读者尝试。…

【JavaScript】文档注释详解

文章目录 什么是文档注释为什么要写文档注释不使用文档注释存在的隐患使用函数成员时的书写问题调用函数时功能使用问题 文档注释官方标签函数参数标签 param参数类型 {}参数注释对象属性属性注释使用带有对象属性注释的参数 返回值标签 returns注释 作者标签 author许可证标签…

利用阿里云物联网平台(IoT)实现WEB数据可视化

一年前在阿里物联网平台测试过一个项目&#xff0c;后来就搁置了&#xff0c;昨天有事需要用&#xff0c;发现出错了。 调整完后写一下使用思路&#xff0c;以便未来之需。 阿里云物联网&#xff08;IoT&#xff09;主页&#xff1a;https://iot.aliyun.com/ 阿里云物联网&…

Linux操作系统配置代理服务器

PS:本文只是针对Linux操作系统对于代理服务器的配置操作&#xff0c;不涉及广告 1.代理的概念 代理服务器英文全称是Proxy Server&#xff0c;其功能就是代理网络用户去取得网络信息。形象的说&#xff1a;它是网络信息的中转站。在一般情况下&#xff0c;我们使用网络浏览器直…

【物联网无线通信技术】802.11无线安全认证

本文由简入繁介绍了IEEE802.11i无线局域网安全技术的前世今生&#xff0c;帮助路由器开发者对WLAN的加密安全策略有一个概念上的认知&#xff0c;能够更好地分析STA掉线以及漫游等问题。 目录 WEP WPA WPA/WPA2-PSK认证过程 802.11i WEP WEP是Wired Equivalent Privacy的简…

LabVIEW 图像处理功能

设置成像系统并采集图像后&#xff0c;您可以分析和处理图像&#xff0c;以提取有关被检测对象的有价值信息。 内容 图像分析图像处理斑点分析机器视觉 图像分析 影像分析结合了基于影像像素的灰度强度计算统计数据和测量的技术。您可以使用影像分析功能来确定影像质量是否足以…