前言

上个月接到一个需求对会员的数据展示、统计。这个也不复杂,当时定的技术是使用react-native实现,有吸顶的效果,在实现的过程中遇到的问题就记录一下。

效果

思路

  • 1、利用FlatList的stickyHeaderIndices属性实现
  • 2、通过监听滚动,判断滚动位置来控制是否固定在顶部

实现1: 利用FlatList的stickyHeaderIndices属性实现

代码

import React, { useEffect } from 'react'
import { View, StyleSheet, FlatList, findNodeHandle, NativeScrollEvent } from 'react-native'
import { NavigationInjectedProps } from 'react-navigation'
import { useDispatch, useSelector } from 'react-redux'
import { FloatingHeader, LoadingFooter, PageFooter, Provider } from '@comps'
import { setState, resetFilter } from '@pages/member-analysis/slice'
import { PageBgEnum } from '@pages/member-analysis/enum'
import {getStoreCustomerStatistics,getPullNewStatistics,queryMemberList,Member
} from '@pages/member-analysis/service'
import { HeaderThemeEnum, px2Dp } from '@kit'
import { RootState, AppDispatch } from '@store'
import { common } from '@config/common'
import { WithScreenProps } from '@type'import CardStatistics from '@pages/member-analysis/components/card-statistics'
import ConsumptionData from '@pages/member-analysis/components/consumption-data'
import UserListSelection from '@pages/member-analysis/components/user-list-selection'
import UserItem from '@pages/member-analysis/components/user-item'
import BottomButton from '@pages/member-analysis/components/bottom-btn'
import PhoneModal from '@pages/member-analysis/components/phone-modal'
import SourceChannelModal from '@pages/member-analysis/components/source-channel-modal'
import NoData from '@pages/member-analysis/components/no-data'const MemberAnalysis = (props: MemberAnalysisProps) => {const { navigation, screenProps } = propsconst { storeCode } = screenPropsconst dispatch: AppDispatch = useDispatch()const dataRef = React.createRef<View>()const listRef = React.createRef<FlatList>()const {dataList,memberType,pageBgColor,currentPageNum,isLoading,hasMore,purchasedCategory,purchasedBrand,beginDate,endDate,sureFlag} = useSelector((state: RootState) => state.memberAnalysis)useEffect(() => {queryCustomerStatistics()queryPullNewStatistics()}, [])useEffect(() => {queryMemberListByPage(1)}, [sureFlag])return (<Provider><Viewstyle={[styles.container,{ paddingTop: px2Dp(88) + common.currentValue.statusBarHeight, backgroundColor: pageBgColor }]}><FloatingHeaderonBack={() => dispatch(resetFilter())}title="会员分析"headerTheme={HeaderThemeEnum.transparent}navigation={navigation}/>{dataList.length ? (<FlatList<Member>ref={listRef}data={dataList}style={styles.container}ListHeaderComponent={<><CardStatistics /><View ref={dataRef} style={styles.bg}><ConsumptionData /></View></>}stickyHeaderIndices={[1]}renderItem={({ item, index }) => {if (item?.itemType === 'topSelection' && index === 0) {return <UserListSelection navigation={navigation} />} else {return <UserItem key={item.snCustNum} data={item} navigation={navigation} />}}}ListEmptyComponent={<NoData />}onScroll={({ nativeEvent }) => onPageScroll(nativeEvent)}onEndReached={() => {if (!isLoading && hasMore) {queryMemberListByPage(currentPageNum + 1)}}}ListFooterComponent={isLoading && hasMore ? (<LoadingFooter />) : !isLoading && !hasMore && dataList.length > 0 ? (<PageFooter />) : null}/>) : null}<BottomButton navigation={navigation} /><PhoneModal /><SourceChannelModal /></View></Provider>)async function queryCustomerStatistics() {getStoreCustomerStatistics(storeCode).then(res => {dispatch(setState({ consumption: res }))})}async function queryPullNewStatistics() {getPullNewStatistics(storeCode).then(res => {dispatch(setState({ pullNewList: res }))})}async function queryMemberListByPage(currentPage: number) {await dispatch(setState({ isLoading: true }))const params = {storeCode,memberType,categoryCode: purchasedCategory,brandCode: purchasedBrand,startTime: beginDate,endTime: endDate,pageNumber: currentPage,pageSize: 15}const res = await queryMemberList(params)const data = res?.dataList || []const list = currentPage === 1 ? data : dataList.concat(data)if (currentPage === 1) {list.unshift({ itemType: 'topSelection' })}dispatch(setState({isLoading: false,isSelectAll: false,dataList: list,totalCount: res.totalCount,currentPageNum: currentPage,hasMore: currentPage < res.totalPageCount}))}function onPageScroll(nativeEvent: NativeScrollEvent) {const offsetTop = nativeEvent.contentOffset.ydataRef.current?.measureLayout(findNodeHandle(listRef.current) || 0,(left: number, top: number) => {dispatch(setState({pageBgColor: offsetTop >= top ? PageBgEnum.WHITE : PageBgEnum.BLUE}))},() => {})}
}export default MemberAnalysistype MemberAnalysisProps = WithScreenProps<{}> & NavigationInjectedPropsconst styles = StyleSheet.create({container: {flex: 1},bg: {backgroundColor: 'transparent'},menuContainer: {flex: 1,flexDirection: 'row',flexWrap: 'wrap',paddingHorizontal: px2Dp(24),paddingVertical: px2Dp(18),justifyContent: 'space-between'}
})

