前两天看到HarmonyOS开发者官网上发布的一个挑战HarmonyOS分布式趣味应用的帖子,然后有个想法想搞一个小游戏出来,结果三天的时间都卡在了自定义组件上,使用了各种方式方法去实现功能,但是还是没有达到预期的效果,暂时先做个小总结,其实坑有的时候真的很深…

一、效果演示

小应用其实也挺简单,以前也见到过,叫做数字华容道,当你把所在的数字以顺序放置完成后游戏结束。

其实属于益智类的小游戏了;

最终实现效果:

当前实现效果:

二、实现过程

暂时说一下现在的进度,每一个方块可以表示一个棋子,棋子的名称也就是3*3的九宫格,1-9的数字,只是最后一个数字单独设置为空白。点击空白周围的棋子可以与这个空白棋子做一次位置调换,直到将所有棋子顺序排列完成为止。

这里先说一个这个棋子,棋子有两个东西需要被记住,一个是棋子的坐标就是在九宫格里面的位置,另一个就是棋子的名称;所以选择使用自定义组件的方式将坐标和名称进行一个绑定。

Position.java

/*** 定义棋子的位置*/
public class Position {public int sizeX; // 总列数public int sizeY; // 总行数public int x; // 横坐标public int y; // 纵坐标public Position() {}public Position(int sizeX, int sizeY) {this.sizeX = sizeX;this.sizeY = sizeY;}public Position(int sizeX, int sizeY, int x, int y) {this.sizeX = sizeX;this.sizeY = sizeY;this.x = x;this.y = y;}public Position(Position orig) {this(orig.sizeX, orig.sizeY, orig.x, orig.y);}/*** 移动到下一个位置*/public boolean moveToNextPosition() {if (x < sizeX - 1) {x++;} else if (y < sizeY - 1) {x = 0;y++;} else {return false;}return true;}@Overridepublic String toString() {return "Position{" +"x=" + x +", y=" + y +'}';}
}

CubeView.java

public class CubeView extends ComponentContainer {private Position mPosition;private int mNumber;private Text mTextCub;private int mTextSize = 20;public CubeView(Context context) {super(context);init();}public CubeView(Context context, AttrSet attrSet) {super(context, attrSet);init();}private void init(){Component component = LayoutScatter.getInstance(getContext()).parse(ResourceTable.Layout_cube_view_item, this, false);mTextCub = (Text) component.findComponentById(ResourceTable.Id_tv_item);mTextCub.setTextSize(mTextSize, Text.TextSizeType.VP);}public void setNumber(int n) {mNumber = n;mTextCub.setText(String.valueOf(n));}public int getNumber() {return mNumber;}public Position getPosition() {return mPosition;}public void setPosition(Position position) {this.mPosition = position;}@Overridepublic String toString() {return "CubeView{" +"mPosition=" + mPosition +", mNumber=" + mNumber +'}';}
}

cube_view_item.xml

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayoutxmlns:ohos="http://schemas.huawei.com/res/ohos"ohos:height="match_content"ohos:width="match_content"><Textohos:id="$+id:tv_item"ohos:height="100vp"ohos:width="100vp"ohos:background_element="$graphic:cube_view_bg"ohos:text="1"ohos:text_alignment="center"ohos:text_color="$color:cubeViewStroke"ohos:text_size="20vp">></Text>
</DirectionalLayout>

到这问题就来了,因为在代码中只是使用到了setText()方法,那么有人会问我为什么不直接继承Text组件,多写一个布局有点麻烦了不是?

第一个坑

这里就是第一个坑了,因为在以前写Android自定义控件的时候,对于简单的组件来说直接继承它的组件名称就可以了,不用去继承公共类然后再去使用布局去定位到里面的组件。原本我也是这么写的,CubeView直接继承Text没有毛病可以使用,可以看到两者间并无差别。

public class CubeView extends Text {private Position mPosition;private int mNumber;public CubeView(Context context) {super(context);init();}public CubeView(Context context, AttrSet attrSet) {super(context, attrSet);init();}private void init(){}public void setNumber(int n) {mNumber = n;setText(String.valueOf(n));}public int getNumber() {return mNumber;}public Position getPosition() {return mPosition;}public void setPosition(Position position) {this.mPosition = position;}@Overridepublic String toString() {return "CubeView{" +"mPosition=" + mPosition +", mNumber=" + mNumber +'}';}
}

