ScrollOut.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import ScrollInner from "./ScrollInner";
  2. const { ccclass, property, menu } = cc._decorator;
  3. @ccclass
  4. @menu('Scroll/ScrollOut')
  5. export default class ScrollOut extends cc.ScrollView {
  6. @property(ScrollInner)
  7. scrollInnerViews: ScrollInner[] = []
  8. scrollInnerView: ScrollInner;
  9. canScroll: boolean = true
  10. isFirst: boolean = true;
  11. m_PlanDir: number;
  12. onLoad() {
  13. this.m_PlanDir = null;
  14. this.scrollInnerViews.forEach(inner => {
  15. inner.setOuterScrollView(this);
  16. });
  17. }
  18. //是否为子物体
  19. //注意,这里递归, 如果child藏的太深, 可能影响效率。其实也还好,只是开始滑动时执行一次。
  20. _isHisChild(child, undeterminedParent) {
  21. if (child == undeterminedParent) {
  22. return true;
  23. }
  24. if (child.parent != null) {
  25. if (child.parent == undeterminedParent) {
  26. return true;
  27. } else {
  28. return this._isHisChild(child.parent, undeterminedParent);
  29. }
  30. }
  31. return false;
  32. }
  33. //判断Target是否是InnerScrollView的子物体, 如果是,就返回这个InnerScrollView。
  34. //注意,这里遍历所有InnerScrollView, 如果InnerScrollView数量太多,可能影响效率。其实也还好,只是开始滑动时执行一次。
  35. _findScrollingInnerSv(target) {
  36. for (let i = 0; i < this.scrollInnerViews.length; i++) {
  37. let isHisChild = this._isHisChild(target, this.scrollInnerViews[i].node);
  38. if (isHisChild) {
  39. return this.scrollInnerViews[i];
  40. }
  41. }
  42. return null;
  43. }
  44. isSame() {
  45. return (this.horizontal && this.scrollInnerView.horizontal) || (this.vertical && this.scrollInnerView.vertical)
  46. }
  47. //#region 重写cc.ScrollView的方法
  48. _hasNestedViewGroup(event, captureListeners) {
  49. if (event.eventPhase !== cc.Event.CAPTURING_PHASE) return;
  50. //不阻止out上onTouch事件执行。
  51. return false;
  52. }
  53. _onTouchBegan(event, captureListeners) {
  54. if (!this.enabledInHierarchy) return;
  55. if (this._hasNestedViewGroup(event, captureListeners)) return;
  56. //重置计划方向
  57. this.m_PlanDir = null;
  58. this.scrollInnerView = null;
  59. this.isFirst = true
  60. var touch = event.touch;
  61. if (this.content) {
  62. this["_handlePressLogic"](touch);
  63. }
  64. this["_touchMoved"] = false;
  65. this["_stopPropagationIfTargetIsMe"](event);
  66. }
  67. _onTouchMoved(event, captureListeners) {
  68. // 答疑:为什么确定 scrollInnerView, 不用captureListeners, 而要用this._findScrollingInnerSv?
  69. // 因为,在子ScrollView上拖动时, captureListeners中并不包含该子ScrollView本身。
  70. // cc.log("----------------------------");
  71. // captureListeners.forEach((captureListener) => {
  72. // cc.log(captureListener.name);
  73. // });
  74. if (!this.enabledInHierarchy) return;
  75. if (this._hasNestedViewGroup(event, captureListeners)) return;
  76. var touch = event.touch;
  77. var deltaMove = touch.getLocation().sub(touch.getStartLocation());
  78. //在滑动时, 设置开始时滑动的方向为计划方向
  79. //为什么在Outer中做这件事?
  80. //因为Outer的_onTouchMoved比Inner的早执行, 如果在Inner里做, Outer中就得忽略一帧,体验可能会不好。
  81. if (deltaMove.mag() > 7) {
  82. this.scrollInnerView = this._findScrollingInnerSv(event.target);
  83. if (this.scrollInnerView != null) {
  84. let contentSize = this.scrollInnerView.content.getContentSize();
  85. let scrollViewSize = this.scrollInnerView.node.getContentSize();
  86. if ((this.scrollInnerView.vertical && (contentSize.height > scrollViewSize.height)) || (this.scrollInnerView.horizontal && (contentSize.width > scrollViewSize.width))) {
  87. if (this.m_PlanDir == null) {
  88. this.m_PlanDir = Math.abs(deltaMove.x) > Math.abs(deltaMove.y) ? 1 : -1; //1水平 -1垂直
  89. }
  90. if (this.m_PlanDir == 1) {
  91. if (this.isSame()) {
  92. if (this.isFirst) {
  93. this.isFirst = false
  94. //回滚0.1 否则走不下去
  95. if (this.scrollInnerView.getScrollOffset().x >= 0) {
  96. this.scrollInnerView.content.x = -0.1
  97. return
  98. }
  99. if (this.scrollInnerView.getScrollOffset().x <= -this.scrollInnerView.getMaxScrollOffset().x) {
  100. this.scrollInnerView.content.x = -this.scrollInnerView.getMaxScrollOffset().x + 0.1
  101. return
  102. }
  103. }
  104. this.canScroll = this.horizontal && (this.scrollInnerView.getScrollOffset().x >= 0 || this.scrollInnerView.getScrollOffset().x <= -this.scrollInnerView.getMaxScrollOffset().x)
  105. this.scrollInnerView.canScroll = !this.canScroll
  106. } else {
  107. this.canScroll = this.horizontal
  108. this.scrollInnerView.canScroll = this.scrollInnerView.horizontal
  109. }
  110. } else {
  111. if (this.isSame()) {
  112. if (this.isFirst) {
  113. this.isFirst = false
  114. //回滚0.1 否则走不下去
  115. if (this.scrollInnerView.getScrollOffset().y <= 0) {
  116. this.scrollInnerView.content.y = 0.1
  117. return
  118. }
  119. if (this.scrollInnerView.getScrollOffset().y >= this.scrollInnerView.getMaxScrollOffset().y) {
  120. this.scrollInnerView.content.y = this.scrollInnerView.getMaxScrollOffset().y - 0.1
  121. return
  122. }
  123. }
  124. this.canScroll = this.vertical && this.scrollInnerView.getScrollOffset().y <= 0 || this.scrollInnerView.getScrollOffset().y >= this.scrollInnerView.getMaxScrollOffset().y
  125. this.scrollInnerView.canScroll = !this.canScroll
  126. } else {
  127. this.canScroll = this.vertical
  128. this.scrollInnerView.canScroll = this.scrollInnerView.vertical
  129. }
  130. }
  131. } else {
  132. this.canScroll = true;
  133. this.scrollInnerView.canScroll = false
  134. }
  135. } else {
  136. this.canScroll = true;
  137. }
  138. }
  139. if (this.content) {
  140. if (this.canScroll) {
  141. this["_handleMoveLogic"](touch);
  142. }
  143. }
  144. if (!this.cancelInnerEvents) {
  145. return;
  146. }
  147. //只取消会捕获事件的直接子物体(如Button)上的事件
  148. if (this.scrollInnerView == null) {
  149. if (deltaMove.mag() > 7) {
  150. if (!this["_touchMoved"] && event.target !== this.node) {
  151. var cancelEvent = new cc.Event.EventTouch(event.getTouches(), event.bubbles);
  152. cancelEvent.type = cc.Node.EventType.TOUCH_CANCEL;
  153. cancelEvent.touch = event.touch;
  154. cancelEvent["simulate"] = true;
  155. event.target.dispatchEvent(cancelEvent);
  156. this["_touchMoved"] = true;
  157. }
  158. }
  159. this["_stopPropagationIfTargetIsMe"](event);
  160. }
  161. }
  162. _onTouchEnded(event, captureListeners) {
  163. if (!this.enabledInHierarchy) return;
  164. if (this._hasNestedViewGroup(event, captureListeners)) return;
  165. this["_dispatchEvent"]('touch-up');
  166. let touch = event.touch;
  167. if (this.content) {
  168. this["_handleReleaseLogic"](touch);
  169. }
  170. if (this["_touchMoved"]) {
  171. event.stopPropagation();
  172. } else {
  173. this["_stopPropagationIfTargetIsMe"](event);
  174. }
  175. }
  176. _onTouchCancelled(event, captureListeners) {
  177. if (!this.enabledInHierarchy) return;
  178. if (this._hasNestedViewGroup(event, captureListeners)) return;
  179. // Filte touch cancel event send from self
  180. if (!event.simulate) {
  181. let touch = event.touch;
  182. if (this.content) {
  183. this["_handleReleaseLogic"](touch);
  184. }
  185. }
  186. this["_stopPropagationIfTargetIsMe"](event);
  187. }
  188. //#endregion
  189. }