Skip to content

Commit 8f35edb

Browse files
author
cunjinli
committed
fix #1 支持一个页面多个长列表
1 parent b541baa commit 8f35edb

File tree

4 files changed

+63
-83
lines changed

4 files changed

+63
-83
lines changed

README.md

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
​ 假设列表数据有100个 item,知道了滚动的位置,怎么知道哪些 item 必须显示在页面?因为 item 还没渲染出来,不能通过 getComputedStyle 等 DOM 操作得到每个 item 的位置,所以无法知道哪些 item 需要渲染。为了解决这个问题,需要每个 item 固定宽高。item 的宽高的定义见下面的 API 的`createRecycleContext()`的参数 itemSize 的介绍。
2222

23+
​ 滚动过程中,重新渲染数据的同时,需要设置当前数据的前后的 div 占位元素高度,同时是指在同一个渲染周期内。页面渲染是通过 setData 触发的,列表数据和 div 占位高度在2个组件内进行 setData 的,为了把这2个 setData 放在同一个渲染周期,用了一个 hack 方法,所以定义 recycle-view 的 batch 属性固定为`batch="{{batchSetRecycleData}}"`
24+
2325
​ 在滚动过程中,为了避免频繁出现白屏,会多渲染当前屏幕的前后2个屏幕的内容。
2426

2527
## 包结构
@@ -63,7 +65,7 @@ npm install --save miniprogram-recycle-view
6365
3. WXML 文件中引用 recycle-view
6466

6567
```xml
66-
<recycle-view id="recycleId">
68+
<recycle-view batch="{{batchSetRecycleData}}" id="recycleId">
6769
<view slot="before">长列表前面的内容</view>
6870
<recycle-item wx:for="{{recycleList}}" wx:key="id">
6971
<view>
@@ -80,6 +82,7 @@ npm install --save miniprogram-recycle-view
8082
| 字段名 | 类型 | 必填 | 描述 |
8183
| --------------------- | ------- | ---- | ----------------------------------------- |
8284
| id | String || id必须是页面唯一的字符串 |
85+
| batch | Boolean || 必须设置为{{batchSetRecycleData}}才能生效 |
8386
| height | Number || 设置recycle-view的高度,默认为页面高度 |
8487
| width | Number || 设置recycle-view的宽度,默认是页面的宽度 |
8588
| enable-back-to-top | Boolean || 默认为false,同scroll-view同名字段 |
@@ -175,15 +178,22 @@ npm install --save miniprogram-recycle-view
175178
}
176179
```
177180

178-
181+
182+
179183
## Tips
180184