但是在调用组件的时候出现了问题,因为我需要把这个棋子的组件添加到我的棋盘布局中,那么就需要先引入这个组件。引入组件后出问题了,布局报错(在原来Android引入自定义组件的时候,单个组件也是可以直接引入的);报错原因是,我最外层没有放置布局导致不能直接识别单个组件,但是如果我加上一个布局的话,文件不会报错,但是在我的棋盘上不能拿到这个棋子的组件;

为此我只能将棋子的自定义组件写成了布局引入方式。

到这里,棋子的开发工作也就基本做完了,下面要对棋盘进行布局。还是选择自定义组件的方式;

cube_view.xml

<?xml version="1.0" encoding="utf-8"?>
<com.example.codelabs_games_hrd.CubeViewxmlns:ohos="http://schemas.huawei.com/res/ohos"ohos:background_element="$graphic:cube_view_bg"ohos:height="100vp"ohos:width="100vp"ohos:id="$+id:title_bar_left"ohos:text="1"ohos:text_alignment="center"ohos:text_color="$color:cubeViewStroke"ohos:text_size="20vp">
</com.example.codelabs_games_hrd.CubeView>

ability_game.xml

<?xml version="1.0" encoding="utf-8"?>
<StackLayoutxmlns:ohos="http://schemas.huawei.com/res/ohos"ohos:height="match_parent"ohos:width="match_parent"ohos:background_element="$color:cubeViewBg"><com.example.codelabs_games_hrd.BoardViewohos:id="$+id:board"ohos:height="300vp"ohos:width="300vp"ohos:layout_alignment="center"ohos:background_element="$color:boardViewBg"></com.example.codelabs_games_hrd.BoardView><Textohos:id="$+id:tvCheat"ohos:height="10vp"ohos:width="10vp"></Text><Textohos:id="$+id:mask"ohos:height="match_parent"ohos:width="match_parent"ohos:background_element="$color:cubeViewBg"ohos:text="123456789"ohos:text_size="48vp"></Text></StackLayout>

BoardView.java

