Skip to content

Commit 52e33a9

Browse files
author
cunjinli
committed
去掉batch属性,改成其他替代,避免#1问题;#26 加scrollY属性;#10
1 parent c0b95c5 commit 52e33a9

File tree

9 files changed

+84
-66
lines changed

9 files changed

+84
-66
lines changed

README.md

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
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-
2523
​ 在滚动过程中,为了避免频繁出现白屏,会多渲染当前屏幕的前后2个屏幕的内容。
2624

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

6765
```xml
68-
<recycle-view batch="{{batchSetRecycleData}}" id="recycleId">
66+
<recycle-view id="recycleId">
6967
<view slot="before">长列表前面的内容</view>
7068
<recycle-item wx:for="{{recycleList}}" wx:key="id">
7169
<view>
@@ -82,11 +80,11 @@ npm install --save miniprogram-recycle-view
8280
| 字段名 | 类型 | 必填 | 描述 |
8381
| --------------------- | ------- | ---- | ----------------------------------------- |
8482
| id | String || id必须是页面唯一的字符串 |
85-
| batch | Boolean || 必须设置为{{batchSetRecycleData}}才能生效 |
8683
| height | Number || 设置recycle-view的高度,默认为页面高度 |
8784
| width | Number || 设置recycle-view的宽度,默认是页面的宽度 |
8885
| enable-back-to-top | Boolean || 默认为false,同scroll-view同名字段 |
8986
| scroll-top | Number || 默认为false,同scroll-view同名字段 |
87+
| scroll-y | Number || 默认为true,同scroll-view同名字段 |
9088
| scroll-to-index | Number || 设置滚动到长列表的项 |
9189
| placeholder-image | String || 默认占位背景图片,在渲染不及时的时候显示,不建议使用大图作为占位。建议传入SVG的Base64格式,可使用[工具](https://codepen.io/jakob-e/pen/doMoML)将SVG代码转为Base64格式。支持SVG中设置rpx。 |
9290
| scroll-with-animation | Boolean || 默认为false,同scroll-view的同名字段 |
@@ -103,7 +101,6 @@ npm install --save miniprogram-recycle-view
103101
| before | 默认 slot 的前面的非回收区域 |
104102
| 默认 slot | 长列表的列表展示区域,recycle-item 必须定义在默认 slot 中 |
105103
| after | 默认 slot 的后面的非回收区域 |
106-
| itemsize | 动态生成宽高的预先加载数据的slot |
107104

108105
​ 长列表的内容实际是在一个 scroll-view 滚动区域里面的,当长列表里面的内容,不止是单独的一个列表的时候,例如我们页面底部都会有一个 copyright 的声明,我们就可以把这部分的内容放在 before 和 after 这2个 slot 里面。
109106

@@ -178,17 +175,15 @@ npm install --save miniprogram-recycle-view
178175
}
179176
```
180177