181-
1. recycle-item的宽高必须和itemSize设置的宽高一致,否则会出现跳动的bug。
182-
2. recycle-view设置的高度必须和其style里面设置的样式一致。
183-
3. `createRecycleContext(options)`的id参数必须和recycle-view的id属性一致,dataKey参数必须和recycle-item的wx:for绑定的变量名一致。
184-
4. 不能在recycle-item里面使用wx:for的index变量作为索引值的,请使用{{item.\_\_index\_\_}}替代。
185-
5. 不要通过setData设置recycle-item的wx:for的变量值,建议recycle-item设置wx:key属性。
186-
6. 如果长列表里面包含图片,必须保证图片资源是有HTTP缓存的,否则在滚动过程中会发起很多的图片请求。
187-
7. 有些数据不一定会渲染出来,使用wx.createSelectorQuery的时候有可能会失效,可使用RecycleContext的getBoundingClientRect来替代。
188-
8. 当使用了useInPage参数的时候,必须在Page里面定义onPageScroll事件。
189-
9. transformRpx会进行四舍五入,所以`transformRpx(20) + transformRpx(90)`不一定等于`transformRpx(110)`
185+
1. recycle-view设置batch属性的值必须为{{batchSetRecycleData}}。
186+
2. recycle-item的宽高必须和itemSize设置的宽高一致,否则会出现跳动的bug。
187+
3. recycle-view设置的高度必须和其style里面设置的样式一致。
188+
4. `createRecycleContext(options)`的id参数必须和recycle-view的id属性一致,dataKey参数必须和recycle-item的wx:for绑定的变量名一致。
189+
5. 不能在recycle-item里面使用wx:for的index变量作为索引值的,请使用{{item.\_\_index\_\_}}替代。
190+
6. 不要通过setData设置recycle-item的wx:for的变量值,建议recycle-item设置wx:key属性。
191+
7. 如果长列表里面包含图片,必须保证图片资源是有HTTP缓存的,否则在滚动过程中会发起很多的图片请求。
192+
8. 有些数据不一定会渲染出来,使用wx.createSelectorQuery的时候有可能会失效,可使用RecycleContext的getBoundingClientRect来替代。
193+
9. 当使用了useInPage参数的时候,必须在Page里面定义onPageScroll事件。
194+
10. transformRpx会进行四舍五入,所以`transformRpx(20) + transformRpx(90)`不一定等于`transformRpx(110)`
195+
11. 如果一个页面有多个长列表,必须多设置batch-key属性,每个的batch-key的值和batch属性的变量必须不一致。例如
196+
```html
197+
<recycle-view batch="{{batchSetRecycleData}}" batch-key="batchSetRecycleData"></recycle-view>
198+
<recycle-view batch="{{batchSetRecycleData1}}" batch-key="batchSetRecycleData1"></recycle-view>
199+
```

src/recycle-view.js

Lines changed: 21 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
/* eslint complexity: ["error", {"max": 50}] */
22
/* eslint-disable indent */
3-
let SHOW_SCREENS = 4
4-
let MAX_SHOW_SCREENS = 5 // 5和3刚好合适?
5-
const DEFAULT_SHOW_SCREENS = SHOW_SCREENS
6-
const DEFAULT_MAX_SHOW_SCREENS = MAX_SHOW_SCREENS
3+
const DEFAULT_SHOW_SCREENS = 4
74
const RECT_SIZE = 200
85
const systemInfo = wx.getSystemInfoSync()
96
const DEBUG = false
10-
const BOUNDARY_INTERVAL = 400 // 到达边界多少距离的时候, 直接改为边界位置
11-
const SETDATA_INTERVAL_BOUNDARY = 300 // 大于300ms则减少MAX_SHOW_SCREEN的值
12-
const SETDATA_INTERVAL_BOUNDARY_1 = 500
137
const transformRpx = require('./utils/transformRpx.js').transformRpx
148