public class BoardView extends ComponentContainer implements ComponentContainer.EstimateSizeListener, ComponentContainer.ArrangeListener {private static final String TAG = "BoardView";/*** 每一行有多少个棋子*/private int mSizeX = 3;/*** 有多少行棋子*/private int mSizeY = 3;private int maxWidth = 0;private int maxHeight = 0;private int mChildSize;private Position mBlankPos;private CubeView[] mChildren;private OnFinishListener mFinishListener;private int xx = 0;private int yy = 0;private int lastHeight = 0;// 子组件索引与其布局数据的集合private final Map<Integer, Layout> axis = new HashMap<>();//位置及大小private static class Layout {int positionX = 0;int positionY = 0;int width = 0;int height = 0;}private void invalidateValues() {xx = 0;yy = 0;maxWidth = 0;maxHeight = 0;axis.clear();}public BoardView(Context context) {super(context);}public BoardView(Context context, AttrSet attrs) {super(context, attrs);setEstimateSizeListener(this);setArrangeListener(this);init();}private void init() {mChildSize = mSizeX * mSizeY - 1;mChildren = new CubeView[mChildSize];Position p = new Position(mSizeX, mSizeY);for (int i = 0; i < mChildSize; i++) {//添加棋子CubeView view = (CubeView) LayoutScatter.getInstance(getContext()).parse(ResourceTable.Layout_cube_view, this, false);view.setPosition(new Position(p));view.setClickedListener(component -> moveChildToBlank(view));addComponent(view);p.moveToNextPosition();mChildren[i] = view;}//最后一个空白棋子mBlankPos = new Position(mSizeX, mSizeY, mSizeX - 1, mSizeY - 1);}public void setData(List<Integer> data) {for (int i = 0; i < mChildSize; i++) {CubeView view = (CubeView) getComponentAt(i);view.setNumber(data.get(i));}}//测量监听方法@Overridepublic boolean onEstimateSize(int widthEstimatedConfig, int heightEstimatedConfig) {invalidateValues();//测量子组件的大小measureChildren( widthEstimatedConfig,  heightEstimatedConfig);//关联子组件的索引与其布局数据for (int idx = 0; idx < getChildCount(); idx++) {CubeView childView = (CubeView) getComponentAt(idx);addChild(childView, idx, EstimateSpec.getSize(widthEstimatedConfig));}//测量本身大小setEstimatedSize( widthEstimatedConfig,  heightEstimatedConfig);return true;}private void measureChildren(int widthEstimatedConfig, int heightEstimatedConfig) {for (int idx = 0; idx < getChildCount(); idx++) {CubeView childView = (CubeView) getComponentAt(idx);if (childView != null) {LayoutConfig lc = childView.getLayoutConfig();int childWidthMeasureSpec;int childHeightMeasureSpec;if (lc.width == LayoutConfig.MATCH_CONTENT) {childWidthMeasureSpec = EstimateSpec.getSizeWithMode(lc.width, EstimateSpec.NOT_EXCEED);} else if (lc.width == LayoutConfig.MATCH_PARENT) {int parentWidth = EstimateSpec.getSize(widthEstimatedConfig);int childWidth = parentWidth - childView.getMarginLeft() - childView.getMarginRight();childWidthMeasureSpec = EstimateSpec.getSizeWithMode(childWidth, EstimateSpec.PRECISE);} else {childWidthMeasureSpec = EstimateSpec.getSizeWithMode(lc.width, EstimateSpec.PRECISE);}if (lc.height == LayoutConfig.MATCH_CONTENT) {childHeightMeasureSpec = EstimateSpec.getSizeWithMode(lc.height, EstimateSpec.NOT_EXCEED);} else if (lc.height == LayoutConfig.MATCH_PARENT) {int parentHeight = EstimateSpec.getSize(heightEstimatedConfig);int childHeight = parentHeight - childView.getMarginTop() - childView.getMarginBottom();childHeightMeasureSpec = EstimateSpec.getSizeWithMode(childHeight, EstimateSpec.PRECISE);} else {childHeightMeasureSpec = EstimateSpec.getSizeWithMode(lc.height, EstimateSpec.PRECISE);}childView.estimateSize(childWidthMeasureSpec, childHeightMeasureSpec);}}}private void measureSelf(int widthEstimatedConfig, int heightEstimatedConfig) {int widthSpce = EstimateSpec.getMode(widthEstimatedConfig);int heightSpce = EstimateSpec.getMode(heightEstimatedConfig);int widthConfig = 0;switch (widthSpce) {case EstimateSpec.UNCONSTRAINT:case EstimateSpec.PRECISE:int width = EstimateSpec.getSize(widthEstimatedConfig);widthConfig = EstimateSpec.getSizeWithMode(width, EstimateSpec.PRECISE);break;case EstimateSpec.NOT_EXCEED:widthConfig = EstimateSpec.getSizeWithMode(maxWidth, EstimateSpec.PRECISE);break;default:break;}int heightConfig = 0;switch (heightSpce) {case EstimateSpec.UNCONSTRAINT:case EstimateSpec.PRECISE:int height = EstimateSpec.getSize(heightEstimatedConfig);heightConfig = EstimateSpec.getSizeWithMode(height, EstimateSpec.PRECISE);break;case EstimateSpec.NOT_EXCEED:heightConfig = EstimateSpec.getSizeWithMode(maxHeight, EstimateSpec.PRECISE);break;default:break;}setEstimatedSize(widthConfig, heightConfig);}//每个棋子组件的位置及大小@Overridepublic boolean onArrange(int l, int t, int r, int b) {for (int idx = 0; idx < getChildCount(); idx++) {Component childView = getComponentAt(idx);Layout layout = axis.get(idx);if (layout != null) {childView.arrange(layout.positionX, layout.positionY, layout.width, layout.height);}}return true;}private void addChild(CubeView component, int id, int layoutWidth) {Layout layout = new Layout();layout.positionX = xx + component.getMarginLeft();layout.positionY = yy + component.getMarginTop();layout.width = component.getEstimatedWidth();layout.height = component.getEstimatedHeight();if ((xx + layout.width) > layoutWidth) {xx = 0;yy += lastHeight;lastHeight = 0;layout.positionX = xx + component.getMarginLeft();layout.positionY = yy + component.getMarginTop();}axis.put(id, layout);lastHeight = Math.max(lastHeight, layout.height + component.getMarginBottom());xx += layout.width + component.getMarginRight();maxWidth = Math.max(maxWidth, layout.positionX + layout.width + component.getMarginRight());maxHeight = Math.max(maxHeight, layout.positionY + layout.height + component.getMarginBottom());}//点击棋子后进行位置切换public void moveChildToBlank(@org.jetbrains.annotations.NotNull CubeView child) {Position childPos = child.getPosition();Position dstPos = mBlankPos;if (childPos.x == dstPos.x && Math.abs(childPos.y - dstPos.y) == 1 ||childPos.y == dstPos.y && Math.abs(childPos.x - dstPos.x) == 1) {child.setPosition(dstPos);//component中没有对组件进行物理平移的方法//setTranslationX(),setTranslationY()两个方法没有child.setTranslationX(dstPos.x * xx);child.setTranslationY(dstPos.y * yy);mBlankPos = childPos;mStepCounter.add();}checkPosition();}/*** 检查所有格子位置是否正确*/private void checkPosition() {if (mBlankPos.x != mSizeX - 1 || mBlankPos.y != mSizeY - 1) {return;}for (CubeView child : mChildren) {int num = child.getNumber();int x = child.getPosition().x;int y = child.getPosition().y;if (y * mSizeX + x + 1 != num) {return;}}if (mFinishListener != null) {mFinishListener.onFinished(mStepCounter.step);}for (CubeView child : mChildren) {child.setClickable(false);}}public void setOnFinishedListener(OnFinishListener l) {mFinishListener = l;}public interface OnFinishListener {void onFinished(int step);}public int getSizeX() {return mSizeX;}public int getSizeY() {return mSizeY;}/*** 步数统计*/class StepCounter {private int step = 0;void add() {step++;}void clear() {step = 0;}}private StepCounter mStepCounter = new StepCounter();}

棋盘的自定义布局也完成了。棋盘的布局稍微复杂一点,因为需要根据棋盘的大小计算每一个棋子的大小,还需要对棋子进行绑定,尤其是需要对最后一个棋子做空白处理。

然后点击棋子进行棋子的平移,平移后与其位置进行互换。

第二个坑

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PvmUPB0c-1634810943992)(C:\Users\HHCH\AppData\Roaming\Typora\typora-user-images\image-20211021175237912.png)]