android端 必现如下错误, ios端正常

stickyHeaderIndices的值必须是数字数组,而且data的length必须大于0,不然在安卓会稳定触发index=xx,count=0的报错。也就是说用了stickyHeaderIndices这个属性,在
指定哪个子元素吸顶的时候,一定要保证这个元素已经渲染,首次渲染的时候,data为空或者为null的情况就要判断

实现2: 通过监听滚动,判断滚动位置来控制是否固定在顶部

const MemberAnalysis = (props: MemberAnalysisProps) => {const { navigation, screenProps } = propsconst { storeCode } = screenPropsconst dispatch: AppDispatch = useDispatch()const dataRef = React.createRef<View>()const listRef = React.createRef<FlatList>()const selectionRef = React.createRef<View>()const {dataList,memberType,pageBgColor,currentPageNum,isLoading,hasMore,purchasedCategory,purchasedBrand,beginDate,endDate,sureFlag} = useSelector((state: RootState) => state.memberAnalysis)useEffect(() => {queryCustomerStatistics()queryPullNewStatistics()}, [])useEffect(() => {queryMemberListByPage(1)}, [sureFlag])return (<Provider><Viewstyle={[styles.container,{ paddingTop: px2Dp(88) + common.currentValue.statusBarHeight, backgroundColor: pageBgColor }]}><FloatingHeaderonBack={() => dispatch(resetFilter())}title="会员分析"headerTheme={HeaderThemeEnum.transparent}navigation={navigation}/><FixedUserListSelection navigation={navigation} /><FlatList<Member>ref={listRef}data={dataList}style={styles.container}ListHeaderComponent={<><CardStatistics /><View ref={dataRef} style={styles.bg}><ConsumptionData /><UserListSelection ref={selectionRef} isFixed={false} navigation={navigation} /></View></>}initialNumToRender={15}keyExtractor={(item, index) => `${index}`}renderItem={({ item }) => {return <UserItem key={item.snCustNum} data={item} navigation={navigation} />}}ListEmptyComponent={<NoData />}onScroll={({ nativeEvent }) => onPageScroll(nativeEvent)}onEndReached={() => {if (!isLoading && hasMore) {queryMemberListByPage(currentPageNum + 1)}}}ListFooterComponent={isLoading && hasMore ? (<LoadingFooter />) : !isLoading && !hasMore && dataList.length > 0 ? (<PageFooter />) : null}/><BottomButton navigation={navigation} /><PhoneModal /><SourceChannelModal /></View></Provider>)async function queryCustomerStatistics() {getStoreCustomerStatistics(storeCode).then(res => {dispatch(setState({ consumption: res }))})}async function queryPullNewStatistics() {getPullNewStatistics(storeCode).then(res => {dispatch(setState({ pullNewList: res }))})}async function queryMemberListByPage(currentPage: number) {await dispatch(setState({ isLoading: true }))const params = {storeCode,memberType,categoryCode: purchasedCategory,brandCode: purchasedBrand,startTime: beginDate,endTime: endDate,pageNumber: currentPage,pageSize: 15}const res = await queryMemberList(params)const data = res?.dataList || []const list = currentPage === 1 ? data : dataList.concat(data)dispatch(setState({isLoading: false,isSelectAll: false,dataList: list,totalCount: res.totalCount,currentPageNum: currentPage,hasMore: currentPage < res.totalPageCount}))}function onPageScroll(nativeEvent: NativeScrollEvent) {const offsetTop = nativeEvent.contentOffset.ydataRef.current?.measureLayout(findNodeHandle(listRef.current) || 0,(left: number, top: number) => {dispatch(setState({pageBgColor: offsetTop >= top ? PageBgEnum.WHITE : PageBgEnum.BLUE}))},() => {})selectionRef.current?.measureLayout(findNodeHandle(listRef.current) || 0,(left: number, top: number) => {dispatch(setState({selectionFixed: offsetTop >= top}))},() => {})}
}export default MemberAnalysistype MemberAnalysisProps = WithScreenProps<{}> & NavigationInjectedPropsconst styles = StyleSheet.create({container: {flex: 1},bg: {backgroundColor: 'transparent'},menuContainer: {flex: 1,flexDirection: 'row',flexWrap: 'wrap',paddingHorizontal: px2Dp(24),paddingVertical: px2Dp(18),justifyContent: 'space-between'}
})

通过滚动距离进行判断

 selectionRef.current?.measureLayout(findNodeHandle(listRef.current) || 0,(left: number, top: number) => {dispatch(setState({selectionFixed: offsetTop >= top}))},() => {})

根据selectionFixed控制吸顶

