react native 右侧字母滚动定位列表

400 阅读2分钟

右侧字母手指触摸滑动,以及列表手指触摸滑动,通过setNativeProps方法使字母滑动顺畅

import React, {Component} from 'react';
import {
  Image,
  PanResponder,
  ScrollView,
  StyleSheet,
  Text,
  TextInput,
  TouchableOpacity,
  View,
} from 'react-native';
import {Brands, BrandVo} from './vo/CarBrandVo';

class ChoosePage extends Component<any, any> {
  constructor(props) {
    super(props);
    this.brand();
    this.state = {currentNum: 0};
  }

  private scrollView: ScrollView;

  /**
   * 英文字母表
   */
  private englishList: string[] = [];

  /**
   * 每个条目的位置
   */
  private itemPageY = [];

  /**
   * 处理数据
   */
  private brandList: BrandVo[] = [];

  private numPageY = [];

  private itemHeight = 23;

  private onScroll = false;

  private textRefs: Map<string, Text> = new Map();

  private viewRefs: Map<string, Text> = new Map();

  private textInput: TextInput;

  private panResponder = PanResponder.create({
    onStartShouldSetPanResponderCapture: (evt, gestureState) => {
      return true;
    },
    onMoveShouldSetPanResponderCapture: (evt, gestureState) => {
      return true;
    },
    onStartShouldSetPanResponder: (evt, gestureState) => {
      return true;
    },

    onPanResponderStart: (e) => {
      this.onScroll = false;
      this.onPanResponder(e);
    },

    onPanResponderMove: (e) => {
      this.onPanResponder(e);
    },
  });

  onPanResponder(e) {
    let currentY = e.nativeEvent.pageY - 130;
    let length = this.numPageY.length - 1;
    if (currentY >= this.numPageY[length].end) {
      this.scrollView.scrollToEnd();
      this.modifyStyle(length);
      return;
    }
    if (currentY <= this.numPageY[0].start) {
      this.scrollView.scrollTo({y: 0});
      this.modifyStyle(0);
      return;
    }
    let index = this.numPageY.findIndex((item) => {
      return item.start <= currentY && item.end >= currentY;
    });
    if (index == 0) {
      this.scrollView.scrollTo({y: 0});
      this.modifyStyle(0);
      return;
    }

    this.modifyStyle(index);
    this.scrollView.scrollTo({y: this.itemPageY[index - 1].height});
  }

  private modifyStyle(current) {
    this.viewRefs.forEach((item, key) => {
      let style = {
        backgroundColor:
          key == this.englishList[current] ? '#FF5F0F' : '#F8F8FA',
      };
      if (item) {
        item.setNativeProps({style});
      }
    });
    this.textRefs.forEach((item, key) => {
      let style = {
        color: key == this.englishList[current] ? '#fff' : '#666',
      };
      if (item) {
        item.setNativeProps({style});
      }
    });
    this.textInput.setNativeProps({text: this.englishList[current]});
  }

  brand() {
    const {result} = Brands;
    result.map((item) => {
      let itemBrand = this.brandList.find((value) => {
        return item.brandInitial === value.brandInitial;
      });
      if (itemBrand) {
        itemBrand.brand.push(item);
      } else {
        this.englishList.push(item.brandInitial);
        this.brandList.push({brandInitial: item.brandInitial, brand: [item]});
      }
    });

    this.englishList.map((item, index) => {
      this.numPageY.push({
        index: index,
        start: this.itemHeight * index + 20,
        end: this.itemHeight * (index + 1) + 20,
      });
    });
  }

  onLayout(event, index) {
    this.itemPageY.push({
      height: event.nativeEvent.layout.height,
      index: index,
    });
    if (this.itemPageY.length == this.brandList.length) {
      this.itemPageY.sort(function (a, b) {
        return a.index - b.index;
      });
      this.itemPageY.flatMap((item, index) => {
        if (index != 0) {
          item.height = item.height + this.itemPageY[index - 1].height;
        }
      });
    }
  }

