PanGrid.ets 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. import curves from '@ohos.curves'
  2. import { util } from '@kit.ArkTS'
  3. import { YTLog } from '../../../../../Index'
  4. @Component
  5. export struct PanGrid {
  6. /**
  7. * @description 需要渲染的数组
  8. * @type extends BasicModel
  9. */
  10. @Prop @Require @Watch('changeList') displayArr: ESObject[]
  11. /**
  12. * @description 需要传入渲染的结构
  13. */
  14. @BuilderParam @Require gridItem: (_: ESObject, index: number) => void
  15. /**
  16. * @description grid的列数 两列示例: '1fr 1fr' 不支持动态传入
  17. */
  18. @Require columnsTemplate: string
  19. sortOrder: string = 'sortOrder'
  20. //完成
  21. @Require onFinishCallBack: (arr: ESObject[]) => void
  22. @State private dragItemId: number | string = -1
  23. @State private scaleItemId: number | string = -1
  24. @State private offsetX: number = 0
  25. @State private offsetY: number = 0
  26. private columnsCount: number = 0
  27. private dragRefOffsetx: number = 0
  28. private dragRefOffsety: number = 0
  29. private FIX_VP_X: number = 108
  30. private FIX_VP_Y: number = 120
  31. private firstGetArea: boolean = true
  32. private isMove: boolean = false
  33. //用户传入的数组中是否有id
  34. private scroller: Scroller = new Scroller()
  35. //使用回调获取Scroller空值grid行为
  36. onPanGridAppear = (_: Scroller) => {
  37. }
  38. aboutToAppear() {
  39. this.columnsCount = this.columnsTemplate.split(' ').length
  40. this.onPanGridAppear(this.scroller)
  41. }
  42. changeList() {
  43. this.displayArr.forEach((item: ESObject) => {
  44. if (item.id == undefined) {
  45. item.id = util.generateRandomUUID()
  46. item.haveId = false
  47. }
  48. })
  49. if (this.isMove) {
  50. return
  51. }
  52. // this.resetStates()
  53. }
  54. build() {
  55. Column() {
  56. Grid(this.scroller) {
  57. ForEach(this.displayArr, (item: ESObject, index: number) => {
  58. GridItem() {
  59. this.gridItem(item, index)
  60. }
  61. .backgroundColor(Color.Transparent)
  62. .shadow(this.dragItemId == item.id ? {
  63. radius: 20, // 模糊半径(对应 20dp)
  64. color: '#0F000000', // 阴影颜色
  65. offsetX: 0, // 水平偏移
  66. offsetY: 0 // 垂直偏移
  67. } : undefined)
  68. // .borderRadius(16)
  69. .onAreaChange(this.firstGetArea ? (_, newArea) => {
  70. this.FIX_VP_X = newArea.width as number
  71. this.FIX_VP_Y = newArea.height as number
  72. } : undefined)
  73. // 指定固定GridItem不响应事件
  74. // .hitTestBehavior(this.isDraggable(this.displayArr.indexOf(item)) ? HitTestMode.Default : HitTestMode.None)
  75. // .scale({ x: this.scaleItemId == item.id ? 1.05 : 1, y: this.scaleItemId == item.id ? 1.05 : 1 })
  76. .zIndex(this.dragItemId == item.id ? 1 : 0)
  77. .translate(this.dragItemId == item.id ? { x: this.offsetX, y: this.offsetY } : { x: 0, y: 0 })
  78. .gesture(
  79. // 以下组合手势为顺序识别,当长按手势事件未正常触发时则不会触发拖动手势事件
  80. GestureGroup(GestureMode.Sequence,
  81. LongPressGesture({ repeat: true })
  82. .onAction((event?: GestureEvent) => {
  83. this.getUIContext()?.animateTo({ curve: Curve.Friction, duration: 300 }, () => {
  84. this.scaleItemId = item.id
  85. })
  86. })
  87. .onActionEnd(() => {
  88. this.getUIContext()?.animateTo({ curve: Curve.Friction, duration: 300 }, () => {
  89. this.scaleItemId = -1
  90. })
  91. }),
  92. PanGesture({ fingers: 1, direction: null, distance: 0 })
  93. .onActionStart(() => {
  94. this.isMove = true
  95. this.dragItemId = item.id
  96. this.dragRefOffsetx = 0
  97. this.dragRefOffsety = 0
  98. })
  99. .onActionUpdate((event: GestureEvent) => {
  100. this.offsetY = event.offsetY - this.dragRefOffsety
  101. this.offsetX = event.offsetX - this.dragRefOffsetx
  102. this.getUIContext()?.animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
  103. let index = this.displayArr.findIndex((item: ESObject) => item.id == this.dragItemId)
  104. if (this.offsetY >= this.FIX_VP_Y / 2 && (this.offsetX <= 44 && this.offsetX >= -44) &&
  105. !this.isLastRow(index)) {
  106. //向下滑
  107. this.down(index)
  108. } else if (this.offsetY <= -this.FIX_VP_Y / 2 && (this.offsetX <= 44 && this.offsetX >= -44) &&
  109. !this.isFirstRow(index)) {
  110. //向上滑
  111. this.up(index)
  112. } else if (this.offsetX >= this.FIX_VP_X / 2 && (this.offsetY <= 50 && this.offsetY >= -50) &&
  113. !this.isLastColumn(index)) {
  114. //向右滑
  115. this.right(index)
  116. } else if (this.offsetX <= -this.FIX_VP_X / 2 && (this.offsetY <= 50 && this.offsetY >= -50) &&
  117. !this.isFirstColumn(index)) {
  118. //向左滑
  119. this.left(index)
  120. } else if (this.offsetX >= this.FIX_VP_X / 2 && this.offsetY >= this.FIX_VP_Y / 2 &&
  121. !this.isLastRow(index) && !this.isLastColumn(index)) {
  122. //向右下滑
  123. this.lowerRight(index)
  124. } else if (this.offsetX >= this.FIX_VP_X / 2 && this.offsetY <= -this.FIX_VP_Y / 2 &&
  125. !this.isFirstRow(index) && !this.isLastColumn(index)) {
  126. //向右上滑
  127. this.upperRight(index)
  128. } else if (this.offsetX <= -this.FIX_VP_X / 2 && this.offsetY >= this.FIX_VP_Y / 2 &&
  129. !this.isLastRow(index) && !this.isFirstColumn(index)) {
  130. //向左下滑
  131. this.lowerLeft(index)
  132. } else if (this.offsetX <= -this.FIX_VP_X / 2 && this.offsetY <= -this.FIX_VP_Y / 2 &&
  133. !this.isFirstRow(index) && !this.isFirstColumn(index)) {
  134. //向左上滑
  135. this.upperLeft(index)
  136. } else if (this.offsetX >= this.FIX_VP_X / 2 && this.offsetY >= this.FIX_VP_Y / 2 &&
  137. this.isLastRow(index) && this.isFirstColumn(index)) {
  138. //向右下滑(右下角为空)
  139. this.down(index)
  140. }
  141. }
  142. )
  143. })
  144. .onActionEnd(() => {
  145. this.getUIContext()?.animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
  146. this.dragItemId = -1
  147. })
  148. this.getUIContext()?.animateTo({
  149. curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150
  150. }, () => {
  151. this.scaleItemId = -1
  152. })
  153. // 更新sortOrder
  154. this.updateSortOrder()
  155. this.isMove = false
  156. //返回排序后的数组
  157. this.onFinishCallBack((() => {
  158. YTLog.info(this.displayArr, '删除id')
  159. this.displayArr.forEach((item: ESObject) => {
  160. //排除undefined的情况
  161. if (item.haveId == false) {
  162. item.id = undefined
  163. }
  164. item.haveId = undefined
  165. })
  166. YTLog.info(this.displayArr, '删除id')
  167. return this.displayArr
  168. })())
  169. })
  170. )
  171. .onCancel(() => {
  172. this.getUIContext()?.animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
  173. this.dragItemId = -1
  174. })
  175. this.getUIContext()?.animateTo({
  176. curve: curves.interpolatingSpring(14, 1, 170, 17)
  177. }, () => {
  178. this.scaleItemId = -1
  179. })
  180. })
  181. )
  182. }, (item: ESObject, _: number) => {
  183. // 使用更稳定的key,优先使用id,其次使用sortOrder,最后使用JSON.stringify
  184. return JSON.stringify(item);
  185. })
  186. }
  187. .width('100%%')
  188. .editMode(true)
  189. .scrollBar(BarState.Off)
  190. .columnsTemplate(this.columnsTemplate)
  191. .rowsGap(8)
  192. }
  193. .width('100%')
  194. .padding({ top: 5 })
  195. }
  196. // 重置所有状态
  197. private resetStates(): void {
  198. this.dragItemId = -1
  199. this.scaleItemId = -1
  200. this.offsetX = 0
  201. this.offsetY = 0
  202. this.dragRefOffsetx = 0
  203. this.dragRefOffsety = 0
  204. }
  205. private itemMove(index: number, newIndex: number): void {
  206. if (!this.isDraggable(newIndex)) {
  207. return
  208. }
  209. let tmp: ESObject[] = this.displayArr.splice(index, 1)
  210. this.displayArr.splice(newIndex, 0, tmp[0])
  211. }
  212. //向下滑
  213. private down(index: number): void {
  214. // 指定固定GridItem不响应事件
  215. if (!this.isDraggable(index + this.columnsCount)) {
  216. return
  217. }
  218. this.offsetY -= this.FIX_VP_Y
  219. this.dragRefOffsety += this.FIX_VP_Y
  220. this.itemMove(index, index + this.columnsCount)
  221. }
  222. //向上滑
  223. private up(index: number): void {
  224. if (!this.isDraggable(index - this.columnsCount)) {
  225. return
  226. }
  227. this.offsetY += this.FIX_VP_Y
  228. this.dragRefOffsety -= this.FIX_VP_Y
  229. this.itemMove(index, index - this.columnsCount)
  230. }
  231. //向左滑
  232. private left(index: number): void {
  233. if (!this.isDraggable(index - 1)) {
  234. return
  235. }
  236. this.offsetX += this.FIX_VP_X
  237. this.dragRefOffsetx -= this.FIX_VP_X
  238. this.itemMove(index, index - 1)
  239. }
  240. //向右滑
  241. private right(index: number): void {
  242. if (!this.isDraggable(index + 1)) {
  243. return
  244. }
  245. this.offsetX -= this.FIX_VP_X
  246. this.dragRefOffsetx += this.FIX_VP_X
  247. this.itemMove(index, index + 1)
  248. }
  249. //向右下滑
  250. private lowerRight(index: number): void {
  251. if (!this.isDraggable(index + this.columnsCount + 1)) {
  252. return
  253. }
  254. this.offsetX -= this.FIX_VP_X
  255. this.dragRefOffsetx += this.FIX_VP_X
  256. this.offsetY -= this.FIX_VP_Y
  257. this.dragRefOffsety += this.FIX_VP_Y
  258. this.itemMove(index, index + this.columnsCount + 1)
  259. }
  260. //向右上滑
  261. private upperRight(index: number): void {
  262. if (!this.isDraggable(index - this.columnsCount + 1)) {
  263. return
  264. }
  265. this.offsetX -= this.FIX_VP_X
  266. this.dragRefOffsetx += this.FIX_VP_X
  267. this.offsetY += this.FIX_VP_Y
  268. this.dragRefOffsety -= this.FIX_VP_Y
  269. this.itemMove(index, index - this.columnsCount + 1)
  270. }
  271. //向左下滑
  272. private lowerLeft(index: number): void {
  273. const targetIndex = index + this.columnsCount - 1; // 正确目标索引是 2
  274. if (!this.isDraggable(targetIndex)) { // 检查目标索引而非 index+2
  275. return
  276. }
  277. this.offsetX += this.FIX_VP_X
  278. this.dragRefOffsetx -= this.FIX_VP_X
  279. this.offsetY -= this.FIX_VP_Y
  280. this.dragRefOffsety += this.FIX_VP_Y
  281. this.itemMove(index, targetIndex) // 使用计算后的目标索引
  282. }
  283. //向左上滑
  284. private upperLeft(index: number): void {
  285. if (!this.isDraggable(index - this.columnsCount - 1)) {
  286. return
  287. }
  288. this.offsetX += this.FIX_VP_X
  289. this.dragRefOffsetx -= this.FIX_VP_X
  290. this.offsetY += this.FIX_VP_Y
  291. this.dragRefOffsety -= this.FIX_VP_Y
  292. this.itemMove(index, index - this.columnsCount - 1)
  293. }
  294. private isDraggable(index: number): boolean {
  295. return index < this.displayArr.length
  296. }
  297. // 新增辅助方法:检查当前项是否在最后一行
  298. private isLastRow(index: number): boolean {
  299. const rowIndex = Math.floor(index / this.columnsCount);
  300. const totalRows = Math.ceil(this.displayArr.length / this.columnsCount);
  301. return rowIndex === totalRows - 1;
  302. }
  303. // 检查是否在第一行
  304. private isFirstRow(index: number): boolean {
  305. return Math.floor(index / this.columnsCount) === 0;
  306. }
  307. // 检查是否在最后一列
  308. private isLastColumn(index: number): boolean {
  309. return index % this.columnsCount === this.columnsCount - 1;
  310. }
  311. // 检查是否在第一列
  312. private isFirstColumn(index: number): boolean {
  313. return index % this.columnsCount === 0;
  314. }
  315. // 更新sortOrder字段
  316. private updateSortOrder(): void {
  317. for (let i = 0; i < this.displayArr.length; i++) {
  318. this.displayArr[i][this.sortOrder] = (i + 1).toString();
  319. }
  320. }
  321. }