总结

  • 实现1是通过官方的api实现,动画效果更好,使用也简单些,缺点存在平台兼容问题
  • 实现2是通过监听滚动,控制显示与隐藏,所以兼容性很好,缺点就是动画效果需要自己实

React Native 如何实现吸顶效果相关推荐

  1. 30秒实现Vue吸顶效果

    酱酱,好久不见鸭! 前言:吸顶效果图: 1.滚动前: image.png 2.滚动中: image.png 3.滚动超过后: image.png 直观效果可参pc端微博左侧的信息栏 第一步:html ...

  2. vue音乐项目歌手页面滚动、吸顶效果

    总结singer页面: 1.api中去获取 ['热',A-Z] 以及根据['热',A-Z]获取的所有歌手的数据 2.渲染数据 2.1 渲染左边 字母title ['热',A-Z] + 该字母开头的歌手 ...

  3. 微信小程序中实现吸顶效果(流畅、不卡顿)

    欢迎访问我的 个人博客 最开始的时候,在小程序中实现吸顶效果,开发工具看起来还挺好的,但是在真机上就会有问题了. 原因是我不停的去 setData 会导致操作反馈延迟严重,无法及时将操作处理结果及时传 ...

  4. 最贴近京东首页体验的嵌套滑动吸顶效果

    吸顶效果是各家 App 或多或少都会用的一个交互,这种交互也常见于 PC.H5,可以说是一种通用性很强的前端交互体验,相比较来说京东首页的嵌套滑动吸顶效果是各个类似效果中体验比较好的一个,因为在嵌套布 ...

  5. vue中怎么实现吸顶效果

    在 web 应用中,我们经常需要让页面中的一个或多个元素在页面滚动时保持固定位置.这种效果通常被称为吸顶效果,因为它使元素像粘在页面顶部一样固定不动. 在 Vue 中,实现吸顶效果有不同的方法.本文将 ...

  6. Android - 吸顶效果 布局篇

    调研了一下微博和豆瓣等大体量的APP,发现内容详情页的评论吸顶效果非常常见. 以截图自豆瓣的效果为例,当上划至内容部分消失时,滑动中的回复条会置顶,并保持在位置不动. 笔者通过实践,记录下目前发现的最 ...

  7. Flutter吸顶效果

    前言: 关于吸顶效果,在Flutter当中,已经提供了这么一个控件,但是由于太复杂,所以网上的资料有些少,本文章主要利用Flutter自带的这种吸顶动画控件,配合着动画完成的一个用户中心的页面. im ...

  8. vue3实现吸顶效果

    一.HTML 用样式控制,如果isFixed 等于true,将内容固定到顶部 <div :class="isFixed ? 'is-fixed' : 'txt'" ref=& ...

  9. flutter 吸顶效果

    flutter 吸顶问题 今天在做一个吸顶效果时,发现了一些问题: 我是用的是官方widget  CustomScrollView+SliverAppBar 可向下拉伸并虚化背景功能代码: Custo ...

最新文章

  1. Vue项目如何提高效率?大厂2大实践总结告诉你
  2. C语言模拟质点运动轨迹坐标,C语言定时器的使用 计算质点运动的移位
  3. [ios2]ios系统中各种设置项的url链接
  4. 使用WebLogic共享库连续交付ADF应用程序
  5. jqprint控件使用
  6. 《LoadRunner 没有告诉你的》之四——理解性能
  7. 对mysql进行压力测试_mysqlslap对mysql进行压力测试
  8. 实例分解神经网络反向传播算法(转)
  9. 集群高并发情况下如何保证分布式唯一全局ID生成
  10. 【实习之T100开发】T100 单档程序开发(2)添加功能
  11. 获取指定域名的IP地址
  12. c语言有理数均值思路,5-35 有理数均值 (20分)
  13. java发送163邮件
  14. Java 将图片或者视频模糊化(附代码) | Java工具类
  15. 算法设计与分析(第2版)屈婉玲 刘田 张立昂 王捍贫编著 第三章课后习题答案
  16. 用 HTML 做一个表单模板
  17. 科技爱好者周刊(第 208 期):晋升制度的问题
  18. Android Studio:使用SVN进行版本控制
  19. 应用结构体实现通讯录
  20. CAD文件怎么打印成黑白图片教程

热门文章

  1. [模板] 快速傅里叶变换(FFT)
  2. Jazzy--一种新的拼写检查器API(Java平台)
  3. 编程入门c语言ppt,C语言入门经典-C语言编程.ppt
  4. 用 Python+openpose 实现抖音尬舞机 1
  5. linux df-h命令详细,df命令 – 显示磁盘空间使用情况
  6. dnf剑魂buff等级上限_DNF:打团这种奶妈不能放,别看buff等级高,这一点是硬伤...
  7. 苹果手机功能大全介绍_苹果手机备忘录功能,纸质文档扫一下电子化,学会秒变手机达人...
  8. [算法入土之路]二叉树
  9. Tx2上人体姿态估计AlphaPose配置安装教程
  10. DR/BDR的选举规则?