  renderItem(item: BrandVo, index) {
    let brandVo = item;
    return (
      <View
        onLayout={(event) => {
          this.onLayout(event, index);
        }}
        style={{marginHorizontal: 8}}>
        <Text style={style.item}>{item.brandInitial}</Text>
        <View style={style.flatList}>
          {item.brand.map((item) => {
            return (
              <TouchableOpacity
                activeOpacity={1}
                onPress={() => {
                  //点击跳转
                }}
                style={style.carItem}>
                <Image
                  source={{uri: item.brandLogo}}
                  style={style.icon}></Image>
                <Text style={style.carType}>{item.brandName}</Text>
              </TouchableOpacity>
            );
          })}
        </View>
      </View>
    );
  }

  numItem(item, index) {
    return (
      <View
        style={{
          height: this.itemHeight,
          width: 16,
          justifyContent: 'center',
        }}>
        <View
          ref={(ref) => {
            this.viewRefs.set(item, ref);
          }}
          style={[
            {
              backgroundColor: 0 == index ? '#ff0000' : '#f4f4f4',
            },
            style.numItem,
          ]}>
          <Text
            ref={(ref) => {
              this.textRefs.set(item, ref);
            }}
            style={{
              fontSize: 12,
              color: 0 == index ? '#fff' : '#666',
            }}>
            {item}
          </Text>
        </View>
      </View>
    );
  }

  render() {
    const {currentNum} = this.state;
    return (
      <View style={{backgroundColor: '#f2f4f8'}}>
        <Text
          style={{
            height: 40,
            textAlign: 'center',
            textAlignVertical: 'center',
            lineHeight: 40,
            marginTop: 40,
          }}>
          {'标题'}
        </Text>
        <View>
          <ScrollView
            ref={(ref) => {
              this.scrollView = ref;
            }}
            alwaysBounceVertical={false}
            scrollEventThrottle={100}
            overScrollMode={'never'}
            bounces={false}
            contentContainerStyle={{paddingBottom: 200}}
            onScrollBeginDrag={() => {
              this.onScroll = true;
            }}
            onScroll={(event) => {
              if (this.onScroll) {
                var contentSize = event.nativeEvent.contentSize.height;
                var contentOffset = event.nativeEvent.contentOffset.y;
                var layoutMeasurement =
                  event.nativeEvent.layoutMeasurement.height;
                if (contentOffset + layoutMeasurement + 5 >= contentSize) {
                  this.modifyStyle(this.itemPageY.length - 1);
                  return;
                }
                let index = this.itemPageY.findIndex((item, index) => {
                  return item.height > event.nativeEvent.contentOffset.y;
                });
                this.modifyStyle(index);
              }
            }}>
            {this.brandList.map((item, index) => {
              return this.renderItem(item, index);
            })}
          </ScrollView>
          <View style={style.topView}>
            <TextInput
              ref={(ref) => {
                this.textInput = ref;
              }}
              editable={false}
              style={style.inputItem}>
              {this.englishList[currentNum]}
            </TextInput>
          </View>
        </View>

        <View
          {...this.panResponder.panHandlers}
          hitSlop={{left: 10, right: 10}}
          style={style.english}>
          {this.englishList.map((item, index) => {
            return this.numItem(item, index);
          })}
        </View>
      </View>
    );
  }
}

const style = StyleSheet.create({
  english: {
    position: 'absolute',
    width: 24,
    borderRadius: 90,
    right: 20,
    top: 130,
    backgroundColor: '#f4f4f4',
    alignItems: 'center',
    paddingTop: 10,
  },
  item: {
    height: 40,
    textAlignVertical: 'center',
    lineHeight: 40,
    marginLeft: 10,
    color: '#222',
    fontSize: 16,
  },
  inputItem: {
    height: 40,
    marginLeft: 10,
    color: '#222',
    fontSize: 16,
  },
  flatList: {
    borderRadius: 10,
    backgroundColor: '#fff',
    overflow: 'hidden',
    paddingLeft: 14,
  },
  carItem: {
    flexDirection: 'row',
    height: 40,
    alignItems: 'center',
  },
  icon: {
    width: 24,
    height: 24,
  },
  carType: {
    fontSize: 14,
    color: '#666',
    marginLeft: 6,
  },
  numItem: {
    borderRadius: 90,
    width: 16,
    height: 16,
    justifyContent: 'center',
    alignItems: 'center',
  },
  topView: {
    position: 'absolute',
    backgroundColor: '#f2f4f8',
    height: 40,
    width: '100%',
    zIndex: 999,
    paddingLeft: 8,
  },
});
export default ChoosePage;