181-
182-
178+
183179
## Tips
184180

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)`
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)`

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "miniprogram-recycle-view",
3-
"version": "0.0.8",
3+
"version": "0.1.1",
44
"description": "miniprogram custom component",
55
"main": "miniprogram_dist/index.js",
66
"scripts": {

src/recycle-view.js

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,9 @@ Component({
5353
type: Boolean,
5454
value: false
5555
},
56-
batch: {
56+
scrollY: {
5757
type: Boolean,
58-
value: false,
59-
observer: '_recycleInnerBatchDataChanged'
58+
value: true,
6059
},
6160
scrollTop: {
6261
type: Number,
@@ -550,20 +549,26 @@ Component({
550549
// 重新渲染事件发生
551550
let beforeReady = false
552551
let afterReady = false
553-
this.createSelectorQuery().select('.slot-before').boundingClientRect((rect) => {
554-
beforeSlotHeight = rect.height
555-
beforeReady = true
556-
if (afterReady) {
557-
if (newCb) { newCb() }
558-
}
559-
}).exec()
560-
this.createSelectorQuery().select('.slot-after').boundingClientRect((rect) => {
561-
afterSlotHeight = rect.height
562-
afterReady = true
563-
if (beforeReady) {
564-
if (newCb) { newCb() }
565-
}
566-
}).exec()
552+
// Fix issue #16
553+
this.setData({
554+
hasBeforeSlotHeight: false,
555+
hasAfterSlotHeight: false,
556+
}, () => {
557+
this.createSelectorQuery().select('.slot-before').boundingClientRect((rect) => {
558+
beforeSlotHeight = rect.height
559+
beforeReady = true
560+
if (afterReady) {
561+
if (newCb) { newCb() }
562+
}
563+
}).exec()
564+
this.createSelectorQuery().select('.slot-after').boundingClientRect((rect) => {
565+
afterSlotHeight = rect.height
566+
afterReady = true
567+
if (beforeReady) {
568+
if (newCb) { newCb() }
569+
}
570+
}).exec()
571+
})
567572
},
568573
_setInnerBeforeAndAfterHeight(obj) {
569574
if (typeof obj.beforeHeight !== 'undefined') {
@@ -573,7 +578,7 @@ Component({
573578
this._tmpAfterHeight = obj.afterHeight
574579
}
575580
},
576-
_recycleInnerBatchDataChanged() {
581+
_recycleInnerBatchDataChanged(cb) {
577582
if (typeof this._tmpBeforeHeight !== 'undefined') {
578583
const setObj = {
579584
innerBeforeHeight: this._tmpBeforeHeight || 0,
@@ -583,16 +588,27 @@ Component({
583588
setObj.innerScrollTop = this._tmpInnerScrollTop
584589
}
585590
const pageObj = {}
591+
let hasPageData = false
586592
if (typeof this._currentSetDataKey !== 'undefined') {
587593
pageObj[this._currentSetDataKey] = this._currentSetDataList
588-
this.page.setData(pageObj)
594+
hasPageData = true
589595
}
590596
const saveScrollWithAnimation = this.data.scrollWithAnimation
591-
this.setData(setObj, () => {
592-
this.setData({
593-
scrollWithAnimation: saveScrollWithAnimation
597+
const groupSetData = () => {
598+
// 如果有分页数据的话
599+
if (hasPageData) {
600+
this.page.setData(pageObj)
601+
}
602+
this.setData(setObj, () => {
603+
this.setData({
604+
scrollWithAnimation: saveScrollWithAnimation
605+
})
606+
if (typeof cb === 'function') {
607+
cb()
608+
}
594609
})
595-
})
610+
}
611+
groupSetData()
596612
delete this._currentSetDataKey
597613
delete this._currentSetDataList
598614
this._tmpBeforeHeight = undefined

src/recycle-view.wxml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<!--components/recycle-view/recycle-view.wxml-->
22
<view bindtouchstart='_beginToScroll' style="height:{{useInPage ? totalHeight : height}}px;width:{{width}}px;transform:translateZ(0);-webkit-transform:translateZ(0);" id="content" class="wrap">
3-
<scroll-view bindscroll="_scrollViewDidScroll" class="content" style='height:100%;position: relative;' scroll-y="{{useInPage ? false : true}}" scroll-x="{{false}}" upper-threshold="{{upperThreshold}}" lower-threshold="{{lowerThreshold}}" scroll-top="{{innerScrollTop}}" scroll-into-view="{{innerScrollIntoView}}" scroll-with-animation="{{scrollWithAnimation}}" bindscrolltoupper="_scrollToUpper" bindscrolltolower="_scrollToLower" enable-back-to-top="{{enableBackToTop}}" throttle="{{throttle}}">
3+
<scroll-view bindscroll="_scrollViewDidScroll" class="content" style='height:100%;position: relative;' scroll-y="{{useInPage ? false : true}}" scroll-x="{{scrollY}}" upper-threshold="{{upperThreshold}}" lower-threshold="{{lowerThreshold}}" scroll-top="{{innerScrollTop}}" scroll-into-view="{{innerScrollIntoView}}" scroll-with-animation="{{scrollWithAnimation}}" bindscrolltoupper="_scrollToUpper" bindscrolltolower="_scrollToLower" enable-back-to-top="{{enableBackToTop}}" throttle="{{throttle}}">
44
<view style="position: absolute;z-index:1;width:100%;left: 0;top: 0;opacity: 0;visibility: hidden;">
55
<slot name="itemsize"></slot>
66
</view>

src/utils/recycle-context.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,13 @@ RecycleContext.prototype._recalculateSize = function (list) {
359359
// 超过了宽度, 移动到下一行, 再根据高度判断是否需要移动到下一个方格
360360
if (offsetLeft + itemSize.width > compData.width) {
361361
offsetLeft = itemSize.width
362-
offsetTop += sizeArray[sizeArray.length - 2].height // 加上最后一个数据的高度
362+
// Fixed issue #22
363+
if (sizeArray.length >= 2) {
364+
offsetTop += sizeArray[sizeArray.length - 2].height || 0 // 加上最后一个数据的高度
365+
} else {
366+
offsetTop += itemSize.height
367+
}
368+
// offsetTop += sizeArray[sizeArray.length - 2].height // 加上最后一个数据的高度
363369
// 根据高度判断是否需要移动到下一个方格
364370
if (offsetTop >= RECT_SIZE * (line + 1)) {
365371
line += parseInt((offsetTop - RECT_SIZE * line) / RECT_SIZE, 10)

src/utils/viewport-change-func.js

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,26 @@ module.exports = function (e, cb) {
2424
}
2525
}
2626
const obj = {
27-
batchSetRecycleData: !this.data.batchSetRecycleData
2827
}
29-
// const setDataStart = +new Date()
3028
obj[item.key] = newList
3129
const comp = this.selectComponent('#' + detail.id)
32-
// comp.setList(item.key, newList)
3330
comp._setInnerBeforeAndAfterHeight({
3431
beforeHeight: pos.minTop,
3532
afterHeight: pos.afterHeight
3633
})
37-
// console.log('before set recycleData')
38-
this.setData(obj, function () {
39-
if (typeof cb === 'function') {
40-
cb()
41-
}
42-
// console.log('set recycleData data use time', Date.now() - setDataStart, JSON.stringify(pos))
43-
})
34+
// Fix #1
35+
// 去掉了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+
}
4449
}

tools/demo/pages/index/index.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@ Page({
1818
id: 'recycleId',
1919
dataKey: 'recycleList',
2020
page: this,
21-
itemSize: {
22-
props: 'azFirst',
23-
// cacheKey: 'cacheKey', // 预先缓存的key
24-
queryClass: 'recycle-itemsize', // 动态查询的class
25-
dataKey: 'recycleListItemSize', // 预先渲染的数据的wx:for绑定的变量
26-
// componentClass: 'recycle-list'
21+
itemSize: function(item, index) {
22+
return {
23+
width: systemInfo.windowWidth / 2,
24+
height: 160
25+
}
2726
},
2827
placeholderClass: ['recycle-image', 'recycle-text'],
2928
// itemSize: function(item) {

tools/demo/pages/index/index.wxml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
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}}" batch="{{batchSetRecycleData}}" height="500" id="recycleId">
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">
44
<view slot="before" style=''>
55
<button bindtap="scrollTo2000">scrollTo2000</button>
66
<button bindtap="scrollTo0">scrollTo0</button>
77
<button bindtap="scrollToid" style="height:123.5rpx">scrollToIdx</button>
88
</view>
9-
<view slot="itemsize">
10-
<template is="recycleItem" data="{{item: item, recycleList: recycleListItemSize}}"></template>
11-
</view>
129
<template is="recycleItem" data="{{item: item, recycleList: recycleList}}"></template>
1310
<view slot="after" style="height:200px;">after height:200px view</view>
1411
</recycle-view>
1512
<template name="recycleItem">
16-
<recycle-item style="width:50%;" wx:for="{{recycleList}}" wx:key="id">
13+
<recycle-item style="width:50%;height:160px;" wx:for="{{recycleList}}" wx:key="id">
1714
<view class="recycle-itemsize" style="width:100%;height:{{item.test.azFirst ? 160 : 160}}px;position: relative;">
1815
<image class='recycle-image' style='width:80px;height:80px;' src="{{item.image_url}}?imageView2/2/w/120/h/0/q/120/format/jpg"></image>
1916
<text class="recycle-text">{{item.idx+1}}. {{item.title}}</text>

tools/demo/project.config.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
"nodeModules": true
1313
},
1414
"compileType": "miniprogram",
15-
"libVersion": "2.2.3",
16-
"appid": "",
15+
"libVersion": "2.7.0",
16+
"appid": "wxe0b5580bba739b69",
1717
"projectname": "recycle-view",
1818
"isGameTourist": false,
1919
"condition": {

0 commit comments

Comments
 (0)