如何将新项目推送到 FlatList 的数据,同时保持更新前的顶行在更新后可见,并且 FlatList 不应滚动到顶部到上面推送的新项目?
问题:当使用需要渲染当前顶行上方的行的新数据更新组件状态时,FlatList 将重新渲染并从顶部重新启动 contentOffset.y。
示例:假设我有以下 FlatList 初始加载数据
this.state = {
data: [
{title: '2019-03-10', events: []},
{title: '2019-03-11', events: []},
{title: '2019-03-12', events: []},
]
}
Run Code Online (Sandbox Code Playgroud)
顶部可见行是标题为“2019-03-10”的项目,然后假设我拉动刷新并想要推送这个新数据 ->
const newData = [
{title: '2019-03-07', events: []},
{title: '2019-03-08', events: []},
{title: '2019-03-09', events: []},
]
Run Code Online (Sandbox Code Playgroud)
进入这样的状态 ->
this.setState({
data: this.mergeAndSortData(this.state.data, newData)
})
Run Code Online (Sandbox Code Playgroud)
现在我的数据按标题的升序日期排序,因此我将新数据添加到当前顶部可见行的数据上方,并且所有行的索引都会更改,这会强制 FlatList 重新加载,并通过重置 contentOffset.y为 0,当前顶部可见行的标题为“2019-03-07”。
首选:我想要一种使用新数据进行更新并将前一个顶部可见行保留在原处的方法。
使这变得更加复杂的另一个条件是每一行可以是任何给定的高度,没有为所有行设置高度。
尝试过:这是我迄今为止尝试过的
组件更新后设置initialScrollIndex状态。
结果:没有做任何事情,我猜initialScrollIndex只在初始加载时起作用,甚至在重新加载FlatList数据后也不起作用。
在更新之前和更新之后获取当前滚动偏移 y,计算 FlatList 需要为新项目添加多少估计高度,并调用该估计高度的 this.list.scrollToOffset。
结果:必须让滚动超时才能执行任何操作,这会导致每次更新和滚动后 FlatList 闪烁。(这是唯一有效但不可用的东西)。
使用scrollToIndex 并将基于项目数据的估计高度传递给 FlatList 上的 getItemLayout 属性。
结果:没有移动到预定的行。
在 while 循环中调用scrollToOffset 并逐渐向下滚动 FlatList,直到我进入 onViewableItemsChanged 回调,即我想要可见的行进入视图。
结果: onViewableItemsChanged 在 while 循环中没有被调用,我无法停止导致无限循环。
为每个行项目设置一个引用,并在更新测量行偏移量的新数据后,我希望通过引用可见并滚动到该偏移量。
结果:由于某种原因,前一行的 y 偏移量总是远小于实际偏移量,也许是因为我正在测量接近重新布局或其他我错过的东西。
这是我的一些代码->
const isCloseToBottom = ({ layoutMeasurement, contentOffset, contentSize }) => {
const paddingToBottom = 80
return (
layoutMeasurement.height + contentOffset.y >=
contentSize.height - paddingToBottom
)
}
const isCloseToTop = ({ layoutMeasurement, contentOffset, contentSize }) => {
const paddingToTop = 80
return contentOffset.y <= paddingToTop
}
var initMonthRange = {
start: moment()
.startOf('month')
.startOf('week')
.format('YYYY-MM-DD')
.toString(),
end: moment()
.endOf('month')
.endOf('week')
.format('YYYY-MM-DD')
.toString()
}
export default class AllEventsView extends React.Component {
constructor(props) {
super(props)
this.state = {
events: this.getGroupedEvents(props),
isFetchingFuture: false,
isFetchingPast: false,
isFetching: false,
currentMonth: moment(),
}
this.offset = 0
this.currentVisibleIndex = 0
this.currentVisibleDate = this.getFirstDate()
this.scrollToRef = this.strToMoment(this.currentVisibleDate)
this.heights = []
this._onViewableItemsChanged = this._onViewableItemsChanged.bind(this)
// this.viewabilityConfig = {
// waitForInteraction: true,
// viewAreaCoveragePercentThreshold: 95
// }
this.itemsRefs = {}
this.isDynamicScroll = false
}
itemHeightForData(item) {
let output = 0
if (item) {
let titleHeight = 45
let paddingBottom = 25 // list cont
let marginBottom = 10 // event cont
let rowHeight = 102
// 82 without row height
var { data } = item
if (data) {
data.map(value => {
output += rowHeight // + marginBottom
})
}
output += titleHeight + paddingBottom + marginBottom
} else {
output = 182
}
return output
}
componentWillReceiveProps(nextProps) {
if (nextProps !== this.props) {
var { loading, events } = nextProps
if (
events &&
events.length > 0 &&
!_.isEqual(events, this.props.events) &&
!_.isEqual(events, this.state.events)
) {
this.setState({
isFetching: loading,
isFetchingPast: loading,
isFetchingFuture: loading
})
this.updateEvents(nextProps)
}
}
}
componentDidUpdate(prevProps, prevState) {
var { events } = prevState
if (events && !_.isEqual(events, this.state.events)) {
if (
this.strToMoment(events[0].title).isAfter(this.state.events[0].title)
) {
// new data is in the passed
this.scrollToSelected()
// this.scrollTillCurrentDate()
//this.scrollToDate()
} else if (
this.strToMoment(events[events.length - 1].title).isBefore(
this.state.events[this.state.events.length - 1].title
)
) {
// new data is in the future
}
}
}
momentToStr(date) {
return date.format('YYYY-MM-DD').toString()
}
strToMoment(str) {
return moment(str)
}
getDateForIndex(index) {
var output
var events = this.state.events
if (events && events.length > index) {
output = events[index].title
}
return output
}
getIndexForDate(date, newEvents) {
var output
var array = newEvents || this.state.events
if (array) {
array.map((value, index) => {
var { title } = value
if (title === date) {
output = index
}
})
}
return output
}
getFirstDate() {
return this.strToMoment(this.getDateForIndex(0))
}
getCurrentIndex(newEvents) {
var index = this.getIndexForDate(
this.momentToStr(this.currentVisibleDate),
newEvents
)
return index
}
updateScrollTo(newEvents) {
var index = this.getIndexForDate(
this.momentToStr(this.currentVisibleDate),
newEvents
)
if (index > -1) {
this.currentVisibleIndex = index
this.setState({
currentVisibleIndex: index
})
}
//this.scrollToRef = this.strToMoment(this.currentVisibleDate)
}
updateEvents(nextProps) {
var newEvents = this.getGroupedEvents(nextProps)
var index = this.getCurrentIndex(newEvents)
this.currentVisibleIndex = index
this.setState({
currentVisibleIndex: index,
events: newEvents
})
}
/*scrollToDate() {
var date = this.momentToStr(this.currentVisibleDate)
if (date) {
if (this.itemsRefs[date]) {
this.itemsRefs[date].measure((ox, oy, width, height, px, py) => {
console.log(ox, oy, width, height, px, py)
this.list.scrollToOffset({
offset: py + oy + height,
animated: false
})
})
}
}
}*/
getOffset() {
let scrollPosition = 0
for (let i = 0; i <= this.state.currentVisibleIndex; i++) {
var itemHeight = this.itemHeightForData(this.state.events[i])
scrollPosition += itemHeight
}
return scrollPosition
}
scrollToSelected() {
if (this.list) {
this.isDynamicScroll = true
var scrollPosition = this.getOffset()
setTimeout(() => {
this.list.scrollToOffset({
offset: scrollPosition,
animated: false
})
this.isDynamicScroll = false
}, 80)
}
}
// scrollTillCurrentDate() {
// if (this.list) {
// var maxOffset = this.getOffset() * 2
// var initOffset = 182
// // var beforeDate = moment(this.currentVisibleDate).isBefore(
// // this.state.clonedDate
// // )
// while (initOffset < maxOffset) {
// this.list.scrollToOffset({
// offset: initOffset,
// animated: false
// })
// initOffset += 182
// }
// }
// }
_keyExtractor = (item, index) => item.title
onDayPress(day) {
this.setState({
currentMonth: day
})
var { onDayPress } = this.props
if (onDayPress) {
onDayPress(day)
}
}
fetchPast() {
if (!this.state.isFetching) {
var { setStartDate } = this.props
if (setStartDate) {
var newStart = moment(initMonthRange.start)
.subtract(1, 'month')
.startOf('month')
.startOf('week')
var newDay = {
dateString: newStart
}
this.onDayPress(newDay)
if (newStart.isBefore(initMonthRange.start)) {
initMonthRange.start = newStart
setStartDate(newStart.format('YYYY-MM-DD').toString())
}
}
}
}
fetchFuture() {
if (!this.state.isFetching) {
var { setEndDate } = this.props
if (setEndDate) {
var newEnd = moment(initMonthRange.end)
.add(1, 'month')
.endOf('month')
.endOf('week')
var newDay = {
dateString: newEnd
}
this.onDayPress(newDay)
if (newEnd.isAfter(initMonthRange.end)) {
initMonthRange.end = newEnd
setEndDate(newEnd.format('YYYY-MM-DD').toString())
}
}
}
}
setVisibleIndex(index, key) {
var output = 0
if (index) {
output = index
} else {
var tryIndex = this.getIndexForDate(key)
if (tryIndex) {
output = tryIndex
}
}
return output
}
setVisibleDate(key, item) {
return moment(key || item.title)
}
_onViewableItemsChanged({ viewableItems, changed }) {
if (viewableItems && viewableItems.length > 0) {
var { isViewable, index, key, item } = viewableItems[0]
if (isViewable) {
var visibleIndex = this.setVisibleIndex(index, key)
var visibleDay = this.setVisibleDate(key, item)
this.currentVisibleDate = visibleDay
}
}
}
_onScroll(event) {
var currentOffset = event.nativeEvent.contentOffset.y
var direction = currentOffset > this.offset ? 'down' : 'up'
this.offset = currentOffset
if (!this.isDynamicScroll) {
if (direction === 'up' && isCloseToTop(event.nativeEvent)) {
this.fetchPast()
}
if (direction === 'down' && isCloseToBottom(event.nativeEvent)) {
this.fetchFuture()
}
}
}
onListTouch() {
this.isDynamicScroll = false
}
getHeightForIndex(data, index) {
// return this.heights[index] not usable for preloaded rows
return this.itemHeightForData(this.state.events[index])
}
onRowLayoutChange(ind, event) {
this.heights[ind] = event.nativeEvent.layout.height
}
_renderItem({ item, index }) {
var { title, data } = item
return (
<View
onLayout={this.onRowLayoutChange.bind(this, index)}
ref={i => (this.itemsRefs[title] = i)}
>
<EventsListView
navigation={this.props.navigation}
error={this.props.error}
loading={this.props.loading}
events={data}
selectedDate={title}
/>
</View>
)
}
getGroupedEvents(props) {
var output = []
var groupedEvents = {}
var { events } = props || this.props
if (events) {
events.map(value => {
var { startDate } = value
if (startDate) {
var dateKey = moment(startDate)
.format('YYYY-MM-DD')
.toString()
var containingEvents = []
if (groupedEvents[dateKey]) {
if (groupedEvents[dateKey] && groupedEvents[dateKey].length > 0) {
containingEvents = groupedEvents[dateKey]
}
}
containingEvents.push(value)
groupedEvents[dateKey] = containingEvents
}
})
}
Object.keys(groupedEvents)
.sort((a, b) => {
return (
moment(a)
.toDate()
.getTime() -
moment(b)
.toDate()
.getTime()
)
})
.map(key => {
output.push({ title: key, data: groupedEvents[key] })
})
return output
}
_renderSeparator = () => <View style={style.itemSeparator} />
render() {
var { loading } = this.props
return (
<View style={{ flex: 1 }}>
{this.state.events && this.state.events.length > 0 ? (
<FlatList
style={{ flex: 1 }}
ref={c => (this.list = c)}
data={this.state.events}
extraData={this.state}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem.bind(this)}
onScroll={this._onScroll.bind(this)}
onViewableItemsChanged={this._onViewableItemsChanged}
showsVerticalScrollIndicator={false}
scrollEventThrottle={200}
getItemLayout={(data, index) => ({
length: this.getHeightForIndex(index),
offset: this.getHeightForIndex(index) * index,
index
})}
onMoveShouldSetResponderCapture={() => {
this.onListTouch()
return false
}}
ItemSeparatorComponent={this._renderSeparator}
/>
) : (
<View
style={[
style.nothingYet,
globalStyle.shadowedRowView,
{ marginRight: 0, marginLeft: 0 }
]}
>
<Text style={style.nothingYetText}>{`${
loading ? 'Loading' : 'Nothing Yet'
}...`}</Text>
</View>
)}
</View>
)
}
}
Run Code Online (Sandbox Code Playgroud)
我想要一种有效的方法在新更新之前滚动到上一个可见的顶行。
非常感谢。
PS 我本来打算使用https://github.com/wix/react-native-calendars议程视图,但他们不支持向上滚动以显示获取的新项目,我想我遇到的问题与本机相同编程 iOS 和 Android,这相当简单,例如https://developer.apple.com/documentation/uikit/uitableview/1614879-insertrows将特定数据添加到特定索引或简单地https://developer.apple.com/ Documentation/uikit/uitableview/1614997-scrolltorowatindexpath滚动到所需的可见行。
| 归档时间: |
|
| 查看次数: |
1840 次 |
| 最近记录: |