159
Component({
@@ -57,6 +51,17 @@ Component({
5751
type: Boolean,
5852
value: true,
5953
},
54+
batch: {
55+
type: Boolean,
56+
value: false,
57+
public: true,
58+
observer: '_recycleInnerBatchDataChanged'
59+
},
60+
batchKey: {
61+
type: String,
62+
value: 'batchSetRecycleData',
63+
public: true,
64+
},
6065
scrollTop: {
6166
type: Number,
6267
value: 0,
@@ -115,6 +120,11 @@ Component({
115120
type: String,
116121
public: true,
117122
value: ''
123+
},
124+
screen: { // 默认渲染多少屏的数据
125+
type: Number,
126+
public: true,
127+
value: DEFAULT_SHOW_SCREENS
118128
}
119129
},
120130

@@ -156,7 +166,6 @@ Component({
156166
}
157167
}, true)
158168
})
159-
this._totalTime = this._totalCount = 0
160169
},
161170
detached() {
162171
this.page = null
@@ -165,7 +174,6 @@ Component({
165174
this.context.destroy()
166175
this.context = null
167176
}
168-
if (this.timerId) clearTimeout(this.timerId)
169177
},
170178
/**
171179
* 组件的方法列表
@@ -186,7 +194,6 @@ Component({
186194
this.triggerEvent('scrolltolower', e.detail)
187195
},
188196
_beginToScroll() {
189-
this._lastRenderTime = Date.now()
190197
if (!this._lastScrollTop) {
191198
this._lastScrollTop = this._pos && (this._pos.top || 0)
192199
}
@@ -235,39 +242,14 @@ Component({
235242
const that = this
236243
const scrollLeft = e.detail.scrollLeft
237244
const scrollTop = e.detail.scrollTop
238-
let isMatchBoundary = false
239-
if (scrollTop - BOUNDARY_INTERVAL < 0) {
240-
// scrollTop = 0
241-
isMatchBoundary = true
242-
}
243-
if (this.totalHeight - scrollTop - BOUNDARY_INTERVAL < this.data.height) {
244-
// scrollTop = this.totalHeight - this.data.height
245-
// isMatchBoundary = true
246-
}
247-
const usetime = Date.now() - this._lastRenderTime
248245
const scrollDistance = Math.abs(scrollTop - this._lastScrollTop)
249246

250247
this._lastScrollTop = scrollTop
251-
this._lastRenderTime = Date.now()
252-
// 当scroll触发时间大于200ms且大于滚动距离,下一个滚动距离会极高,容易出现白屏,因此需要马上渲染
253-
const isNextScrollExpose = false
254-
// const mustRender = force || isMatchBoundary || isNextScrollExpose
255-
const mustRender = force || isNextScrollExpose
256-
this._log('scrollTop', e.detail.scrollTop, isMatchBoundary, mustRender)
257-
if (!mustRender) {
258-
if ((Math.abs(scrollTop - pos.top) < pos.height * 1.5)) {
259-
this._log('【not exceed height')
260-
return
261-
}
262-
}
263-
if (force && this.timerId) {
264-
clearTimeout(this.timerId)
265-
}
266-
SHOW_SCREENS = DEFAULT_SHOW_SCREENS // 固定4屏幕
267-
this._log('SHOW_SCREENS', SHOW_SCREENS, scrollTop, isNextScrollExpose)
248+
const SHOW_SCREENS = this.data.screen // 固定4屏幕
249+
this._log('SHOW_SCREENS', SHOW_SCREENS, scrollTop)
268250
this._calcViewportIndexes(scrollLeft, scrollTop,
269251
(beginIndex, endIndex, minTop, afterHeight, maxTop) => {
270-
that._log('scrollDistance', scrollDistance, 'usetime', usetime, 'indexes', beginIndex, endIndex)
252+
that._log('scrollDistance', scrollDistance, 'indexes', beginIndex, endIndex)
271253
// 渲染的数据不变
272254
if (!force && pos.beginIndex === beginIndex && pos.endIndex === endIndex &&
273255
pos.minTop === minTop && pos.afterHeight === afterHeight) {
@@ -286,32 +268,12 @@ Component({
286268
pos.maxTop = maxTop
287269
pos.afterHeight = afterHeight
288270
pos.ignoreBeginIndex = pos.ignoreEndIndex = -1
289-
pos.lastSetDataTime = Date.now() // 用于节流时间判断
290-
that._isScrollRendering = true
291-
const st = Date.now()
292271
that.page._recycleViewportChange({
293272
detail: {
294273
data: that._pos,
295274
id: that.id
296275
}
297276
}, () => {
298-
that._isScrollRendering = false
299-
if (that._totalCount < 5) {
300-
that._totalCount++
301-
that._totalTime += (Date.now() - st)
302-
} else {
303-
that._totalCount = 1
304-
that._totalTime = (Date.now() - st)
305-
}
306-
pos.lastSetDataTime = 0 // 用于节流时间判断
307-
// if (that._totalCount / that._totalTime <= SETDATA_INTERVAL_BOUNDARY) {
308-
// MAX_SHOW_SCREENS = DEFAULT_MAX_SHOW_SCREENS + 1 // 多渲染2个屏幕的内容
309-
if (that._totalTime / that._totalCount > SETDATA_INTERVAL_BOUNDARY) {
310-
that._log('【【SHOW_SCREENS 调整', that._totalCount / that._totalTime)
311-
MAX_SHOW_SCREENS = DEFAULT_MAX_SHOW_SCREENS - 1
312-
} else if (that._totalTime / that._totalCount > SETDATA_INTERVAL_BOUNDARY_1) {
313-
MAX_SHOW_SCREENS = DEFAULT_MAX_SHOW_SCREENS - 2
314-
}
315277
if (e.detail.cb) {
316278
e.detail.cb()
317279
}
@@ -405,6 +367,7 @@ Component({
405367
// top = Math.max(top, this.data.beforeSlotHeight)
406368
const beforeSlotHeight = this.data.beforeSlotHeight || 0
407369
// 和direction无关了
370+
const SHOW_SCREENS = this.data.screen
408371
let minTop = top - pos.height * SHOW_SCREENS - beforeSlotHeight
409372
let maxTop = top + pos.height * SHOW_SCREENS - beforeSlotHeight
410373
// maxTop或者是minTop超出了范围

src/utils/viewport-change-func.js

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,26 +24,33 @@ module.exports = function (e, cb) {
2424
}
2525
}
2626
const obj = {
27+
// batchSetRecycleData: !this.data.batchSetRecycleData
2728
}
2829
obj[item.key] = newList
2930
const comp = this.selectComponent('#' + detail.id)
31+
obj[comp.data.batchKey] = !this.data.batchSetRecycleData
3032
comp._setInnerBeforeAndAfterHeight({
3133
beforeHeight: pos.minTop,
3234
afterHeight: pos.afterHeight
3335
})
36+
this.setData(obj, () => {
37+
if (typeof cb === 'function') {
38+
cb()
39+
}
40+
})
3441
// Fix #1
3542
// 去掉了batchSetDataKey,支持一个页面内显示2个recycle-view
36-
const groupSetData = () => {
37-
this.setData(obj)
38-
comp._recycleInnerBatchDataChanged(() => {
39-
if (typeof cb === 'function') {
40-
cb()
41-
}
42-
})
43-
}
44-
if (typeof this.groupSetData === 'function') {
45-
this.groupSetData(groupSetData)
46-
} else {
47-
groupSetData()
48-
}
43+
// const groupSetData = () => {
44+
// this.setData(obj)
45+
// comp._recycleInnerBatchDataChanged(() => {
46+
// if (typeof cb === 'function') {
47+
// cb()
48+
// }
49+
// })
50+
// }
51+
// if (typeof this.groupSetData === 'function') {
52+
// this.groupSetData(groupSetData)
53+
// } else {
54+
// groupSetData()
55+
// }
4956
}

tools/demo/pages/index/index.wxml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<!-- <button bindtap="showRecycleview1">showRecycleview</button> -->
22
<!-- <button bindtap="hideRecycleview">hideRecycleview</button> -->
3-
<recycle-view class="recycle-list" placeholder-image="{{placeholderImage}}" bindscrolltolower="scrollToLower" scroll-with-animation="{{true}}" scroll-to-index="{{index}}" scroll-top="{{scrollTop}}" height="500" id="recycleId">
3+
<recycle-view class="recycle-list" placeholder-image="{{placeholderImage}}" bindscrolltolower="scrollToLower" scroll-with-animation="{{true}}" batch="{{batchSetRecycleData}}" batch-key="batchSetRecycleData" scroll-to-index="{{index}}" scroll-top="{{scrollTop}}" height="500" id="recycleId">
44
<view slot="before" style=''>
55
<button bindtap="scrollTo2000">scrollTo2000</button>
66
<button bindtap="scrollTo0">scrollTo0</button>

0 commit comments

Comments
 (0)