MouseUISuperItem.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. /*
  2. * @Author: steveJobs
  3. * @Email: icipiqkm@gmail.com
  4. * @Date: 2020-11-19 01:15:38
  5. * @Last Modified by: steveJobs
  6. * @Last Modified time: 2020-12-04 14:41:01
  7. * @Description: Description
  8. */
  9. import MouseUISuperLayout, { MouseUIChangeBrotherEvnet } from "./MouseUISuperLayout";
  10. const { ccclass, property } = cc._decorator;
  11. @ccclass
  12. export default class MouseUISuperItem extends cc.Component {
  13. private layout: MouseUISuperLayout
  14. private brother: cc.Node
  15. private originSize: cc.Size
  16. private originScale: cc.Vec2
  17. /** 根据可视范围 和 一组item的个数 去掉 边距/间隔 来计算本item的真实宽度 */
  18. private get width() {
  19. if (this.layout.vertical) {
  20. // 垂直滑动时 固定宽度
  21. return (this.layout.accommodWidth - this.layout.spacingWidth) / this.layout.column
  22. } else {
  23. // 水平模式时 宽度随意
  24. return this.node.width * this.layout.getUsedScaleValue(this.node.scaleX)
  25. }
  26. }
  27. /** 根据可视范围 和 一组item的个数 去掉 边距/间隔 来计算本item的真实高度 */
  28. private get height() {
  29. if (this.layout.horizontal) {
  30. // 水平模式时 固定高度
  31. return (this.layout.accommodHeight - this.layout.spacingWidth) / this.layout.column
  32. } else {
  33. // 垂直滑动时 高度随意
  34. return this.node.height * this.layout.getUsedScaleValue(this.node.scaleY)
  35. }
  36. }
  37. onLoad() {
  38. // 向node写入一个方法 省去了先获取组件然后调用的步骤
  39. this.node['watchSelf'] = this.watchSelf.bind(this)
  40. this.node['saveOriginSize'] = this.saveOriginSize.bind(this)
  41. let widget = this.node.getComponent(cc.Widget)
  42. if (widget) {
  43. cc.warn("UISuperItem: item不允许挂cc.Widget组件 请手动移除")
  44. this.node.removeComponent(widget)
  45. }
  46. }
  47. public saveOriginSize() {
  48. this.originSize = cc.size(this.width, this.height)
  49. this.node.setContentSize(this.originSize)
  50. this.originScale = cc.v2(this.node.scaleX, this.node.scaleY)
  51. }
  52. public init(layout: MouseUISuperLayout) {
  53. this.layout = layout
  54. this.layout.node.on(MouseUIChangeBrotherEvnet, this.onChangeBrother, this)
  55. this.saveOriginSize()
  56. this.node.on(cc.Node.EventType.SIZE_CHANGED, this.watchSize, this)
  57. this.node.on(cc.Node.EventType.SCALE_CHANGED, this.watchSize, this)
  58. this.onChangeBrother()
  59. }
  60. onDestroy() {
  61. this.layout.node.off(MouseUIChangeBrotherEvnet, this.onChangeBrother, this)
  62. this.node.off(cc.Node.EventType.SIZE_CHANGED, this.watchSize, this)
  63. this.node.off(cc.Node.EventType.SCALE_CHANGED, this.watchSize, this)
  64. this.unlisten()
  65. }
  66. /**
  67. * 当兄弟节点的顺序变化时 来改变自己监听的对象
  68. * 0,1,2,3,4,5,6,7,8,9 例如列表中共有10个item 0是header 9是footer
  69. * 正序排列时 监听的顺序是 9->8->7->6->5->4->3->2->1->0 0的 brother=null
  70. * 向下填充的逻辑是 0跑到9后面 0=footer 0的brother=9 相对9的位置设置自己 此时1=header
  71. * 向上填充的逻辑是 9跑到0前面 此时9=header 9的brother=null 主动设置自己相对于0前面位置之后 0的brother=9 8=footer
  72. */
  73. private onChangeBrother() {
  74. let _brother = this.layout.getBrotherNode(this.node) //获取我应该监听的那个兄弟
  75. if (_brother?.uuid == this.brother?.uuid) return //如果没有变化 则跳过
  76. this.unlisten() //我的兄弟换人了?先移除我原来的
  77. this.brother = _brother //他是我的兄弟
  78. this.listen() //监听他
  79. this.watchBrother() //相对兄弟节点来设置自己的位置
  80. }
  81. private listen() {
  82. this.brother?.on('leave', this.unlisten, this)
  83. this.brother?.on(cc.Node.EventType.POSITION_CHANGED, this.watchBrother, this)
  84. }
  85. private unlisten() {
  86. this.brother?.off('leave', this.unlisten, this)
  87. this.brother?.off(cc.Node.EventType.POSITION_CHANGED, this.watchBrother, this)
  88. this.brother = null
  89. }
  90. /** 当我的尺寸/缩放改变时 */
  91. private watchSize() {
  92. if (this.layout.column > 1) { //如果是Grid模式 不允许修改尺寸/缩放 强制改回来
  93. this.node.setContentSize(this.originSize)
  94. this.node.setScale(this.originScale)
  95. } else {
  96. if (this.layout.vertical && (this.node.getContentSize().width != this.originSize.width || this.node.scaleX != this.originScale.x)) {
  97. cc.warn("垂直排列不允许修改【宽度】")
  98. this.node.width = this.originSize.width
  99. this.node.scaleX = this.originScale.x
  100. } else if (this.layout.horizontal && (this.node.getContentSize().height != this.originSize.height || this.node.scaleY != this.originScale.y)) {
  101. cc.warn("水平排列不允许修改【高度】")
  102. this.node.height = this.originSize.height
  103. this.node.scaleY = this.originScale.y
  104. }
  105. // 如果我监听了兄弟节点就设置自己相对兄弟节点的位置,否则 我就发送一个位置变化的消息 让监听我的兄弟相对我做出变化
  106. this.brother && this.watchBrother()
  107. this.layout.resetScrollView()
  108. this.node.emit(cc.Node.EventType.POSITION_CHANGED)
  109. }
  110. if (this.node['index'] == 0 && this.layout.isNormalSize) {
  111. this.node.setPosition(this.layout.getGroupHeader(this.node))
  112. }
  113. }
  114. // 设置自己相对于上一个兄弟节点的位置
  115. public watchBrother() {
  116. if (!this.brother) return
  117. if (this.layout.headerToFooter) { //正序排列时
  118. this.headerToFooterRelativeToFooter(this.brother)
  119. } else {//倒序排列时
  120. this.footerToHeaderRelativeToFooter(this.brother)
  121. }
  122. }
  123. private isOutOfBoundary(offset: cc.Vec2) {
  124. if (this.layout.vertical && offset.y == 0) return true
  125. if (this.layout.horizontal && offset.x == 0) return true
  126. return false
  127. }
  128. /** 从下到上排序方向 检查头部是否需要向上填充 */
  129. private footerToHeaderWatchHeader() {
  130. // 如果不是头部一组的任意一个时跳过 比如一组有3个item 只计算 0,1,2
  131. if (this.layout.getSiblingIndex(this.node) >= this.layout.column) return
  132. // 如果此时【尾部】已经是最后一个数据时
  133. let index = this.layout.footer['index'] + 1
  134. if (index >= this.layout.maxItemTotal) {
  135. if (!this.layout.footerLoop || this.layout.scrollToHeaderOrFooter) return
  136. index = 0
  137. }
  138. // 计算超出的偏移量 (从下到上排序方向时 头部在 下尾部在上 检测【头部】是否超出下边框)
  139. let offset = this.layout.isOutOfBoundaryFooter(this.node)
  140. // 没有超出时跳过
  141. if (!this.isOutOfBoundary(offset)) return
  142. // 将自己的数据索引 + 1
  143. this.node['index'] = index
  144. // 发送通知到应用层 刷新显示
  145. this.layout.notifyRefreshItem(this.node)
  146. // 发给监听我的节点 通知我离开了 移除对我的所有监听
  147. this.node.emit("leave")
  148. // 将自己的节点索引设置到尾部
  149. this.layout.setSiblingIndex(this.node, this.layout.children.length - 1)
  150. }
  151. /** 从下到上排序方向 检查尾部是否需要向下填充 */
  152. private footerToHeaderWatchFooter() {
  153. // 如果不是尾部一组的任意一个时跳过 比如一组有3个item 只计算末尾的3个item
  154. if (this.layout.getSiblingIndex(this.node) < this.layout.children.length - this.layout.column) return
  155. // 如果此时【头部】已经是第一个数据时
  156. let index = this.layout.header['index'] - 1
  157. if (index < 0) {
  158. // 如果没有使用无限循环功能 否则不往下走
  159. if (!this.layout.headerLoop || this.layout.scrollToHeaderOrFooter) return
  160. index = this.node['index']
  161. }
  162. // 计算超出的偏移量 (从下到上排序方向时 头部在 下尾部在上 检测【尾部】是否超出下边框)
  163. let offset = this.layout.isOutOfBoundaryHeader(this.node)
  164. // 没有超出时跳过
  165. if (!this.isOutOfBoundary(offset)) return
  166. // 将自己的数据索引 - 1
  167. this.node['index'] = index
  168. // 发送通知到应用层 刷新显示
  169. this.layout.notifyRefreshItem(this.node)
  170. // 发给监听我的兄弟 通知我离开了 移除对我的所有监听
  171. this.node.emit("leave")
  172. // 因为我是尾部 我监听了别人,此时移除我的所有监听 因为我马上就要成为老大 老大不需要监听任何人
  173. this.unlisten()
  174. // 因为我是老大 我不能相对别人来设置自己的相对位置,所以我需要主动设置自己(相对上一个老大的位置来设置自己) 别人都会相对我的位置做出变化
  175. this.footerToHeaderRelativeToHeader(this.layout.header)
  176. // 将自己的节点索引设置到头部
  177. this.layout.setSiblingIndex(this.node, 0)
  178. }
  179. /** 从上到下排序方向 检查头部是否需要向下填充 */
  180. private headerToFooterWatchHeader() {
  181. // 如果不是头部一组的任意一个时跳过 比如一组有3个item 只计算 0,1,2
  182. if (this.layout.getSiblingIndex(this.node) >= this.layout.column) return
  183. // 如果此时【尾部】已经是第一个数据时
  184. let index = this.layout.footer['index'] + 1
  185. if (index > this.layout.maxItemTotal - 1) {
  186. // 如果没有使用无限循环功能 否则不往下走
  187. if (!this.layout.footerLoop || this.layout.scrollToHeaderOrFooter) return
  188. index = 0
  189. }
  190. // 计算超出的偏移量 (从下到上排序方向时 头部在下 尾部在上 检测【尾部】是否超出下边框)
  191. let offset = this.layout.isOutOfBoundaryHeader(this.node)
  192. // 没有超出时跳过
  193. if (!this.isOutOfBoundary(offset)) return
  194. // 将自己的数据索引 + 1
  195. this.node['index'] = index
  196. // 发送通知到应用层 刷新显示
  197. this.layout.notifyRefreshItem(this.node)
  198. // 发给监听我的兄弟 通知我离开了 移除对我的所有监听
  199. this.node.emit("leave")
  200. // 将自己的节点索引设置到尾部
  201. this.layout.setSiblingIndex(this.node, this.layout.children.length - 1)
  202. }
  203. /** 从上到下排序方向 检查尾部是否需要向上填充 */
  204. private headerToFooterWatchFooter() {
  205. // 如果不是尾部一组的任意一个时跳过 比如一组有3个item 只计算末尾的3个item
  206. if (this.layout.getSiblingIndex(this.node) < this.layout.children.length - this.layout.column) return
  207. // 如果此时【头部】已经是第一个数据时
  208. let index = this.layout.header['index'] - 1
  209. if (index < 0) {
  210. // 如果没有使用无限循环功能 否则不往下走
  211. if (!this.layout.headerLoop || this.layout.scrollToHeaderOrFooter) return
  212. index = this.node['index']
  213. }
  214. // 计算超出的偏移量 (从上到下排序方向时 头部在上 尾部在下 检测【尾部】是否超出下边框)
  215. let offset = this.layout.isOutOfBoundaryFooter(this.node)
  216. // 没有超出时跳过
  217. if (!this.isOutOfBoundary(offset)) return
  218. // 将自己的数据索引 - 1
  219. this.node['index'] = index
  220. // 发送通知到应用层 刷新显示
  221. this.layout.notifyRefreshItem(this.node)
  222. // 发给监听我的兄弟 通知我离开了 移除对我的所有监听
  223. this.node.emit("leave")
  224. // 因为我是尾部 我监听了别人,此时移除我的所有监听 因为我马上就要成为老大 老大不需要监听任何人
  225. this.unlisten()
  226. // 因为我是老大 我不能相对别人来设置自己的相对位置,所以我需要主动设置自己(相对上一个老大的位置来设置自己) 别人都会相对我的位置做出变化
  227. this.headerToFooterRelativeToHeader(this.layout.header)
  228. // 将自己的节点索引设置到尾部
  229. this.layout.setSiblingIndex(this.node, 0)
  230. }
  231. /** isScrollToFooter=true 向下滑动 */
  232. public watchSelf(isScrollToFooter: boolean) {
  233. if (isScrollToFooter) {
  234. if (this.layout.headerToFooter) {
  235. // 从【上到下排序】方向 检查【尾部】是否需要向上填充
  236. this.headerToFooterWatchFooter()
  237. } else {
  238. // 从【下到上排序】方向 检查【头部】是否需要向上填充
  239. this.footerToHeaderWatchHeader()
  240. }
  241. } else {
  242. if (this.layout.headerToFooter) {
  243. // 从【上到下排序】方向 检查【头部】是否需要向下填充
  244. this.headerToFooterWatchHeader()
  245. } else {
  246. // 从【下到上排序】方向 检查【尾部】是否需要向下填充
  247. this.footerToHeaderWatchFooter()
  248. }
  249. }
  250. }
  251. /** 从下到上 从右到左 排序方向 设置自己到相对node的头部 */
  252. private footerToHeaderRelativeToHeader(relative: cc.Node) {
  253. let pos = this.node.getPosition()
  254. // 从下到上
  255. if (this.layout.vertical) {
  256. if (this.layout.isGroupHeader(relative)) {
  257. pos.x = this.layout.getGroupFooter(this.node).x
  258. pos.y = this.layout.getGroupBottomY(this.node, relative)
  259. } else {
  260. pos.x = this.layout.getGroupLeftX(this.node, relative)
  261. pos.y = relative.y
  262. }
  263. if (this.node['index'] == 0) {
  264. pos.x = this.layout.getGroupHeader(this.node).x
  265. }
  266. } else {
  267. // 从右到左
  268. if (this.layout.isGroupHeader(relative)) {
  269. pos.x = this.layout.getGroupRightX(this.node, relative)
  270. pos.y = this.layout.getGroupFooter(this.node).y
  271. } else {
  272. pos.x = relative.x
  273. pos.y = this.layout.getGroupTopY(this.node, relative)
  274. }
  275. if (this.node['index'] == 0) {
  276. pos.y = this.layout.getGroupHeader(this.node).y
  277. }
  278. }
  279. this.node.setPosition(pos)
  280. }
  281. /** 从下到上 从右到左 排序方向 设置自己到相对node的尾部 */
  282. private footerToHeaderRelativeToFooter(relative: cc.Node) {
  283. let pos = this.node.getPosition()
  284. // 从下到上
  285. if (this.layout.vertical) {
  286. if (this.layout.isGroupFooter(relative)) {
  287. pos.x = this.layout.getGroupHeader(this.node).x
  288. pos.y = this.layout.getGroupTopY(this.node, relative)
  289. } else {
  290. pos.x = this.layout.getGroupRightX(this.node, relative)
  291. pos.y = relative.y
  292. }
  293. } else {
  294. // 从右到左
  295. if (this.layout.isGroupFooter(relative)) {
  296. pos.x = this.layout.getGroupLeftX(this.node, relative)
  297. pos.y = this.layout.getGroupHeader(this.node).y
  298. } else {
  299. pos.x = relative.x
  300. pos.y = this.layout.getGroupBottomY(this.node, relative)
  301. }
  302. }
  303. this.node.setPosition(pos)
  304. }
  305. /** 从上到下 从左到右 排序方向 设置自己到相对node的头部 */
  306. private headerToFooterRelativeToHeader(relative: cc.Node) {
  307. let pos = this.node.getPosition()
  308. // 从上到下
  309. if (this.layout.vertical) {
  310. if (this.layout.isGroupHeader(relative)) {
  311. pos.x = this.layout.getGroupFooter(this.node).x
  312. pos.y = this.layout.getGroupTopY(this.node, relative)
  313. } else {
  314. pos.x = this.layout.getGroupLeftX(this.node, relative)
  315. pos.y = relative.y
  316. }
  317. if (this.node['index'] == 0) {
  318. pos.x = this.layout.getGroupHeader(this.node).x
  319. }
  320. } else {
  321. // 从左到右
  322. if (this.layout.isGroupHeader(relative)) {
  323. pos.x = this.layout.getGroupLeftX(this.node, relative)
  324. pos.y = this.layout.getGroupFooter(this.node).y
  325. } else {
  326. pos.x = relative.x
  327. pos.y = this.layout.getGroupTopY(this.node, relative)
  328. }
  329. if (this.node['index'] == 0) {
  330. pos.y = this.layout.getGroupHeader(this.node).y
  331. }
  332. }
  333. this.node.setPosition(pos)
  334. }
  335. /** 从上到下 从左到右 排序方向 设置自己到相对node尾部 */
  336. private headerToFooterRelativeToFooter(relative: cc.Node) {
  337. let pos = this.node.getPosition()
  338. // 从上到下
  339. if (this.layout.vertical) {
  340. if (this.layout.isGroupFooter(relative)) {
  341. pos.x = this.layout.getGroupHeader(this.node).x
  342. pos.y = this.layout.getGroupBottomY(this.node, relative)
  343. } else {
  344. pos.x = this.layout.getGroupRightX(this.node, relative)
  345. pos.y = relative.y
  346. }
  347. } else {
  348. // 从左到右
  349. if (this.layout.isGroupFooter(relative)) {
  350. pos.x = this.layout.getGroupRightX(this.node, relative)
  351. pos.y = this.layout.getGroupHeader(this.node).y
  352. } else {
  353. pos.x = relative.x
  354. pos.y = this.layout.getGroupBottomY(this.node, relative)
  355. }
  356. }
  357. this.node.setPosition(pos)
  358. }
  359. }