赞
踩
在实际的开发过程中,往往系统自己自带的组件是不能满足开发需求的,需要自己按照系统的规则去定制自己的组件;
鸿蒙os中的所有的组件的基类直接或者间接Component类,像Button,text,Image等等,能容纳别的组件的组件我们一般称之为容器类组件,这类组件的基类直接或者间接是ComponentContainer,像DirectionalLayout,DependentLayout等等;
- public class TestComponent extends Component {
-
- public TestComponent(Context context) {
- super(context);
- }
-
- public TestComponent(Context context, AttrSet attrSet) {
- super(context, attrSet);
- }
-
- public TestComponent(Context context, AttrSet attrSet, String styleName) {
- super(context, attrSet, styleName);
- }
-
- public TestComponent(Context context, AttrSet attrSet, int resId) {
- super(context, attrSet, resId);
- }
- }
创建TestComponent继承Component并覆写其中的构造方案,如果当前的自定义组件需要使用到xml中,那需要至少复现一个带AttrSet参数的方法,因为xml中的属性需要用这个类获取和设置,是不是跟android的感觉一样,后面会有跟多的相似的地方;
(1)首先创建一个绘制的task
- private DrawTask mDrawTask = new DrawTask() {
- @Override
- public void onDraw(Component component, Canvas canvas) {
- TestComponent.this.onDraw(component,canvas);
- }
- };
(2)实现上一步骤中的onDraw方法
- private void onDraw(Component component, Canvas canvas) {
- //todo 这里就是使用canvas绘制我们需要内容
-
- }
(3)在构造方法中加入Task
- public TestComponent(Context context) {
- super(context);
- addDrawTask(mDrawTask);
- }
-
- public TestComponent(Context context, AttrSet attrSet) {
- super(context, attrSet);
- addDrawTask(mDrawTask);
- }
这里只是展示了两个构造方案中调用addDrawTask,但不是只能这么用,因为鸿蒙os在绘制我们的组件的时候,会调用我们add进去的这个task,所以我们提前把我们业务逻辑add进去而已;
这样你只需要在private void onDraw中绘制我们的内容就可以了;
例如我们绘制一个画线的画板:绘制的代码如下
- import ohos.agp.components.AttrSet;
- import ohos.agp.components.Component;
- import ohos.agp.render.Canvas;
- import ohos.agp.render.Paint;
- import ohos.agp.render.Path;
- import ohos.agp.utils.Color;
- import ohos.agp.utils.Point;
- import ohos.app.Context;
- import ohos.hiviewdfx.HiLog;
- import ohos.hiviewdfx.HiLogLabel;
- import ohos.multimodalinput.event.MmiPoint;
- import ohos.multimodalinput.event.TouchEvent;
-
- public class TestComponent extends Component {
- private static final String TAG = "TestComponent";
-
- private DrawTask mDrawTask = (component, canvas) -> TestComponent.this.onDraw(component,canvas);
-
- private TouchEventListener mTouchEventListener = (component, touchEvent) -> TestComponent.this.onTouchEvent(component, touchEvent);
-
- private Paint mPaint;
- private Path mPath;
- private Point mPrePoint;
- private Point mPreCtrlPoint;
-
- public TestComponent(Context context) {
- super(context);
- initPaint();
- addDrawTask(mDrawTask);
- setTouchEventListener(mTouchEventListener);
- }
-
- public TestComponent(Context context, AttrSet attrSet) {
- super(context, attrSet);
- initPaint();
- addDrawTask(mDrawTask);
- setTouchEventListener(mTouchEventListener);
- }
-
- public TestComponent(Context context, AttrSet attrSet, String styleName) {
- super(context, attrSet, styleName);
- }
-
- public TestComponent(Context context, AttrSet attrSet, int resId) {
- super(context, attrSet, resId);
- }
-
- private void initPaint() {
- //初始化paint
- mPaint = new Paint();
- mPaint.setColor(Color.WHITE);
- mPaint.setStrokeWidth(5f);
- mPaint.setStyle(Paint.Style.STROKE_STYLE);
- }
-
- private void onDraw(Component component, Canvas canvas) {
- canvas.drawPath(mPath, mPaint);
- }
-
- public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
- switch (touchEvent.getAction()) {
- case TouchEvent.PRIMARY_POINT_DOWN: {
- //鸿蒙Log工具
- HiLog.debug(new HiLogLabel(0, 0, TAG), "TouchEvent.PRIMARY_POINT_DOWN");
- //获取点信息
- MmiPoint point = touchEvent.getPointerPosition(touchEvent.getIndex());
- mPath.reset();
- mPath.moveTo(point.getX(), point.getY());
- mPrePoint.position[0] = point.getX();
- mPrePoint.position[1] = point.getY();
- mPreCtrlPoint.position[0] = point.getX();
- mPreCtrlPoint.position[1] = point.getY();
- //PRIMARY_POINT_DOWN 一定要返回true
- return true;
- }
- case TouchEvent.PRIMARY_POINT_UP:
-
- break;
- case TouchEvent.POINT_MOVE: {
- HiLog.debug(new HiLogLabel(0, 0, TAG), "TouchEvent.POINT_MOVE");
- MmiPoint point = touchEvent.getPointerPosition(touchEvent.getIndex());
- Point currCtrlPoint = new Point((point.getX() + mPrePoint.position[0]) / 2,
- (point.getY() + mPrePoint.position[1]) / 2);
- //绘制三阶贝塞尔曲线
- mPath.cubicTo(mPrePoint, mPreCtrlPoint, currCtrlPoint);
- mPreCtrlPoint.position[0] = currCtrlPoint.position[0];
- mPreCtrlPoint.position[1] = currCtrlPoint.position[1];
- mPrePoint.position[0] = point.getX();
- mPrePoint.position[1] = point.getY();
- //更新显示
- invalidate();
- break;
- }
-
- }
- return false;
- }
- }
上面就实现自定义普通组件的过程
- public class TestContainer extends ComponentContainer {
-
- public TestContainer(Context context) {
- super(context);
- }
-
- public TestContainer(Context context, AttrSet attrSet) {
- super(context, attrSet);
- }
-
- public TestContainer(Context context, AttrSet attrSet, String styleName) {
- super(context, attrSet, styleName);
- }
- }
跟自定义Component一样继承容器类控件的基类,并实现其中的构造方法
因为容器类组件是能容纳其他组件的特殊组件,所以它不仅仅需要关心自己,也需要关心它内部的子组件,故我们需要关注的方法变成了三个:
(1)初始化测量,布局,绘制的回调
- //测量的回调
- private EstimateSizeListener mEstimateSizeListener = new EstimateSizeListener() {
- @Override
- public boolean onEstimateSize(int widthEstimateConfig, int heightEstimateConfig) {
- return TestContainer.this.onEstimateSize(widthEstimateConfig,heightEstimateConfig);
- }
- };
-
- //布局的回调
- private ArrangeListener mArrangeListener = new ArrangeListener() {
- @Override
- public boolean onArrange(int left, int top, int width, int height) {
- return TestContainer.this.onArrange(left,top,width,height);
- }
- };
-
- //绘制的回调
- private DrawTask mDrawTask = new DrawTask() {
- @Override
- public void onDraw(Component component, Canvas canvas) {
- onDraw(component,canvas);
- }
- };
里面的回调,下面会讲解,先把基本步骤搞定;
(2)添加回调
- public TestContainer(Context context) {
- super(context);
- //add测量的回调
- setEstimateSizeListener(mEstimateSizeListener);
- //add布局的回调
- setArrangeListener(mArrangeListener);
- //add绘制的回调
- addDrawTask(mDrawTask);
- }
-
- public TestContainer(Context context, AttrSet attrSet) {
- super(context, attrSet);
- //add测量的回调
- setEstimateSizeListener(mEstimateSizeListener);
- //add布局的回调
- setArrangeListener(mArrangeListener);
- //add绘制的回调
- addDrawTask(mDrawTask);
- }
这就是自定义的ComponentContainer的基本步骤
上面步骤中
setEstimateSizeListener(mEstimateSizeListener); 这个是设置测量的回调,里面是一个接口,参数是int类型,分别是widthEstimateConfig和heightEstimateConfig
这两个值也是分为Mode和value的,获取这Mode和Value使用的是EstimateSpec这个类,如下:
- int wideMode = EstimateSpec.getMode(widthEstimateConfig);
- int wideSize = EstimateSpec.getSize(widthEstimateConfig);
- int heightMode = EstimateSpec.getMode(heightEstimateConfig);
- int heightSize = EstimateSpec.getSize(heightEstimateConfig);
Mode的值一共有如下三种:
NOT_EXCEED :在此模式下,已为子组件指定最大大小;
PRECISE :在这种模式下,父组件已经确定了子组件的确切尺寸;
UNCONSTRAINT :在这种模式下,父组件对子组件没有任何约束,这意味着子组件可以是所需的任何大小;
android | 鸿蒙 | |
基类 | View(普通组件)ViewGroup(容器类组件) | Component(普通组件)ComponentContainer(容器类组件) |
普通组件 | 覆写onDraw方法 | 添加绘制的任务(回调) 添加方法 addDrawTask 任务类 DrawTask |
容器类组件 | 覆写测量方法onMeasure | 添加测量的回调 设置方法 setEstimateSizeListener 回调接口的方法 onEstimateSize |
覆写布局方法onLayout | 添加布局的回调 设置方法 setArrangeListener 回调接口的方法 onArrange | |
覆写绘制方法onDraw | 添加绘制的任务(回调) 添加方法 addDrawTask 任务类 DrawTask | |
Mode值 | UNSPECIFIED:父容器没有对当前View有任何限制,当前View可以任意取尺寸 | UNCONSTRAINT:在这种模式下,父组件对子组件没有任何约束,这意味着子组件可以是所需的任何大小 |
EXACTLY:当前的尺寸就是当前View应该取的尺寸 | PRECISE:在这种模式下,父组件已经确定了子组件的确切尺寸 | |
AT_MOST:当前尺寸是当前View能取的最大尺寸 | NOT_EXCEED:在此模式下,已为子组件指定最大大小 |
总结,以上就是鸿蒙自定义组件的基础内容,由于当前还看不到系统的源码,具体的实现逻辑还有待开源后补充;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。