带有滚动功能的React Native中的可折叠手风琴样式导航

我发现在React Native中找到一个好的,高性能,手风琴风格的导航组件有点困难,这不太复杂。 Oblador有一个相当不错的产品,但是它没有考虑滚动视图中的项目。 我希望能够做的是在用户设备的顶部设置适当的手风琴菜单项,并显示其子类别菜单项。

我在Expnent的Codepen式Snack应用程序中构建了它的精简版,并在此处将其作为开放源代码git repo的更全功能版本。 它是使用标准的react native入门指南构建的,因此您可以遵循这些指示来启动和运行项目。 该代码有很好的注释,但实际上它是由三个部分组成: scroll accordion ,其中包含包含subcategory links touts 。 当您单击某个功能区时,该功能区将滚动到用户设备的顶部,而不管他们在scrollView中的滚动位置如何,并在子类别链接列表中添加动画效果。

棘手的部分是尝试使代码保持动态,以便您的吹捧者可以是任意高度,并且子类别链接的数量可以随类别的不同而变化。 本示例尝试通过在必要时使用PureComponents来执行此操作,并且仅在初始布局上进行尺寸计算,以便在实际单击tout时,我们要做的就是随着子类别链接容器的动画高度和查看滚动。

如果单击另一个提示,我仍然需要实现自动关闭上一个子类别链接容器的功能。 除此之外,还有代码。 让我知道您是否有任何疑问或改进。

  “使用严格” 
 从'react'导入React,{组件}; 
 从'react-native'导入{AppRegistry,Animated,Dimensions,Easing,Image,Text,View,StyleSheet,TouchableOpacity,ScrollView}; 
  const categoryLinks = [//为简单起见,我们对所有tout使用相同的类别链接集 
  {标签:'Subcategory1'}, 
  {标签:'Subcategory2'}, 
  {标签:'Subcategory3'}, 
  {标签:'Subcategory4'}, 
  {标签:'Subcategory5'}, 
  {标签:'Subcategory6'}, 
  {标签:'Subcategory7'}, 
  {标签:'Subcategory8'}, 
  {标签:'Subcategory9'}, 
  {标签:'Subcategory10'}, 
  ] 
  const categoryTouts = [// touts是保存我们链接的可点击图像项 
  {image:require('./ assets / images / image01.jpg'),标题:“ Category1”,链接:categoryLinks}, 
  {image:require('./ assets / images / image02.jpg'),标题:“ Category2”,链接:categoryLinks}, 
  {image:require('./ assets / images / image03.jpg'),标题:“ Category3”,链接:categoryLinks}, 
  ] 
  const SUBCATEGORY_FADE_TIME = 400 //当手风琴动画时淡入/淡出我们的子类别的时间(以毫秒为单位) 
  const SUBCATEGORY_HEIGHT = 40 //为了节省昂贵的测量过程,我们知道子类别的物品将始终具有一致的高度,因此我们可以通过将子类别容器的高度乘以物品数量来计算将其扩展到多大的高度 
  const CONTAINER_PADDING_TOP = 40 //为设备电池条留出空间 
  const categoryLinksLength = categoryLinks.length //子类别项的数量-如果我们不对所有tout使用相同的链接集,则可能需要将其存储在每个tout类中,以了解每个容器应扩展到多大才能显示全部链接 
  const subcategoryContainerHeight = categoryLinksLength * SUBCATEGORY_HEIGHT //容器的总高度 
 导出类CategoryLinks扩展了React.PureComponent {//使用PureComponent将防止不必要的渲染 
  toutPositions = [] //在未展开的情况下将保留每个tout的测量偏移 
  render(){ 
 返回( 
  <Animated.View //视图应该是动画的,因为它的不透明度会改变 
  style = {{position:'absolute',top:0,left:0,opacity:this.props.subcategoryOpacity}} 
  > 
   
  { 
  this.props.links && this.props.links.map((link,index,links)=> {//渲染我们的子类别链接 
 返回( 
  <查看 
 键= {link.label} 
  > 
   {link.label}  
   
  }) 
  } 
   
   
  } 
  } 
 导出类Tout扩展了React.PureComponent {//使用PureComponent将防止不必要的渲染 
 状态= { 
  toutSubcategoriesVisible:false,// true当我们点击了tout并且暴露了子类别项目时 
  } 
  animationValue = new Animated.Value(0)//我们将在0到1之间设置此值的动画以隐藏和显示子类别 
  animCategoryHeight = this.animatedValue.interpolate({ 
  inputRange:[0,1], 
  outputRange:[0,subcategoryContainerHeight],//当动画值为1时,子类别容器将等于链接数*每个链接的高度 
  }) 
  subcategoryOpacity = new Animated.Value(0)//为每个子类别列表的不透明度分别设置动画值,因为我们将使其与高度无关 
  Measurements = {} //将保留每个tout在页面上的位置,以便我们可以自动将其滚动到视图顶部 
  measureToutRef =()=> { 
  this.toutRef.measure((x,y,width,height,pageX,pageY)=> {//测量为我们提供了所有这些属性,因此我们必须捕获它们并只传递我们需要的两个 
  this.measurements.pageY = pageY //整个视图中的Y位置 
  this.measurements.height = height //高度(在我们的示例中,所有高度都相同,但是我们将允许高度以这种方式具有不同的高度) 
  this.props.handleLayout(this.measurements,this.props.toutIndex)//将此传递回父级(scrollAccordion) 
  }) 
  } 
  handlePressTout =()=> { 
  if(this.props.links && this.props.links.length){//如果tout有子类别链接,则根据当前状态隐藏或显示它们 
  const toutSubcategoriesVisible = this.state.toutSubcategoriesVisible 
 如果(toutSubcategoriesVisible){ 
  this.hideToutSubcatgories() 
  } 
 其他{ 
  this.showToutSubcatgories() 
  } 
  } 
  } 
  showToutSubcatgories =()=> { 
  this.setState({toutSubcategoriesVisible:true}) 
  Animated.timing(this.animatedValue,{//将这个值从零动画化为一会更新子类别容器的高度,并对该值进行插值 
 值:1 
 持续时间:SUBCATEGORY_FADE_TIME, 
 缓动:Easing.inOut(Easing.quad), 
  })。start(()=> { 
  this.props.handlePressTout(this.props.toutIndex) 
  }) 
  Animated.timing(this.subcategoryOpacity,{ 
 值:1 
 持续时间:SUBCATEGORY_FADE_TIME, 
 缓动:Easing.inOut(Easing.quad), 
  })。开始() 
  } 
  hideToutSubcatgories =()=> { 
  Animated.timing(this.animatedValue,{ 
  toValue:0, 
 持续时间:SUBCATEGORY_FADE_TIME, 
 缓动:Easing.inOut(Easing.quad), 
  })。start(()=> { 
  this.setState({toutSubcategoriesVisible:false}) 
  }) 
  Animated.timing(this.subcategoryOpacity,{ 
  toValue:0, 
 持续时间:SUBCATEGORY_FADE_TIME, 
 缓动:Easing.inOut(Easing.quad), 
  })。开始() 
  } 
  setToutRef = node => {//存储对tout的引用,以便我们对其进行测量 
 如果(节点){ 
  this.toutRef =节点 
  } 
  } 
  render(){ 
 让categoryLinks 
  if(this.props.links && this.props.links.length){//如果tout有链接,请在此处进行渲染 
  categoryLinks =( 
  <动画视图 
 样式= {{高度:this.animCategoryHeight}} 
  > 
   
   
  }其他{ 
  categoryLinks = null 
  } 
 返回( 
  <查看 
 样式= {this.props.toutIndex === 0?  {marginTop:0}:{marginTop:5}} //如果这是第一个吹捧,则顶部不需要保证金 
  onLayout = {!this.measurements.pageY吗?  this.measureToutRef:()=> null} //如果我们已经拥有此功能的度量,则无需再次渲染它们。 否则,获取测量值 
  > 
  <TouchableOpacity 
  ref = {this.setToutRef} 
  onPress = {this.handlePressTout} 
  > 
  <图像 
 来源= {this.props.image} 
  style = {styles.toutImage} 
 宽度= {'100%'} 
  > 
  <文字 
  style = {styles.toutText} //文本由图像包裹,因此可以很容易地居中 
  > 
  {this.props.title} 
   
   
   
  {categoryLinks} 
   
  } 
  } 
  AppRegistry.registerComponent('Tout',()=> Tout); 
 导出默认类scrollAccordion扩展了React.PureComponent {// scroll手风琴是我们的父类-它呈现了touts及其子类别 
 尺寸= [] 
  handlePressTout =(toutIndex)=> {//当我们按下Tout时,将其动画化到屏幕顶部并显示其子类别 
  this.scrollViewRef.scrollTo({ 
  y:this.measurements [toutIndex] .pageY-CONTAINER_PADDING_TOP, 
  }) 
  } 
  setScrollRef = node => {//存储对滚动视图的引用,以便我们可以调用其scrollTo方法 
 如果(节点){ 
  this.scrollViewRef =节点 
  } 
  } 
  handleLayout =(measurements,toutIndex)=> {//此过程非常昂贵,因此我们仅在必要时进行测量。 可能可以进一步优化... 
  if(!this.measurements [toutIndex]){//如果不存在... 
  this.measurements [toutIndex] =测量// ...将每个兜售的测量值放入数组中的适当位置 
  } 
  } 
  render(){ 
  console.log('render') 
 返回( 
   
  <ScrollView 
  scrollEventThrottle = {20} //限制滚动事件将减少我们存储当前滚动位置的次数。 
  ref = {this.setScrollRef} 
  > 
   
  { 
  categoryTouts.map((tout,index)=> { 
 返回( 
  <Tout 
 键= {索引} 
  toutIndex = {index} // tout索引将帮助我们了解我们正在点击哪个tout 
  {... tout} 
  handleLayout = {this.handleLayout} //当触发布局时,我们可以对其进行测量 
  handlePressTout = {this.handlePressTout} 
  /> 
  }) 
  } 
   
   
   
  } 
  } 
  const styles = StyleSheet.create({ 
 容器: { 
 弹性:1, 
  alignItems:'中心', 
  justifyContent:“中心”, 
  paddingTop:CONTAINER_PADDING_TOP, 
  backgroundColor:“白色”, 
  }, 
  toutText:{ 
 颜色:'白色',背景颜色:'透明',字体重量:'粗体',字体大小:24 
  }, 
  toutImage:{ 
  alignItems:“中心”,justifyContent:“中心” 
  }, 
 子类别链接:{ 
  lineHeight:40, 
  } 
  }); 
  AppRegistry.registerComponent('scrollAccordion',()=> scrollAccordion); 

滚动手风琴
在手机上尝试这个项目! 使用Expo的在线编辑器进行更改并保存您自己的副本。 零食博览会