由于项目需求,需要做个单词本并且可跳转的功能。网上找了很久,有用老版本的ListView的,也有说改源码virtualList的,现在用的新版SectionList,是性能较好的,且适合做这种分组滚动跳转的组件。
由于官网上的讲解文档不是很详细,跳转原理刚开始没搞清楚导致浪费了大量的时间研究跳转方法中的参数是什么意思 - -
后面百度到了解了SectionList组件的跳转原理后,做出来了单词本功能~
供大家参考
-
效果图
1.SectionList中scrollToLocation()
和getItemLayout
理解
scrollToLocation(params: object) =>这是官方文档的解释
将可视区内位于特定`sectionIndex` 或 `itemIndex` (section内)位置的列表项,滚动到可视区的制定位置。
比如说,`viewPosition` 为0时将这个列表项滚动到可视区顶部 (可能会被顶部粘接的header覆盖),
为1时将它滚动到可视区底部,为0.5时将它滚动到可视区中央。`viewOffset`是一个以像素为单位,
到最终位置偏移距离的固定值,比如为了弥补粘接的header所占据的空间
注意: 如果没有设置`getItemLayout`,就不能滚动到位于外部渲染区的位置。
下面引用会开花的树-通俗易懂的解释。
scrollToLocation
方法,可以传递sectionIndex
,itemIndex
,viewOffset
三个参数,达到滚动到SectionList
的某一个位置的效果
➡️戳这里了解跳转原理、参数意思以及index的理解
2.核心代码展示附注释
//@author:Benny
//@date:2018.06.19
//@description:单词本
import React from 'react';
import {
View,
Text,
TouchableOpacity,
SectionList
} from 'react-native';
import { commonStyles, studyWordStyles } from '../../../../styles';
const ITEM_HEIGHT = 44; //item的高度
const HEADER_HEIGHT = 24; //分组头部的高度
export default class Index extends Component {
constructor(props) {
super(props);
this.state = {
wordList: [{ "title": "C", "data": [{ "Name": "complication", "Id": 3614 }] }, { "title": "D", "data": [{ "Name": "dominate", "Id": 5378 }] }, { "title": "E", "data": [{ "Name": "educate", "Id": 5417 }] }, { "title": "I", "data": [{ "Name": "ignore", "Id": 5686 }, { "Name": "intake", "Id": 4092 }, { "Name": "interaction", "Id": 4103 }] }, { "title": "M", "data": [{ "Name": "mutual", "Id": 1004 }] }, { "title": "N", "data": [{ "Name": "natural habitat", "Id": 4272 }, { "Name": "negatively", "Id": 4288 }, { "Name": "nutrition", "Id": 2648 }] }, { "title": "O", "data": [{ "Name": "obesity", "Id": 2652 }, { "Name": "over-consumption", "Id": 1074 }] }, { "title": "P", "data": [{ "Name": "professional", "Id": 6066 }, { "Name": "project", "Id": 6073 }] }, { "title": "R", "data": [{ "Name": "reveal", "Id": 4480 }] }, { "title": "S", "data": [{ "Name": "submit", "Id": 6334 }] }, { "title": "U", "data": [{ "Name": "urban", "Id": 1588 }] }, { "title": "W", "data": [{ "Name": "well-preserved", "Id": 4843 }, { "Name": "widespread", "Id": 4883 }] }],
}
}
_getData() {
// 这块是请求后台接口获取单词数据的,并且格式化成 title,data的格式 (供参考)
//$http.post($urls.studentApi.Study_Word_GetWordListJson).then((data) => {
// if (data.Rstatus) {
// let list = data.Rdata.map(item => {
// return {
// title: item.ClassifyName,
// data: item.List.map(w => {
// return {
// Name: w.WordName,
// Id: w.WordId
// }
// }),
// };
// });
// console.log(JSON.stringify(list))
//
// this.setState({
// wordList: list,
// isLoading: false
// })
// }
// });
}
/*最主要的是这块代码 (计算每一项Item对应的位移及索引)
list指以title、data为格式的数组(参考this.state.wordList的数据)*/
_setItemLayout(list) {
//ITEM_HEIGHT是每一项的高度、HEADER_HEIGHT是每一个SectionHeader的高度
//例如单词C这一行的高度就是HEADER_HEIGHT,complication这一行的高度就是ITEM_HEIGHT
let [itemHeight, headerHeight] = [ITEM_HEIGHT, HEADER_HEIGHT];
let layoutList = [];
let layoutIndex = 0;//索引
let layoutOffset = 0;//偏移量
list.forEach(section => {
layoutList.push({
index: layoutIndex,
length: headerHeight,
offset: layoutOffset,
});
layoutIndex += 1; //Header索引
layoutOffset += headerHeight; //加上头部title高度
section.data.forEach(() => { //遍历每一个section里头的data值
layoutList.push({
index: layoutIndex,
length: itemHeight,
offset: layoutOffset,
});
layoutIndex += 1; //各项item的索引
layoutOffset += itemHeight; //加上各项item高度
});
layoutList.push({
index: layoutIndex,
length: 0,
offset: layoutOffset,
});
layoutIndex += 1; //footer索引
// if(screenH-layoutOffset)
});
this.layoutList = layoutList;
}
_getItemLayout(data, index) {
//渲染元素在所有渲染元素中的位置(数组中的索引)
let layout = this.layoutList.filter(n => n.index == index)[0];
return layout;
}
_keyExtractor = (item, index) => index;
_onSectionselect = (k) => {
//跳转到某一项
this.sectionList.scrollToLocation(
{
sectionIndex: k,//右边字母点击后的索引值 k
itemIndex: 0,
viewOffset: HEADER_HEIGHT,//向下偏移一个头部高度
}
);
}
_renderItem = ({ item }) => {
return (
//这里的点击事件可以去掉。用来点击某个单词进入到单词详情
<TouchableOpacity style={studyWordStyles.sectionItem} onPress={() => {
this.props.navigation.navigate('StudyWordbookDetail', {
WordName: item.Name,
IsExistWordBook: true,
callback: () => {
this._getData()
}
})
}}>
<Text style={commonStyles.text6}>{item.Name}</Text>
</TouchableOpacity>
)
}
_wordListView() {
//判断单词本是否有数据,没有的会展示空记录
if (this.state.wordList && this.state.wordList.length > 0) {
return (
<View style={studyWordStyles.sectionContainer}>
<SectionList
ref={(ref) => { this.sectionList = ref }}
showsVerticalScrollIndicator={false}
getItemLayout={this._getItemLayout.bind(this)}
keyExtractor={this._keyExtractor}
sections={this.state.wordList}
renderItem={this._renderItem}
renderSectionHeader={({ section }) => <View style={studyWordStyles.sectionHeader}><Text style={studyWordStyles.sectionHeaderTxt}>{section.title}</Text></View>}
/>
/*右侧单词索引*/
<View style={studyWordStyles.titleListWrapper}>
<View style={studyWordStyles.sectionTitleList}>
{
this.state.wordList.map((v, k) => {
return (
<Text style={studyWordStyles.titleText} key={k} onPress={() => { this._onSectionselect(k) }}>{v.title}</Text>
)
})
}
</View>
</View>
</View>
)
}
}
componentDidMount() {
this._setItemLayout(this.state.wordList);
}
render() {
return (
<View style={commonStyles.container}>
{
this._wordListView()
}
</View>
);
}
}
-
studyWordStyles
文件样式代码(这里就不把所有代码贴出来啦。values对象的某些值自己修改下就行了)
import {
Platform,
StyleSheet,
Dimensions
} from 'react-native';
import * as values from '../../values';
const ITEM_HEIGHT = 44; //item的高度
const HEADER_HEIGHT = 24; //分组头部的高度
const screenHeight = Dimensions.get('window').height;
export default StyleSheet.create({
//单词本
container: {
flex: 1,
paddingTop: 22,
},
sectionContainer: {
marginBottom: 50
},
sectionHeader: {
height: HEADER_HEIGHT,
paddingLeft: 10,
justifyContent: 'center',
backgroundColor: 'rgba(247,247,247,1.0)',
},
sectionHeaderTxt: {
fontSize: 14,
fontWeight: 'bold',
color: values.color666
},
sectionItem: {
height: ITEM_HEIGHT,
paddingLeft: 10,
justifyContent: 'center',
borderBottomWidth: values.miniWidth,
borderBottomColor: '#eee',
backgroundColor: '#fff',
},
//右侧标题
titleListWrapper: {
position: 'absolute',
height: screenHeight,
top: 0,
right: 10,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
sectionTitleList: {
paddingVertical: 5
},
titleText: {
color: values.color666,
textAlign: 'center',
paddingVertical: 1,
width: 20,
fontSize: values.fontSize12
},
inactivetext: {
fontWeight: '700',
color: '#CCCCCC'
},
//单词本详情
wordPan: {
paddingVertical: 20,
paddingHorizontal: 15,
borderBottomWidth: values.miniWidth,
borderBottomColor: '#eee',
},
wordText: {
fontSize: 30,
},
wordSoundPan: {
marginTop: 20,
flexDirection: 'row',
alignItems: 'center',
},
iconSize: {
marginRight: 10,
color: values.sysColorGreen,
fontSize: 20,
},
soundText: {
color: values.color666,
marginRight: 30,
},
translationText: {
marginTop: 15,
color: values.color666,
},
examplePan: {
paddingHorizontal: 15,
paddingVertical: 20,
},
eTitle: {
fontSize: 20,
marginBottom: 10,
},
media: {
marginTop: 0,
paddingTop: 10,
paddingBottom: 10,
display: "flex",
flexDirection: "row",
},
sentenceText: {
marginBottom: 10
},
translationSentence: {
lineHeight: 20,
},
addWrapper: {
flexDirection: 'row',
position: 'absolute',
bottom: 0,
left: 0,
width: values.screenWidth,
height: 50,
// backgroundColor: '#000',
alignItems: 'center',
justifyContent: 'center',
borderTopWidth: values.miniWidth,
borderTopColor: values.colorBg,
},
addContainer: {
width: values.screenWidth,
},
mgtl: {
paddingLeft: 10
}
});
3.最后
总结下...关键的思路就是计算好itemLayout。各个项位置偏移算准了,点击索引跳转的位置才会准确。一个section的索引包含sectionHeader、Item(可能不止一个)、以及sectionFooter。索引从0开始计。主要的方法在2中_setItemLayout (list)
中。
有什么意见或者疑问都可以评论哦~
路过的小伙伴们觉得还不错就点个赞吧~