点击棋子进行位置平移,因为在API里面没有找到component公共组件下的平移方法,setTranslationX()/setTranslationY()方法,没有办法做到组件的物理位置平移,导致大家看到开头演示的效果,点击后与空白位置坐了切换但是重新对其进行物理位置赋值的时候没有办法去赋值,这个问题困扰了我两天。

现在还是没有解决掉,试着想想是不是可以使用TouchEvent事件一个滑动处理,不做点击事件做滑动事件。

最终现在项目的结构如下:

总结

后面还会继续去完善,以至于到整个功能可以正常去使用,踩坑还是要踩的,总会有收获的时候…

我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=3w24mefasg8wc

鸿蒙小游戏-数字华容道 自定义组件的踩坑记录相关推荐

  1. 数字华容道java_从零开发HarmonyOS(鸿蒙)手机小游戏——数字华容道

    前言 2月16号HarmonyOS2.0手机开发者Beta版已经发布了,作为"1+8+N"战略的重要入口和生态核心,怎么能少得了手机应用开发呢,今天将由深鸿会深大学习小组(Zzt_ ...

  2. 从零开发HarmonyOS(鸿蒙)手机小游戏——数字华容道

    HarmonyOS(鸿蒙)手机第一个小游戏app--数字华容道 前言 概述 正文 创建项目 实现初始界面布局 实现数字的随机打乱 实现滑动或点击调换数字 实现游戏成功界面 源代码 结语 前言 2月16 ...

  3. 如何运行开源游戏?八分音符酱python版踩坑记录

    如何运行开源游戏?八分音符酱python版踩坑记录 如何从零到一搭建一个python游戏?这篇博客将以八分音符酱介绍详细过程. 下载源代码源代码 搭建python环境 参考此网站 此游戏运行基于pyt ...

  4. 微信小程序中如何引用weUI(踩坑记录)

    众所周知WeUI 是一套同微信原生视觉体验一致的基础样式库,由微信官方设计团队为微信内网页和微信小程序量身设计,令用户的使用感知更加统一. 在初学小程序开发时,我也想使用WeUI进行我的小程序开发,但 ...

  5. slider wpf 垂直_WPF自定义Slider样式踩坑记录

    0x01. 概要 WPF 自带的拖动条控件是 Slider, 其默认样式为: 这种风格一般很难和实际的APP匹配, UI肯定会给一种自己的APP风格的拖动条. 最简单的莫过于修改滑块图案, 滑轨颜色等 ...

  6. 鸿蒙手游——数字华容道

    HarmonyOS2.0手机开发者Beta版已经发布了,作为"1+8+N"战略的重要入口和生态核心,怎么能少得了手机应用开发呢,今天将由深鸿会深大学习小组(Zzt_01-23)从零 ...

  7. 鸿蒙小游戏-俄罗斯方块

    作者:225王宗振 前言 为了更好地熟练掌握鸿蒙手机应用开发,查阅资料和算法尝试开发鸿蒙小游戏--俄罗斯方块. 概述 完成鸿蒙小游戏APP在手机上的编译在项目中所使用到的软件为DevEco Studi ...

  8. 手把手教你实现小程序中的自定义组件

    之前做小程序开发的时候,对于开发来说比较头疼的莫过于自定义组件了,当时官方对这方面的文档也只是寥寥几句,一笔带过而已,所以写起来真的是非常非常痛苦!! 好在微信小程序的库从 1.6.3 开始,官方对于 ...

  9. 微信小游戏踩坑记录(二)

    2019独角兽企业重金招聘Python工程师标准>>> 微信视频组件 这次是关于视频组件的,creator的视频组件在小游戏中是没有用的,只能使用小游戏自带的组件.使用小游戏Vide ...

最新文章

  1. SQL分割字符串,SQL按照指定字符分割字符串,SQL处理字符串...
  2. input取消焦点 vue_Vue有什么特性,相对于其他框架都有那些优势!
  3. 【机器视觉】 Halcon代码导出高级语言代码
  4. MMDetection-配置文件
  5. 使用DBUnit集成Spring简化测试
  6. 第三堂:Java程序流程控制
  7. Logstash自定义grok正则匹配规则
  8. Visio2016 安装教程
  9. SAP ABAP 教程大全之 01 面向初学者的 SAP ABAP介绍(含hello world 源码)
  10. 工作流(Workflow)基本介绍
  11. 利用服务器在家远程登录办公室电脑
  12. redis 删除操作命令
  13. 植物免疫研究与抗病虫绿色防控:进展、机遇与挑战
  14. 这个 api 管理工具悄悄开源了,快来看看
  15. Linux——赋予普通用户root的权限
  16. 关于MCU M4内核移植FreeRTOS的笔记
  17. 项目时间管理总结及主要工作清单表
  18. 乔布斯去世并未刺激苹果收藏品大幅升值
  19. 你不知道的JavaScript(上卷)作用域和闭包
  20. 智能手机、平板电脑与GPS定位精度

热门文章

  1. Date Structure: Graph --- Represent graph structure with adjacency list
  2. 【解决方案】如何通过EasyNVR实现连锁餐饮企业远程监控管理?
  3. 从微信扔骰子看iOS应用安全与逆向分析
  4. JAVA计算机毕业设计师资管理系统源码+系统+mysql数据库+lw文档
  5. 唐僧师徒用了手机之后
  6. 计算机网络技术极实践应用,计算机网络技术及在实践中的应用
  7. 无线电 波段划分知识总结
  8. 9.mallplus -uniapp 制作 h5 小程序 app商城
  9. BootStrap三十分钟入门
  10. 机器人操作系统ROS(8)arbotix控制器控制小车运动