refresher.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  1. // [z-paging]下拉刷新view模块
  2. import u from '.././z-paging-utils'
  3. import c from '.././z-paging-constant'
  4. import Enum from '.././z-paging-enum'
  5. // #ifdef APP-NVUE
  6. const weexAnimation = weex.requireModule('animation');
  7. // #endif
  8. export default {
  9. props: {
  10. // 下拉刷新的主题样式,支持black,white,默认black
  11. refresherThemeStyle: {
  12. type: String,
  13. default: u.gc('refresherThemeStyle', '')
  14. },
  15. // 自定义下拉刷新中左侧图标的样式
  16. refresherImgStyle: {
  17. type: Object,
  18. default: u.gc('refresherImgStyle', {})
  19. },
  20. // 自定义下拉刷新中右侧状态描述文字的样式
  21. refresherTitleStyle: {
  22. type: Object,
  23. default: u.gc('refresherTitleStyle', {})
  24. },
  25. // 自定义下拉刷新中右侧最后更新时间文字的样式(show-refresher-update-time为true时有效)
  26. refresherUpdateTimeStyle: {
  27. type: Object,
  28. default: u.gc('refresherUpdateTimeStyle', {})
  29. },
  30. // 在微信小程序和QQ小程序中,是否实时监听下拉刷新中进度,默认为否
  31. watchRefresherTouchmove: {
  32. type: Boolean,
  33. default: u.gc('watchRefresherTouchmove', false)
  34. },
  35. // 底部加载更多的主题样式,支持black,white,默认black
  36. loadingMoreThemeStyle: {
  37. type: String,
  38. default: u.gc('loadingMoreThemeStyle', '')
  39. },
  40. // 是否只使用下拉刷新,设置为true后将关闭mounted自动请求数据、关闭滚动到底部加载更多,强制隐藏空数据图。默认为否
  41. refresherOnly: {
  42. type: Boolean,
  43. default: u.gc('refresherOnly', false)
  44. },
  45. // 自定义下拉刷新默认状态下回弹动画时间,单位为毫秒,默认为100毫秒,nvue无效
  46. refresherDefaultDuration: {
  47. type: [Number, String],
  48. default: u.gc('refresherDefaultDuration', 100)
  49. },
  50. // 自定义下拉刷新结束以后延迟回弹的时间,单位为毫秒,默认为0
  51. refresherCompleteDelay: {
  52. type: [Number, String],
  53. default: u.gc('refresherCompleteDelay', 0)
  54. },
  55. // 自定义下拉刷新结束回弹动画时间,单位为毫秒,默认为300毫秒(refresherEndBounceEnabled为false时,refresherCompleteDuration为设定值的1/3),nvue无效
  56. refresherCompleteDuration: {
  57. type: [Number, String],
  58. default: u.gc('refresherCompleteDuration', 300)
  59. },
  60. // 自定义下拉刷新中是否允许列表滚动,默认为是
  61. refresherRefreshingScrollable: {
  62. type: Boolean,
  63. default: u.gc('refresherRefreshingScrollable', true)
  64. },
  65. // 自定义下拉刷新结束状态下是否允许列表滚动,默认为否
  66. refresherCompleteScrollable: {
  67. type: Boolean,
  68. default: u.gc('refresherCompleteScrollable', false)
  69. },
  70. // 是否使用自定义的下拉刷新,默认为是,即使用z-paging的下拉刷新。设置为false即代表使用uni scroll-view自带的下拉刷新,h5、App、微信小程序以外的平台不支持uni scroll-view自带的下拉刷新
  71. useCustomRefresher: {
  72. type: Boolean,
  73. default: u.gc('useCustomRefresher', true)
  74. },
  75. // 自定义下拉刷新下拉帧率,默认为40,过高可能会出现抖动问题
  76. refresherFps: {
  77. type: [Number, String],
  78. default: u.gc('refresherFps', 40)
  79. },
  80. // 自定义下拉刷新允许触发的最大下拉角度,默认为40度,当下拉角度小于设定值时,自定义下拉刷新动画不会被触发
  81. refresherMaxAngle: {
  82. type: [Number, String],
  83. default: u.gc('refresherMaxAngle', 40)
  84. },
  85. // 自定义下拉刷新的角度由未达到最大角度变到达到最大角度时,是否继续下拉刷新手势,默认为否
  86. refresherAngleEnableChangeContinued: {
  87. type: Boolean,
  88. default: u.gc('refresherAngleEnableChangeContinued', false)
  89. },
  90. // 自定义下拉刷新默认状态下的文字
  91. refresherDefaultText: {
  92. type: [String, Object],
  93. default: u.gc('refresherDefaultText', null)
  94. },
  95. // 自定义下拉刷新松手立即刷新状态下的文字
  96. refresherPullingText: {
  97. type: [String, Object],
  98. default: u.gc('refresherPullingText', null)
  99. },
  100. // 自定义下拉刷新刷新中状态下的文字
  101. refresherRefreshingText: {
  102. type: [String, Object],
  103. default: u.gc('refresherRefreshingText', null)
  104. },
  105. // 自定义下拉刷新刷新结束状态下的文字
  106. refresherCompleteText: {
  107. type: [String, Object],
  108. default: u.gc('refresherCompleteText', null)
  109. },
  110. // 自定义继续下拉进入二楼文字
  111. refresherGoF2Text: {
  112. type: [String, Object],
  113. default: u.gc('refresherGoF2Text', null)
  114. },
  115. // 自定义下拉刷新默认状态下的图片
  116. refresherDefaultImg: {
  117. type: String,
  118. default: u.gc('refresherDefaultImg', null)
  119. },
  120. // 自定义下拉刷新松手立即刷新状态下的图片,默认与refresherDefaultImg一致
  121. refresherPullingImg: {
  122. type: String,
  123. default: u.gc('refresherPullingImg', null)
  124. },
  125. // 自定义下拉刷新刷新中状态下的图片
  126. refresherRefreshingImg: {
  127. type: String,
  128. default: u.gc('refresherRefreshingImg', null)
  129. },
  130. // 自定义下拉刷新刷新结束状态下的图片
  131. refresherCompleteImg: {
  132. type: String,
  133. default: u.gc('refresherCompleteImg', null)
  134. },
  135. // 自定义下拉刷新刷新中状态下是否展示旋转动画
  136. refresherRefreshingAnimated: {
  137. type: Boolean,
  138. default: u.gc('refresherRefreshingAnimated', true)
  139. },
  140. // 是否开启自定义下拉刷新刷新结束回弹效果,默认为是
  141. refresherEndBounceEnabled: {
  142. type: Boolean,
  143. default: u.gc('refresherEndBounceEnabled', true)
  144. },
  145. // 是否开启自定义下拉刷新,默认为是
  146. refresherEnabled: {
  147. type: Boolean,
  148. default: u.gc('refresherEnabled', true)
  149. },
  150. // 设置自定义下拉刷新阈值,默认为80rpx
  151. refresherThreshold: {
  152. type: [Number, String],
  153. default: u.gc('refresherThreshold', '80rpx')
  154. },
  155. // 设置系统下拉刷新默认样式,支持设置 black,white,none,none 表示不使用默认样式,默认为black
  156. refresherDefaultStyle: {
  157. type: String,
  158. default: u.gc('refresherDefaultStyle', 'black')
  159. },
  160. // 设置自定义下拉刷新区域背景
  161. refresherBackground: {
  162. type: String,
  163. default: u.gc('refresherBackground', 'transparent')
  164. },
  165. // 设置固定的自定义下拉刷新区域背景
  166. refresherFixedBackground: {
  167. type: String,
  168. default: u.gc('refresherFixedBackground', 'transparent')
  169. },
  170. // 设置固定的自定义下拉刷新区域高度,默认为0
  171. refresherFixedBacHeight: {
  172. type: [Number, String],
  173. default: u.gc('refresherFixedBacHeight', 0)
  174. },
  175. // 设置自定义下拉刷新下拉超出阈值后继续下拉位移衰减的比例,范围0-1,值越大代表衰减越多。默认为0.65(nvue无效)
  176. refresherOutRate: {
  177. type: Number,
  178. default: u.gc('refresherOutRate', 0.65)
  179. },
  180. // 是否开启下拉进入二楼功能,默认为否
  181. refresherF2Enabled: {
  182. type: Boolean,
  183. default: u.gc('refresherF2Enabled', false)
  184. },
  185. // 下拉进入二楼阈值,默认为200rpx
  186. refresherF2Threshold: {
  187. type: [Number, String],
  188. default: u.gc('refresherF2Threshold', '200rpx')
  189. },
  190. // 下拉进入二楼动画时间,单位为毫秒,默认为200毫秒
  191. refresherF2Duration: {
  192. type: [Number, String],
  193. default: u.gc('refresherF2Duration', 200)
  194. },
  195. // 下拉进入二楼状态松手后是否弹出二楼,默认为是
  196. showRefresherF2: {
  197. type: Boolean,
  198. default: u.gc('showRefresherF2', true)
  199. },
  200. // 设置自定义下拉刷新下拉时实际下拉位移与用户下拉距离的比值,默认为0.75,即代表若用户下拉10px,则实际位移为7.5px(nvue无效)
  201. refresherPullRate: {
  202. type: Number,
  203. default: u.gc('refresherPullRate', 0.75)
  204. },
  205. // 是否显示最后更新时间,默认为否
  206. showRefresherUpdateTime: {
  207. type: Boolean,
  208. default: u.gc('showRefresherUpdateTime', false)
  209. },
  210. // 如果需要区别不同页面的最后更新时间,请为不同页面的z-paging的`refresher-update-time-key`设置不同的字符串
  211. refresherUpdateTimeKey: {
  212. type: String,
  213. default: u.gc('refresherUpdateTimeKey', 'default')
  214. },
  215. // 下拉刷新时下拉到“松手立即刷新”或“松手进入二楼”状态时是否使手机短振动,默认为否(h5无效)
  216. refresherVibrate: {
  217. type: Boolean,
  218. default: u.gc('refresherVibrate', false)
  219. },
  220. // 下拉刷新时是否禁止下拉刷新view跟随用户触摸竖直移动,默认为否。注意此属性只是禁止下拉刷新view移动,其他下拉刷新逻辑依然会正常触发
  221. refresherNoTransform: {
  222. type: Boolean,
  223. default: u.gc('refresherNoTransform', false)
  224. },
  225. // 是否开启下拉刷新状态栏占位,适用于隐藏导航栏时,下拉刷新需要避开状态栏高度的情况,默认为否
  226. useRefresherStatusBarPlaceholder: {
  227. type: Boolean,
  228. default: u.gc('useRefresherStatusBarPlaceholder', false)
  229. },
  230. },
  231. data() {
  232. return {
  233. R: Enum.Refresher,
  234. //下拉刷新状态
  235. refresherStatus: Enum.Refresher.Default,
  236. refresherTouchstartY: 0,
  237. lastRefresherTouchmove: null,
  238. refresherReachMaxAngle: true,
  239. refresherTransform: 'translateY(0px)',
  240. refresherTransition: '',
  241. finalRefresherDefaultStyle: 'black',
  242. refresherRevealStackCount: 0,
  243. refresherCompleteTimeout: null,
  244. refresherCompleteSubTimeout: null,
  245. refresherEndTimeout: null,
  246. isTouchmovingTimeout: null,
  247. refresherTriggered: false,
  248. isTouchmoving: false,
  249. isTouchEnded: false,
  250. isUserPullDown: false,
  251. privateRefresherEnabled: -1,
  252. privateShowRefresherWhenReload: false,
  253. customRefresherHeight: -1,
  254. showCustomRefresher: false,
  255. doRefreshAnimateAfter: false,
  256. isRefresherInComplete: false,
  257. showF2: false,
  258. f2Transform: '',
  259. pullDownTimeStamp: 0,
  260. moveDis: 0,
  261. oldMoveDis: 0,
  262. currentDis: 0,
  263. oldCurrentMoveDis: 0,
  264. oldRefresherTouchmoveY: 0,
  265. oldTouchDirection: '',
  266. oldEmitedTouchDirection: '',
  267. oldPullingDistance: -1,
  268. refresherThresholdUpdateTag: 0
  269. }
  270. },
  271. watch: {
  272. refresherDefaultStyle: {
  273. handler(newVal) {
  274. if (newVal.length) {
  275. this.finalRefresherDefaultStyle = newVal;
  276. }
  277. },
  278. immediate: true
  279. },
  280. refresherStatus(newVal) {
  281. newVal === Enum.Refresher.Loading && this._cleanRefresherEndTimeout();
  282. this.refresherVibrate && (newVal === Enum.Refresher.ReleaseToRefresh || newVal === Enum.Refresher.GoF2) && this._doVibrateShort();
  283. this.$emit('refresherStatusChange', newVal);
  284. this.$emit('update:refresherStatus', newVal);
  285. },
  286. // 监听当前下拉刷新启用/禁用状态
  287. refresherEnabled(newVal) {
  288. // 当禁用下拉刷新时,强制收回正在展示的下拉刷新view
  289. !newVal && this.endRefresh();
  290. }
  291. },
  292. computed: {
  293. pullDownDisTimeStamp() {
  294. return 1000 / this.refresherFps;
  295. },
  296. refresherThresholdUnitConverted() {
  297. return u.addUnit(this.refresherThreshold, this.unit);
  298. },
  299. finalRefresherEnabled() {
  300. if (this.layoutOnly || this.useChatRecordMode) return false;
  301. if (this.privateRefresherEnabled === -1) return this.refresherEnabled;
  302. return this.privateRefresherEnabled === 1;
  303. },
  304. finalRefresherThreshold() {
  305. let refresherThreshold = this.refresherThresholdUnitConverted;
  306. let idDefault = false;
  307. if (refresherThreshold === u.addUnit(80, this.unit)) {
  308. idDefault = true;
  309. if (this.showRefresherUpdateTime) {
  310. refresherThreshold = u.addUnit(120, this.unit);
  311. }
  312. }
  313. if (idDefault && this.customRefresherHeight > 0) return this.customRefresherHeight + this.finalRefresherThresholdPlaceholder;
  314. return u.convertToPx(refresherThreshold) + this.finalRefresherThresholdPlaceholder;
  315. },
  316. finalRefresherF2Threshold() {
  317. return u.convertToPx(u.addUnit(this.refresherF2Threshold, this.unit));
  318. },
  319. finalRefresherThresholdPlaceholder() {
  320. return this.useRefresherStatusBarPlaceholder ? this.statusBarHeight : 0;
  321. },
  322. finalRefresherFixedBacHeight() {
  323. return u.convertToPx(this.refresherFixedBacHeight);
  324. },
  325. finalRefresherThemeStyle() {
  326. return this.refresherThemeStyle.length ? this.refresherThemeStyle : this.defaultThemeStyle;
  327. },
  328. finalRefresherOutRate() {
  329. let rate = this.refresherOutRate;
  330. rate = Math.max(0,rate);
  331. rate = Math.min(1,rate);
  332. return rate;
  333. },
  334. finalRefresherPullRate() {
  335. let rate = this.refresherPullRate;
  336. rate = Math.max(0,rate);
  337. return rate;
  338. },
  339. finalRefresherTransform() {
  340. if (this.refresherNoTransform || this.refresherTransform === 'translateY(0px)') return 'none';
  341. return this.refresherTransform;
  342. },
  343. finalShowRefresherWhenReload() {
  344. return this.showRefresherWhenReload || this.privateShowRefresherWhenReload;
  345. },
  346. finalRefresherTriggered() {
  347. if (!(this.finalRefresherEnabled && !this.useCustomRefresher)) return false;
  348. return this.refresherTriggered;
  349. },
  350. showRefresher() {
  351. const showRefresher = this.finalRefresherEnabled || this.useCustomRefresher && !this.useChatRecordMode;
  352. // #ifndef APP-NVUE
  353. this.active && this.customRefresherHeight === -1 && showRefresher && this.updateCustomRefresherHeight();
  354. // #endif
  355. return showRefresher;
  356. },
  357. hasTouchmove() {
  358. // #ifdef VUE2
  359. // #ifdef APP-VUE || H5
  360. if (this.$listeners && !this.$listeners.refresherTouchmove) return false;
  361. // #endif
  362. // #ifdef MP-WEIXIN || MP-QQ
  363. return this.watchRefresherTouchmove;
  364. // #endif
  365. return true;
  366. // #endif
  367. return this.watchRefresherTouchmove;
  368. },
  369. },
  370. methods: {
  371. // 终止下拉刷新状态
  372. endRefresh() {
  373. this.totalData = this.realTotalData;
  374. this._refresherEnd();
  375. this._endSystemLoadingAndRefresh();
  376. this._handleScrollViewBounce({ bounce: true });
  377. this.$nextTick(() => {
  378. this.refresherTriggered = false;
  379. })
  380. },
  381. // 手动更新自定义下拉刷新view高度
  382. updateCustomRefresherHeight() {
  383. u.delay(() => this.$nextTick(this._updateCustomRefresherHeight));
  384. },
  385. // 进入二楼
  386. goF2() {
  387. this._handleGoF2();
  388. },
  389. // 关闭二楼
  390. closeF2() {
  391. this._handleCloseF2();
  392. },
  393. // 自定义下拉刷新被触发
  394. _onRefresh(fromScrollView = false, isUserPullDown = true) {
  395. if (fromScrollView && !(this.finalRefresherEnabled && !this.useCustomRefresher)) return;
  396. this.$emit('onRefresh');
  397. this.$emit('Refresh');
  398. // #ifdef APP-NVUE
  399. if (this.loading) {
  400. u.delay(this._nRefresherEnd, 500)
  401. return;
  402. }
  403. // #endif
  404. if (this.loading || this.isRefresherInComplete) return;
  405. this.loadingType = Enum.LoadingType.Refresher;
  406. if (this.nShowRefresherReveal) return;
  407. this.isUserPullDown = isUserPullDown;
  408. this.isUserReload = !isUserPullDown;
  409. this._startLoading(true);
  410. this.refresherTriggered = true;
  411. if (this.reloadWhenRefresh && isUserPullDown) {
  412. this.useChatRecordMode ? this._onLoadingMore('click') : this._reload(false, false, isUserPullDown);
  413. }
  414. },
  415. // 自定义下拉刷新被复位
  416. _onRestore() {
  417. this.refresherTriggered = 'restore';
  418. this.$emit('onRestore');
  419. this.$emit('Restore');
  420. },
  421. // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
  422. // touch开始
  423. _refresherTouchstart(e) {
  424. this._handleListTouchstart();
  425. if (this._touchDisabled()) return;
  426. this._handleRefresherTouchstart(u.getTouch(e));
  427. },
  428. // #endif
  429. // 进一步处理touch开始结果
  430. _handleRefresherTouchstart(touch) {
  431. if (!this.loading && this.isTouchEnded) {
  432. this.isTouchmoving = false;
  433. }
  434. this.loadingType = Enum.LoadingType.Refresher;
  435. this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout);
  436. this.isTouchEnded = false;
  437. this.refresherTransition = '';
  438. this.refresherTouchstartY = touch.touchY;
  439. this.$emit('refresherTouchstart', this.refresherTouchstartY);
  440. this.lastRefresherTouchmove = touch;
  441. this._cleanRefresherCompleteTimeout();
  442. this._cleanRefresherEndTimeout();
  443. },
  444. // 非app-vue或微信小程序或QQ小程序或h5平台,使用js控制下拉刷新
  445. // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
  446. // touch中
  447. _refresherTouchmove(e) {
  448. const currentTimeStamp = u.getTime();
  449. let touch = null;
  450. let refresherTouchmoveY = 0;
  451. if (this.watchTouchDirectionChange) {
  452. // 检测下拉刷新方向改变
  453. touch = u.getTouch(e);
  454. refresherTouchmoveY = touch.touchY;
  455. const direction = refresherTouchmoveY > this.oldRefresherTouchmoveY ? 'top' : 'bottom';
  456. // 只有在方向改变的时候才emit相关事件
  457. if (direction === this.oldTouchDirection && direction !== this.oldEmitedTouchDirection) {
  458. this._handleTouchDirectionChange({ direction });
  459. this.oldEmitedTouchDirection = direction;
  460. }
  461. this.oldTouchDirection = direction;
  462. this.oldRefresherTouchmoveY = refresherTouchmoveY;
  463. }
  464. // 节流处理,在pullDownDisTimeStamp时间内的下拉刷新中事件不进行处理
  465. if (this.pullDownTimeStamp && currentTimeStamp - this.pullDownTimeStamp <= this.pullDownDisTimeStamp) return;
  466. // 如果不允许下拉,则return
  467. if (this._touchDisabled()) return;
  468. this.pullDownTimeStamp = Number(currentTimeStamp);
  469. touch = u.getTouch(e);
  470. refresherTouchmoveY = touch.touchY;
  471. // 获取当前touch的y - 初始touch的y,计算它们的差
  472. let moveDis = refresherTouchmoveY - this.refresherTouchstartY;
  473. if (moveDis < 0) return;
  474. // 对下拉刷新的角度进行限制
  475. if (this.refresherMaxAngle >= 0 && this.refresherMaxAngle <= 90 && this.lastRefresherTouchmove && this.lastRefresherTouchmove.touchY <= refresherTouchmoveY) {
  476. if (!moveDis && !this.refresherAngleEnableChangeContinued && this.moveDis < 1 && !this.refresherReachMaxAngle) return;
  477. const x = Math.abs(touch.touchX - this.lastRefresherTouchmove.touchX);
  478. const y = Math.abs(refresherTouchmoveY - this.lastRefresherTouchmove.touchY);
  479. const z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
  480. if ((x || y) && x > 1) {
  481. // 获取下拉刷新前后两次位移的角度
  482. const angle = Math.asin(y / z) / Math.PI * 180;
  483. // 如果角度小于配置要求,则return
  484. if (angle < this.refresherMaxAngle) {
  485. this.lastRefresherTouchmove = touch;
  486. this.refresherReachMaxAngle = false;
  487. return;
  488. }
  489. }
  490. }
  491. // 获取最终的moveDis
  492. moveDis = this._getFinalRefresherMoveDis(moveDis);
  493. // 处理下拉刷新位移
  494. this._handleRefresherTouchmove(moveDis, touch);
  495. // 下拉刷新时,禁止页面滚动以防止页面向下滚动和下拉刷新同时作用导致下拉刷新位置偏移超过预期
  496. if (!this.disabledBounce) {
  497. // #ifndef MP-LARK
  498. this._handleScrollViewBounce({ bounce: false });
  499. // #endif
  500. this.disabledBounce = true;
  501. }
  502. this._emitTouchmove({ pullingDistance: moveDis, dy: this.moveDis - this.oldMoveDis });
  503. },
  504. // #endif
  505. // 进一步处理touch中结果
  506. _handleRefresherTouchmove(moveDis, touch) {
  507. this.refresherReachMaxAngle = true;
  508. this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout);
  509. this.isTouchmoving = true;
  510. this.isTouchEnded = false;
  511. // 更新下拉刷新状态
  512. // 下拉刷新距离超过阈值
  513. if (moveDis >= this.finalRefresherThreshold) {
  514. // 如果开启了下拉进入二楼并且下拉刷新距离超过进入二楼阈值,则当前下拉刷新状态为松手进入二楼,否则为松手立即刷新
  515. this.refresherStatus = this.refresherF2Enabled && moveDis >= this.finalRefresherF2Threshold ? Enum.Refresher.GoF2 : Enum.Refresher.ReleaseToRefresh;
  516. } else {
  517. // 下拉刷新距离未超过阈值,显示默认状态
  518. this.refresherStatus = Enum.Refresher.Default;
  519. }
  520. // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
  521. // this.scrollEnable = false;
  522. // 通过transform控制下拉刷新view垂直偏移
  523. this.refresherTransform = `translateY(${moveDis}px)`;
  524. this.lastRefresherTouchmove = touch;
  525. // #endif
  526. this.moveDis = moveDis;
  527. },
  528. // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
  529. // touch结束
  530. _refresherTouchend(e) {
  531. // 下拉刷新用户手离开屏幕,允许列表滚动
  532. this._handleScrollViewBounce({bounce: true});
  533. if (this._touchDisabled() || !this.isTouchmoving) return;
  534. const touch = u.getTouch(e);
  535. let refresherTouchendY = touch.touchY;
  536. let moveDis = refresherTouchendY - this.refresherTouchstartY;
  537. moveDis = this._getFinalRefresherMoveDis(moveDis);
  538. this._handleRefresherTouchend(moveDis);
  539. this.disabledBounce = false;
  540. },
  541. // #endif
  542. // 进一步处理touch结束结果
  543. _handleRefresherTouchend(moveDis) {
  544. // #ifndef APP-PLUS || H5 || MP-WEIXIN
  545. if (!this.isTouchmoving) return;
  546. // #endif
  547. this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout);
  548. this.refresherReachMaxAngle = true;
  549. this.isTouchEnded = true;
  550. const refresherThreshold = this.finalRefresherThreshold;
  551. if (moveDis >= refresherThreshold && [Enum.Refresher.ReleaseToRefresh, Enum.Refresher.GoF2].indexOf(this.refresherStatus) >= 0) {
  552. // 如果是松手进入二楼状态,则触发进入二楼
  553. if (this.refresherStatus === Enum.Refresher.GoF2) {
  554. this._handleGoF2();
  555. this._refresherEnd();
  556. } else {
  557. // 如果是松手立即刷新状态,则触发下拉刷新
  558. // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
  559. this.refresherTransform = `translateY(${refresherThreshold}px)`;
  560. this.refresherTransition = 'transform .1s linear';
  561. // #endif
  562. u.delay(() => {
  563. this._emitTouchmove({ pullingDistance: refresherThreshold, dy: this.moveDis - refresherThreshold });
  564. }, 0.1);
  565. this.moveDis = refresherThreshold;
  566. this.refresherStatus = Enum.Refresher.Loading;
  567. this._doRefresherLoad();
  568. }
  569. } else {
  570. this._refresherEnd();
  571. this.isTouchmovingTimeout = u.delay(() => {
  572. this.isTouchmoving = false;
  573. }, this.refresherDefaultDuration);
  574. }
  575. this.scrollEnable = true;
  576. this.$emit('refresherTouchend', moveDis);
  577. },
  578. // 处理列表触摸开始事件
  579. _handleListTouchstart() {
  580. if (this.useChatRecordMode && this.autoHideKeyboardWhenChat) {
  581. uni.hideKeyboard();
  582. this.$emit('hidedKeyboard');
  583. }
  584. },
  585. // 处理scroll-view bounce是否生效
  586. _handleScrollViewBounce({ bounce }) {
  587. if (!this.usePageScroll && !this.scrollToTopBounceEnabled) {
  588. if (this.wxsScrollTop <= 5) {
  589. // #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5
  590. this.refresherTransition = '';
  591. // #endif
  592. this.scrollEnable = bounce;
  593. } else if (bounce) {
  594. this.scrollEnable = bounce;
  595. }
  596. }
  597. },
  598. // wxs正在下拉状态改变处理
  599. _handleWxsPullingDownStatusChange(onPullingDown) {
  600. this.wxsOnPullingDown = onPullingDown;
  601. if (onPullingDown && !this.useChatRecordMode) {
  602. this.renderPropScrollTop = 0;
  603. }
  604. },
  605. // wxs正在下拉处理
  606. _handleWxsPullingDown({ moveDis, diffDis }){
  607. this._emitTouchmove({ pullingDistance: moveDis,dy: diffDis });
  608. },
  609. // wxs触摸方向改变
  610. _handleTouchDirectionChange({ direction }) {
  611. this.$emit('touchDirectionChange',direction);
  612. },
  613. // wxs通知更新其props
  614. _handlePropUpdate(){
  615. this.wxsPropType = u.getTime().toString();
  616. },
  617. // 下拉刷新结束
  618. _refresherEnd(shouldEndLoadingDelay = true, fromAddData = false, isUserPullDown = false, setLoading = true) {
  619. if (this.loadingType === Enum.LoadingType.Refresher) {
  620. // 计算当前下拉刷新结束需要延迟的时间(用户主动下拉刷新或reload时显示下拉刷新view才需要计算延迟时间)
  621. const refresherCompleteDelay = (fromAddData && (isUserPullDown || this.finalShowRefresherWhenReload)) ? this.refresherCompleteDelay : 0;
  622. // 如果延迟时间大于0,则展示刷新结束状态,否则直接展示默认状态
  623. const refresherStatus = refresherCompleteDelay > 0 ? Enum.Refresher.Complete : Enum.Refresher.Default;
  624. if (this.finalShowRefresherWhenReload) {
  625. const stackCount = this.refresherRevealStackCount;
  626. this.refresherRevealStackCount --;
  627. if (stackCount > 1) return;
  628. }
  629. this._cleanRefresherEndTimeout();
  630. this.refresherEndTimeout = u.delay(() => {
  631. // 更新下拉刷新状态
  632. this.refresherStatus = refresherStatus;
  633. // 如果当前下拉刷新状态不是刷新结束,则认为其不在刷新结束状态
  634. if (refresherStatus !== Enum.Refresher.Complete) {
  635. this.isRefresherInComplete = false;
  636. }
  637. }, this.refresherStatus !== Enum.Refresher.Default && refresherStatus === Enum.Refresher.Default ? this.refresherCompleteDuration : 0);
  638. // #ifndef APP-NVUE
  639. if (refresherCompleteDelay > 0) {
  640. this.isRefresherInComplete = true;
  641. }
  642. // #endif
  643. this._cleanRefresherCompleteTimeout();
  644. this.refresherCompleteTimeout = u.delay(() => {
  645. let animateDuration = 1;
  646. const animateType = this.refresherEndBounceEnabled && fromAddData ? 'cubic-bezier(0.19,1.64,0.42,0.72)' : 'linear';
  647. if (fromAddData) {
  648. animateDuration = this.refresherEndBounceEnabled ? this.refresherCompleteDuration / 1000 : this.refresherCompleteDuration / 3000;
  649. }
  650. this.refresherTransition = `transform ${fromAddData ? animateDuration : this.refresherDefaultDuration / 1000}s ${animateType}`;
  651. // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
  652. this.refresherTransform = 'translateY(0px)';
  653. this.currentDis = 0;
  654. // #endif
  655. // #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5
  656. this.wxsPropType = this.refresherTransition + 'end' + u.getTime();
  657. // #endif
  658. // #ifdef APP-NVUE
  659. this._nRefresherEnd();
  660. // #endif
  661. this.moveDis = 0;
  662. // #ifndef APP-NVUE
  663. if (refresherStatus === Enum.Refresher.Complete) {
  664. if (this.refresherCompleteSubTimeout) {
  665. clearTimeout(this.refresherCompleteSubTimeout);
  666. this.refresherCompleteSubTimeout = null;
  667. }
  668. this.refresherCompleteSubTimeout = u.delay(() => {
  669. this.$nextTick(() => {
  670. this.refresherStatus = Enum.Refresher.Default;
  671. this.isRefresherInComplete = false;
  672. })
  673. }, animateDuration * 800);
  674. }
  675. // #endif
  676. this._emitTouchmove({ pullingDistance: 0, dy: this.moveDis });
  677. }, refresherCompleteDelay);
  678. }
  679. if (setLoading) {
  680. u.delay(() => this.loading = false, shouldEndLoadingDelay ? 10 : 0);
  681. isUserPullDown && this._onRestore();
  682. }
  683. },
  684. // 处理进入二楼
  685. _handleGoF2() {
  686. if (this.showF2 || !this.refresherF2Enabled) return;
  687. this.$emit('refresherF2Change', 'go');
  688. if (!this.showRefresherF2) return;
  689. // #ifndef APP-NVUE
  690. this.f2Transform = `translateY(${-this.superContentHeight}px)`;
  691. this.showF2 = true;
  692. u.delay(() => {
  693. this.f2Transform = 'translateY(0px)';
  694. }, 100, 'f2ShowDelay')
  695. // #endif
  696. // #ifdef APP-NVUE
  697. this.showF2 = true;
  698. this.$nextTick(() => {
  699. weexAnimation.transition(this.$refs['zp-n-f2'], {
  700. styles: { transform: `translateY(${-this.superContentHeight}px)` },
  701. duration: 0,
  702. timingFunction: 'linear',
  703. needLayout: true,
  704. delay: 0
  705. })
  706. this.nF2Opacity = 1;
  707. })
  708. u.delay(() => {
  709. weexAnimation.transition(this.$refs['zp-n-f2'], {
  710. styles: { transform: 'translateY(0px)' },
  711. duration: this.refresherF2Duration,
  712. timingFunction: 'linear',
  713. needLayout: true,
  714. delay: 0
  715. })
  716. }, 10, 'f2GoDelay')
  717. // #endif
  718. },
  719. // 处理退出二楼
  720. _handleCloseF2() {
  721. if (!this.showF2 || !this.refresherF2Enabled) return;
  722. this.$emit('refresherF2Change', 'close');
  723. if (!this.showRefresherF2) return;
  724. // #ifndef APP-NVUE
  725. this.f2Transform = `translateY(${-this.superContentHeight}px)`;
  726. // #endif
  727. // #ifdef APP-NVUE
  728. weexAnimation.transition(this.$refs['zp-n-f2'], {
  729. styles: { transform: `translateY(${-this.superContentHeight}px)` },
  730. duration: this.refresherF2Duration,
  731. timingFunction: 'linear',
  732. needLayout: true,
  733. delay: 0
  734. })
  735. // #endif
  736. u.delay(() => {
  737. this.showF2 = false;
  738. this.nF2Opacity = 0;
  739. }, this.refresherF2Duration, 'f2CloseDelay')
  740. },
  741. // 模拟用户手动触发下拉刷新
  742. _doRefresherRefreshAnimate() {
  743. this._cleanRefresherCompleteTimeout();
  744. // 用户处理用户在短时间内多次调用reload的情况,此时下拉刷新view不需要重复显示,只需要保证最后一次reload对应的请求结束后收回下拉刷新view即可
  745. // #ifndef APP-NVUE
  746. const doRefreshAnimateAfter = !this.doRefreshAnimateAfter && (this.finalShowRefresherWhenReload) && this
  747. .customRefresherHeight === -1 && this.refresherThreshold === u.addUnit(80, this.unit);
  748. if (doRefreshAnimateAfter) {
  749. this.doRefreshAnimateAfter = true;
  750. return;
  751. }
  752. // #endif
  753. this.refresherRevealStackCount ++;
  754. // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
  755. this.refresherTransform = `translateY(${this.finalRefresherThreshold}px)`;
  756. // #endif
  757. // #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5
  758. this.wxsPropType = 'begin' + u.getTime();
  759. // #endif
  760. this.moveDis = this.finalRefresherThreshold;
  761. this.refresherStatus = Enum.Refresher.Loading;
  762. this.isTouchmoving = true;
  763. this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout);
  764. this._doRefresherLoad(false);
  765. },
  766. // 触发下拉刷新
  767. _doRefresherLoad(isUserPullDown = true) {
  768. this._onRefresh(false, isUserPullDown);
  769. this.loading = true;
  770. },
  771. // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
  772. // 获取处理后的moveDis
  773. _getFinalRefresherMoveDis(moveDis) {
  774. let diffDis = moveDis - this.oldCurrentMoveDis;
  775. this.oldCurrentMoveDis = moveDis;
  776. if (diffDis > 0) {
  777. // 根据配置的下拉刷新用户手势位移与实际需要的位移比率计算最终的diffDis
  778. diffDis = diffDis * this.finalRefresherPullRate;
  779. if (this.currentDis > this.finalRefresherThreshold) {
  780. diffDis = diffDis * (1 - this.finalRefresherOutRate);
  781. }
  782. }
  783. // 控制diffDis过大的情况,比如进入页面突然猛然下拉,此时diffDis不应进行太大的偏移
  784. diffDis = diffDis > 100 ? diffDis / 100 : diffDis;
  785. this.currentDis += diffDis;
  786. this.currentDis = Math.max(0, this.currentDis);
  787. return this.currentDis;
  788. },
  789. // 判断touch手势是否要触发
  790. _touchDisabled() {
  791. const checkOldScrollTop = this.oldScrollTop > 5;
  792. return this.loading || this.isRefresherInComplete || this.useChatRecordMode || this.layoutOnly || !this.refresherEnabled || !this.useCustomRefresher || (this.usePageScroll && this.useCustomRefresher && this.pageScrollTop > 10) || (!(this.usePageScroll && this.useCustomRefresher) && checkOldScrollTop);
  793. },
  794. // #endif
  795. // 更新自定义下拉刷新view高度
  796. _updateCustomRefresherHeight() {
  797. this._getNodeClientRect('.zp-custom-refresher-slot-view').then((res) => {
  798. this.customRefresherHeight = res ? res[0].height : 0;
  799. this.showCustomRefresher = this.customRefresherHeight > 0;
  800. if (this.doRefreshAnimateAfter) {
  801. this.doRefreshAnimateAfter = false;
  802. this._doRefresherRefreshAnimate();
  803. }
  804. });
  805. },
  806. // emit pullingDown事件
  807. _emitTouchmove(e) {
  808. // #ifndef APP-NVUE
  809. e.viewHeight = this.finalRefresherThreshold;
  810. // #endif
  811. e.rate = e.viewHeight > 0 ? e.pullingDistance / e.viewHeight : 0;
  812. this.hasTouchmove && this.oldPullingDistance !== e.pullingDistance && this.$emit('refresherTouchmove', e);
  813. this.oldPullingDistance = e.pullingDistance;
  814. },
  815. // 清除refresherCompleteTimeout
  816. _cleanRefresherCompleteTimeout() {
  817. this.refresherCompleteTimeout = this._cleanTimeout(this.refresherCompleteTimeout);
  818. // #ifdef APP-NVUE
  819. this._nRefresherEnd(false);
  820. // #endif
  821. },
  822. // 清除refresherEndTimeout
  823. _cleanRefresherEndTimeout() {
  824. this.refresherEndTimeout = this._cleanTimeout(this.refresherEndTimeout);
  825. },
  826. }
  827. }