本文內(nèi)容
1膊畴、SectionList 的簡(jiǎn)單使用
2,右側(cè)索引的實(shí)現(xiàn)
3,實(shí)現(xiàn)sectionList按索引滾動(dòng)的方法
5F8363F3-92EA-4231-806B-F2CAA748605C.png
關(guān)于右側(cè)索引滑動(dòng)實(shí)現(xiàn)的問(wèn)題
<View style={styles.sectionItemViewStyle}
ref="sectionItemView"
onStartShouldSetResponder={()=>true} // 在用戶開(kāi)始觸摸的時(shí)候(手指剛剛接觸屏幕的瞬間),是否愿意成為響應(yīng)者?
onMoveShouldSetResponder={()=>true} // :如果View不是響應(yīng)者寸五,那么在每一個(gè)觸摸點(diǎn)開(kāi)始移動(dòng)(沒(méi)有停下也沒(méi)有離開(kāi)屏幕)時(shí)再詢問(wèn)一次:是否愿意響應(yīng)觸摸交互呢梳凛?
onResponderGrant={this.responderGrant} // View現(xiàn)在要開(kāi)始響應(yīng)觸摸事件了。這也是需要做高亮的時(shí)候梳杏,使用戶知道他到底點(diǎn)到了哪里
onResponderMove={this.responderMove} // 用戶正在屏幕上移動(dòng)手指時(shí)(沒(méi)有停下也沒(méi)有離開(kāi)屏幕)
onResponderRelease={this.responderRelease} // 觸摸操作結(jié)束時(shí)觸發(fā)韧拒,比如"touchUp"(手指抬起離開(kāi)屏幕)
>
{sectionItem}
</View>
有三個(gè)方法可以利用
1,用戶手指按下去 (改變背景色)
2十性,用戶手指移動(dòng)(不離開(kāi)屏幕)(獲取移動(dòng)到那個(gè)一個(gè)確定的位置叛溢,進(jìn)而改變sectionList選中的section)
3,用戶手指抬起 (改變背景色)
· onResponderMove(event) 此方法會(huì)返回一個(gè)pageY的參數(shù)劲适,此參數(shù)是當(dāng)前手指在屏幕的Y坐標(biāo)
·右側(cè)索引表的A~Z 每一個(gè)Item的高度都是固定的楷掉,所以根據(jù)pageY和item的高度,可以計(jì)算出當(dāng)前手指在哪個(gè)item的范圍內(nèi)
/*手指滑動(dòng)霞势,觸發(fā)事件*/
scrollSectionList(event) {
const touch = event.nativeEvent.touches[0];
// 手指滑動(dòng)范圍 從 A-Q 范圍從50 到 50 + sectionItemHeight * cities.length
if (touch.pageY - statusHeight >= sectionTopBottomHeight && touch.pageY <= statusHeight + sectionTopBottomHeight + sectionItemHeight * cities.length) {
//touch.pageY 從頂部開(kāi)始烹植,包括導(dǎo)航條 iOS 如此,如果是android 則具體判斷
const index = (touch.pageY - statusHeight - sectionTopBottomHeight) / sectionItemHeight;
console.log(parseInt(index));
// 默認(rèn)跳轉(zhuǎn)到 第 index 個(gè)section 的第 1 個(gè) item
this.refs.sectionList.scrollToLocation({animated: true, itemIndex: 0, sectionIndex: parseInt(index)});
}
}
完整代碼
/**
* Created by mymac on 2017/8/24.
*/
/**
* Created by mymac on 2017/8/24.
*/
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
SectionList,
Dimensions,
TouchableOpacity,
} from 'react-native';
const {width, height} = Dimensions.get('window');
import cities from './city.json';
const statusHeight = 20;
const rowHeight = 44;
const separatorHeight = 1;
const headerHeight = 24;
const sesctionWidth = 20;
const sectionTopBottomHeight = 50;
const sectionItemHeight = (height - statusHeight - sectionTopBottomHeight * 2) / cities.length;
const touchDownBGColor = '#999999';
const touchUpBGColor = 'transparent';
export default class flatList extends Component {
constructor(props){
super(props);
this.state={
data: [],
refreshing: false,
isTouchDown: false,
};
this.renderItem = this.renderItem.bind(this);
this.separatorComponent = this.separatorComponent.bind(this);
this.listFooterComponent = this.listFooterComponent.bind(this);
this.listHeaderComponent = this.listHeaderComponent.bind(this);
this.sectionItemView = this.sectionItemView.bind(this);
this.itemLayout = this.itemLayout.bind(this);
this.responderGrant = this.responderGrant.bind(this);
this.responderMove = this.responderMove.bind(this);
this.responderRelease = this.responderRelease.bind(this);
this.scrollSectionList = this.scrollSectionList.bind(this);
}
componentDidMount() {
setTimeout(()=>{
this.setState({
data: cities,
});
// 數(shù)據(jù)結(jié)構(gòu)示例
// { key1: "A", data: [{ name: "阿童木" }, { name: "阿瑪尼" }, { name: "愛(ài)多多" }] },
}, 2000)
}
/*section header*/
renderSectionHeader(info){
return (
<Text style={styles.sectionStyle}>
{info.section.key}
</Text>
)
}
/*row*/
renderItem(info){
/*
* index 0
* item { name: "阿童木" } 要顯示的值
* section 當(dāng)前section 的整個(gè)數(shù)據(jù)
* */
return (
<Text style={styles.rowStyle}>
{info.item.name}
</Text>
)
}
/*分割線*/
separatorComponent(){
return <View style={styles.separtorStyle}/>
}
/*底部組件*/
listFooterComponent(){
return this.state.data.length !== 0 ? <View>
<Text style={styles.sectionHeaderFooterStyle}>我是底部組件</Text>
</View> : null;
}
/*頭部組件*/
listHeaderComponent(){
return this.state.data.length !== 0 ? <View>
<Text style={styles.sectionHeaderFooterStyle}>我是頭部組件</Text>
</View> : null;
}
/*沒(méi)有數(shù)據(jù)時(shí)顯示的組件*/
listEmptyComponent() {
return (
<View style={styles.noDataViewStyle}>
<Text style={styles.noDataSubViewStyle}>
暫時(shí)沒(méi)有數(shù)據(jù),先等2秒
</Text>
</View>
)
}
/*刷新*/
refresh(){
this.setState({
refreshing: true,
});
setTimeout(()=>{
this.setState({
refreshing: false,
});
},2000);
}
/*用戶手指開(kāi)始觸摸*/
responderGrant(event){
this.scrollSectionList(event);
this.setState({
isTouchDown: true,
})
}
/*用戶手指在屏幕上移動(dòng)手指愕贡,沒(méi)有停下也沒(méi)有離開(kāi)*/
responderMove(event){
this.scrollSectionList(event);
this.setState({
isTouchDown: true,
})
}
/*用戶手指離開(kāi)屏幕*/
responderRelease(event){
this.setState({
isTouchDown: false,
})
}
/*手指滑動(dòng)草雕,觸發(fā)事件*/
scrollSectionList(event) {
const touch = event.nativeEvent.touches[0];
// 手指滑動(dòng)范圍 從 A-Q 范圍從50 到 50 + sectionItemHeight * cities.length
if (touch.pageY - statusHeight >= sectionTopBottomHeight && touch.pageY <= statusHeight + sectionTopBottomHeight + sectionItemHeight * cities.length) {
//touch.pageY 從頂部開(kāi)始,包括導(dǎo)航條 iOS 如此固以,如果是android 則具體判斷
const index = (touch.pageY - statusHeight - sectionTopBottomHeight) / sectionItemHeight;
console.log(parseInt(index));
// 默認(rèn)跳轉(zhuǎn)到 第 index 個(gè)section 的第 1 個(gè) item
this.refs.sectionList.scrollToLocation({animated: true, itemIndex: 0, sectionIndex: parseInt(index)});
}
}
/*右側(cè)索引*/
sectionItemView(){
const sectionItem = cities.map((item, index)=>{
return <Text key={index}
style={
[styles.sectionItemStyle,
{backgroundColor: this.state.isTouchDown ? touchDownBGColor : touchUpBGColor}]
}
>
{item.key}
</Text>
});
return(
<View style={styles.sectionItemViewStyle}
ref="sectionItemView"
onStartShouldSetResponder={()=>true} // 在用戶開(kāi)始觸摸的時(shí)候(手指剛剛接觸屏幕的瞬間)墩虹,是否愿意成為響應(yīng)者?
onMoveShouldSetResponder={()=>true} // :如果View不是響應(yīng)者憨琳,那么在每一個(gè)觸摸點(diǎn)開(kāi)始移動(dòng)(沒(méi)有停下也沒(méi)有離開(kāi)屏幕)時(shí)再詢問(wèn)一次:是否愿意響應(yīng)觸摸交互呢诫钓?
onResponderGrant={this.responderGrant} // View現(xiàn)在要開(kāi)始響應(yīng)觸摸事件了。這也是需要做高亮的時(shí)候篙螟,使用戶知道他到底點(diǎn)到了哪里
onResponderMove={this.responderMove} // 用戶正在屏幕上移動(dòng)手指時(shí)(沒(méi)有停下也沒(méi)有離開(kāi)屏幕)
onResponderRelease={this.responderRelease} // 觸摸操作結(jié)束時(shí)觸發(fā)尖坤,比如"touchUp"(手指抬起離開(kāi)屏幕)
>
{sectionItem}
</View>
);
}
/*每一項(xiàng)的高度(rowHeight)和其在父組件中的偏移量(offset)和位置(index)
* length : 當(dāng)前rowItem的高度
* offset : 當(dāng)前rowItem在父組件中的偏移量(包括rowItem的高度 + 分割線的高度 + section的高度)
* index : 當(dāng)前rowItem的位置
*
* 如果需要手動(dòng)的跳轉(zhuǎn)。則必須實(shí)現(xiàn)此方法
* */
itemLayout(data, index) {
return {length: rowHeight, offset: (rowHeight + separatorHeight) * index + headerHeight, index};
}
// http://www.reibang.com/p/09dd60d7b34f
render() {
return (
<View style={{flex: 1, marginTop: statusHeight}}>
<SectionList
ref="sectionList"
renderSectionHeader={this.renderSectionHeader} // sectionHeader
renderItem={this.renderItem} // rowItem
sections={this.state.data} // 數(shù)據(jù)源
// getItemLayout={this.itemLayout} // 在知道高度的情況下闲擦,得到每個(gè)item的位置 , 由于我改了源碼慢味,在源碼中跳轉(zhuǎn)指定了位置信息场梆,此處不實(shí)現(xiàn)該方法
keyExtractor={(item, index)=> item.code} // 每個(gè)item都有唯一的key
ItemSeparatorComponent={this.separatorComponent} // 分割線
ListHeaderComponent={this.listHeaderComponent} // 頭部組件
ListFooterComponent={this.listFooterComponent} // 尾部組件
ListEmptyComponent={this.listEmptyComponent()} // 沒(méi)有數(shù)據(jù)時(shí)顯示的組件
refreshing={this.state.refreshing} // 是否刷新 ,自帶刷新控件
onRefresh={()=>{
this.refresh();
}} // 刷新方法,寫了此方法纯路,下拉才會(huì)出現(xiàn) 刷新控件或油,使用此方法必須寫 refreshing
// horizontal={true}
/>
{this.sectionItemView()}
</View>
);
}
}
const styles = StyleSheet.create({
sectionStyle:{
color: 'black',
backgroundColor: '#f5f5f5',
paddingLeft: 20,
height: headerHeight,
lineHeight: headerHeight,
},
rowStyle:{
height: rowHeight,
lineHeight: rowHeight,
width: width,
marginLeft: 30,
color: 'black',
},
separtorStyle:{
height: separatorHeight,
backgroundColor: '#f5f5f5'
},
sectionHeaderFooterStyle:{
alignItems: 'center',
textAlign: 'center',
height: sectionTopBottomHeight,
backgroundColor: 'red',
lineHeight: sectionTopBottomHeight,
},
noDataViewStyle:{
backgroundColor: 'red',
flex: 1,
height: height
},
noDataSubViewStyle:{
alignItems: 'center',
textAlign: 'center',
lineHeight: height,
color: 'white'
},
sectionItemViewStyle:{
position: 'absolute',
width: sesctionWidth,
height: height - statusHeight,
right: 0,
top: 0,
paddingTop: sectionTopBottomHeight,
paddingBottom: sectionTopBottomHeight,
},
sectionItemStyle:{
textAlign: 'center',
alignItems: 'center',
height: sectionItemHeight,
lineHeight: sectionItemHeight
},
});
AppRegistry.registerComponent('RNProjectTestApp', () => flatList);
數(shù)據(jù)結(jié)構(gòu),摘取其中部分
其中 key 這個(gè)字段 驰唬,最好是帶上顶岸,且必須是唯一的
data 這個(gè)字段是必須的,源碼里面就是 .data 出來(lái)得到數(shù)據(jù)的
[
{
"key": "A",
"data": [
{
"name": "阿拉善盟",
}
]
},
{
"key": "B",
"data": [
{
"name": "北京市",
}
]
},
{
"key": "C",
"data": [
{
"name": "承德市",
}
]
},
{
"key": "D",
"data": [
{
"name": "大同市",
}
]
}
]
重中之重的重點(diǎn):
this.refs.sectionList.scrollToLocation({animated: true, itemIndex: 0, sectionIndex: parseInt(index)});
sectionList 只包含一個(gè) scrollToLocation 的方法叫编,實(shí)現(xiàn)方法
路徑:
node_modules/react-native/Libraries/Lists/VirtualizedSectionList.js,代碼格式化后大概在150行的位置
scrollToLocation(params: {
animated?: ?boolean,
itemIndex: number,
sectionIndex: number,
viewPosition?: number,
}) {
let index = params.itemIndex + 1;
for (let ii = 0; ii < params.sectionIndex; ii++) {
index += this.props.sections[ii].data.length + 2;
}
const toIndexParams = {
...params,
index,
};
this._listRef.scrollToIndex(toIndexParams);
}
本人腦子不夠用辖佣,實(shí)在沒(méi)有看懂這里面的邏輯,for 里面的 +2 搓逾,實(shí)在是沒(méi)有看懂卷谈,而且用了這個(gè) scrollToLocation 方法,并不能真正的跳轉(zhuǎn)到我想跳轉(zhuǎn)的位置霞篡,測(cè)試了好幾天世蔗,很尷尬??
本人的解決辦法,重新改造了新的 scrollToLocation 的方法朗兵,
scrollToLocation(params: {
animated?: ?boolean,
itemIndex: number,
sectionIndex: number,
viewPosition?: number,
}) {
// 44 = rowItem 的高度
const rowItemHeight = 44;
// 24 = sectionItem 的高度
const sectionItemHeight = 24;
// 50 = 頭部item 的高度
const sectionHeaderHeight = 50;
// 1 = rowItem 之間的分割線的高度
const separatorHeight = 1;
// 當(dāng)前 section 之前的 section 的 rowItem(分割線) 的總高度 污淋, 默認(rèn)為頭部高度
let upRowHeight = sectionHeaderHeight;
for (let ii = 0; ii < params.sectionIndex; ii++) {
// rowItem 的高度 + 分割線的高度 + sectionHeader 的高度,
upRowHeight += this.props.sections[ii].data.length * rowItemHeight + (this.props.sections[ii].data.length - 1) * separatorHeight + sectionItemHeight;
}
// 當(dāng)前需要顯示的 rowItem 偏移量 所有rowItem的高度 + 所有分割線的高度
let downHeight = params.itemIndex * rowItemHeight + params.itemIndex * separatorHeight;
// 滾動(dòng)到具體的位置
this._listRef.scrollToOffset({ animated: true, offset: upRowHeight + downHeight })
}
·原本的 scrollToLocation 的方法內(nèi)部是根據(jù) scrollToIndex 來(lái)跳轉(zhuǎn)的
·我的 scrollToLocation 的方法內(nèi)部是根據(jù) scrollToOffset 來(lái)跳轉(zhuǎn)的
由于改了源碼余掖,所以缺點(diǎn)很明顯寸爆。。盐欺。而昨。
但我是實(shí)在理解不了原來(lái)的 scrollToLocation 方法里面的邏輯,希望有大神能注意到這里的話找田,麻煩給講解下??~O(∩_∩)O謝謝
??????????????????????????????????
根據(jù)評(píng)論的大神們說(shuō):多些一個(gè) viewOffset
就可以了歌憨,本人沒(méi)試~
this.sectionList.scrollToLocation({animated: true,sectionIndex:index, itemIndex:0, viewOffset:26})