react-native可自定义联级选择器

百里嘉泽
2023-12-01
    • 组件代码
import React, {useState,useEffect,useRef,useCallback,Component,} from 'react';
import {View,Text,Dimensions,StyleSheet,TouchableOpacity,Image,FlatList,ScrollView,} from 'react-native';
import {scale, sc} from 'react-native-size-matters';
import { isIOSDevice } from '../utils/DimensionUtil';
const {width, height} = Dimensions.get('window');
let screenWidth = width;
let itemHeight = sc(42); // item高度
let distanceItem = 2; //选中高亮高度调节,距离顶部item个高度
// let deviceName = global?.AppInfo?.deviceName  == 'HUAWEI' || global?.AppInfo?.deviceName == 'HONOR' || global?.AppInfo?.deviceName == 'OPPO' ? true : false;
let deviceName = global?.AppInfo?.deviceName  == 'Xiaomi' || global?.AppInfo?.deviceName  == 'Redmi' ? true : false;
export default class ScrollPicker extends Component {
  /**
   *
 1. @param {*} pickData 二维数组,每一个子数组为一联[[{a:1},{a:2}],[{a:11},{a:12}],...arr]
 2. @param {*} selectedValue 已选下标组合值,index为0时为第一列,index为1时为第二列...,如[2,1]则表示默认选中第一列的第3个值,第二列的第2个值
 3. @param {*} onChange(data, index, scrollIndex, selectedValue) 回调函数: [具体值] | 第n列下标 | 当前选择的行数下标 | [第(当前索引下标)列选中的下标,第(当前索引下标)列选中的下标,...]
 4. @param {*} confirm(data, index, scrollIndex, selectedValue) 点击确认按钮  回调同上 通过ref触发
 5.  6. iot扩展:
 7. @param dataType (1-血压   2-心率   3-体重   4-睡眠    5-血糖)
 8. @param allPickerDate n年对应的n个月数据 [{"key":"2022年","data":[{"a":"1月"},{"a":"2月"}],"date":["2022-01","2022-02"]},{"key":"2021年","data":[{"a":"12月"}],"date":["2021-12"]}]
   */

  constructor(props) {
    super(props);
    this.state = {
      bottomHeight: sc(350), // 底部滚动区域高度
      selectedValue: props.selectedValue || [0,0],
      pickData: props.pickData || [],
      allData: {}
      // selectedValue: [2,1],
      // pickData: [[{a: 1},{a: 2},{a: 3},{a: 4},{a: 5}],[{a: 11},{a: 12},{a: 13},{a: 14},{a: 15}]]
    };
  }

  componentDidMount = () => {
    // deviceName = global?.AppInfo?.deviceName
    // deviceName = global?.AppInfo?.deviceName  == 'HUAWEI' || global?.AppInfo?.deviceName == 'HONOR' || global?.AppInfo?.deviceName == 'OPPO' ? true : false;
    deviceName = global?.AppInfo?.deviceName  == 'Xiaomi' || global?.AppInfo?.deviceName  == 'Redmi' ? true : false;
    console.log('deviceName', global?.AppInfo?.deviceName);
    //   console.log('111111111111+++++++++++++=====',this.props.pickData)
  };
  onMomentumScrollBegin = ({nativeEvent}) => {
    // console.log("onMomentumScrollBegin", nativeEvent);
  };

  onMomentumScrollEnd = (index, {nativeEvent}) => {
    let {selectedValue} = this.state;
    let unitHeight = itemHeight;
    let {contentOffset} = nativeEvent;
    let offsetY = contentOffset.y;
    let scrollIndex = Math.round(offsetY / unitHeight);
    selectedValue[index] = scrollIndex;
    let state = true;
    // this.setState({selectedValue});
    this[`refs${index}`]?.scrollTo({
      x: 0,
      y: unitHeight * scrollIndex,
      animated: true,
    });
    let data = []
    this.state.pickData.map((item, i) => {
      let svi = selectedValue[i] <= 0 ? 0 : selectedValue[i]
      if(item?.[svi]?.a){
        data.push(item?.[svi]?.a)
      }
      else{
        //iot 年、月日期动态高亮(若滚动列没有对应下一列的高亮,则置为高亮最后一项。)
        if(!this.state.pickData?.[i]){
          state = false;
        }
        else{
          if(i == 2 || i == 1){
            data.push(item?.[this.state.pickData[i].length-1]?.a);
            selectedValue[i] = this.state.pickData[i].length-1;
          }
        }
      }
    });

    // 解决ios年滚动超出最后一项,报错问题。
    if(!state) return;
    if(this.props?.dataType && data?.length == 1 && data?.[0].indexOf('月') != -1){
      data.unshift(this.state.pickData[0][this.state.pickData[0].length-1]?.a);
      // selectedValue = [0,this.state.pickData[1].length-1];
      // console.log('-----------data1111',data,selectedValue)
    };

    let allData = {data, index, scrollIndex, selectedValue}
    this.setState({allData})
    this.setState({selectedValue});

    let pickArr = this.state.pickData;
    // 动态渲染数据 index滚动列(0年份、1月份、2日)
    if(this.props?.dataType){
       /**
        * 年、月
        * 动态渲染年份对应的月份(例:n年对应1、2、3月的数据,n+n年对应9、10、11、12月的数据)
       */
      // if(this.props?.dataType != 2 && index == 0){
      if(index == 0){
        // console.log('______________________________',JSON.stringify(this.props.allPickerDate))
        let month = [];
        this.props.allPickerDate.map((item,index)=>{
          if(item.key == data[0]){
            month = item.data;
          }
        });
        this.setState({
          pickData: [pickArr[0],month]
        })
        this.props?.onChange({data, index, scrollIndex, selectedValue},[pickArr[0],month]);
        return;
      };

      /**
       * 年、月、日
       */
      // if(this.props?.dataType == 2){
      //   // 若当前滚动的为日,则不需要更改数据
      //   if(index == 2){
      //   this.props?.onChange({data, index, scrollIndex, selectedValue},null);
      //   // this.props?.onChange({data, index, scrollIndex, selectedValue},pickArr);
      //     return;
      //   };
      //   // 若当前滚动的为年、月,则需要动态更改对应的日期数据。(不同年份不同月份日期数据不同)
      //   let month = data[1];
      //   if(month.length <3){
      //     month = '0' + month
      //   };
      //   let dayArr = [];
      //   let lastDay = new Date(
      //     data[0].slice(0,4),
      //     month.slice(0,2),
      //     0,
      //   ).getDate();
      //   for(var i=1;i<=lastDay; i ++){
      //     dayArr.push({a: `${i}日`})
      //   };
      //   this.setState({
      //     pickData: [pickArr[0],pickArr[1],dayArr]
      //   });
      //   this.props?.onChange({data, index, scrollIndex, selectedValue},[pickArr[0],pickArr[1],dayArr]);
      //   return;
      // }

      // dataType != 2 && index != 0  若当前滚动的为月,则不需要更改数据
      this.props?.onChange({data, index, scrollIndex, selectedValue},null);
      // this.props?.onChange({data, index, scrollIndex, selectedValue},[pickArr[0],pickArr[1]]);
      return;
    }
    else{
      this.props.onChange &&
      this.props.onChange({data, index, scrollIndex, selectedValue}); //回调函数: [具体值] | 第n列下标 | 当前选择的行数下标 | [第(当前索引下标)列选中的下标,第(当前索引下标)列选中的下标,...]
    }
  };

  onLayout = () => {
    let unitHeight = itemHeight;
    let {selectedValue} = this.state;
    selectedValue?.forEach((item, index) => {
      this[`refs${index}`]?.scrollTo({
        x: 0,
        y: unitHeight * item,
        animated: true,
      });
    });
  };

  confirm = () => { //点击确认按钮
    let {selectedValue} = this.state;
    if(JSON.stringify(this.state.allData) === '{}'){ //没有选择任何值,无任何滑动 直接点确定
      let data = []
      this.state.pickData.map((item, i) => {
          data.push(item[selectedValue[i]].a)
      })
      this.props.confirm &&
      this.props.confirm({data, selectedValue: selectedValue})
    }else{
      this.props.confirm &&
      this.props.confirm({...this.state.allData})
    }
  }

  renderSelectColumn = (list, index) => {
    let width = screenWidth / this.state.pickData.length - sc(30);
    return (
      <View style={{height: '100%', width: width, alignItems: 'center'}}>
        <ScrollView
          ref={node => {
            this[`refs${index}`] = node;
          }}
          bounces={false}
          onLayout={this.onLayout}
          alwaysBounceHorizontal={false}
          showsVerticalScrollIndicator={false}
          onMomentumScrollBegin={this.onMomentumScrollBegin}
          onScrollEndDrag={isIOSDevice ? this.onMomentumScrollEnd.bind(this, index):null} // ios用此方法监听
          onMomentumScrollEnd={isIOSDevice ? null : this.onMomentumScrollEnd.bind(this, index)} // android用此方法监听
        >
          <View
            style={{
              height: itemHeight*distanceItem, // 选中高亮高度调节,*n为距离顶部n个item高度
              width: width,
              // width: sc(Dimensions.get("screen").width/this.state.pickData.length),
              justifyContent: 'center',
              alignItems: 'center',
            }}></View>
          {list?.length ?
            list?.map((item, index1) => {
              return (
                <View
                  key={index1}
                  style={{
                    height: itemHeight,
                    // width: sc(Dimensions.get("screen").width/this.state.pickData.length),
                    justifyContent: 'center',
                    alignItems: 'center',
                  }}>
                  <Text
                    style={[
                      {fontSize: sc(17), color: '#AFAFAF',},
                      this.state.selectedValue?.[index] == index1
                      // paddingHorizontal: sc(20),
                        ? {color: this.props?.fontColor || '#24B39B', fontWeight: 'bold',}
                        : null,
                    ]}>
                    {item.a}
                  </Text>
                </View>
              );
            }):null}
          <View
            style={{
              height: isIOSDevice ? sc(this.state.bottomHeight || 0) - (itemHeight*4-sc(40)) : sc(this.state.bottomHeight || 0) - (itemHeight*(deviceName ? 3.8 : 3.2)-sc(20)), // 调节底部最后一个选项顶起起的高度 米系与其他安卓机有点差距
            //   width: sc(100),
            //   height: itemHeight*5+sc(25),
              justifyContent: 'center',
              alignItems: 'center',
            //   backgroundColor: 'red',
            }}></View>
        </ScrollView>
      </View>
    );
  };

  render() {
    return (
      <React.Fragment>
        <View style={{ position: "absolute", top: itemHeight*(distanceItem+1)+sc(15), width, height: sc(.5), backgroundColor: "#EEEEEE", zIndex: 999 }}></View>
        <View style={{ position: "absolute", top: itemHeight*(distanceItem+2)+sc(15), width, height: sc(.5), backgroundColor: "#EEEEEE", zIndex: 999 }}></View>
        {/* <View style={{ position: "absolute", top: itemHeight*3+sc(15), width, height: StyleSheet.hairlineWidth, backgroundColor: "#EEEEEE", zIndex: 999 }}></View> */}
        <View
          onLayout={e => {
            e.nativeEvent.layout.height != this.state.bottomHeight &&
              this.setState({bottomHeight: e.nativeEvent.layout.height});
          }}
          style={{
            width: screenWidth,
            // height: sc(150),
            flexDirection: 'row',
            alignItems: 'center',
            justifyContent: 'center',
            overflow: 'hidden',
          }}>
          {this.state.pickData?.map((item, index) => {
            return <View key={index}>{this.renderSelectColumn(item, index)}</View>
          })}
        </View>
      </React.Fragment>
    );
  }
}
  1. 父组件代码
let date = [
    [
        {
            "a": "00时"
        },
        {
            "a": "01时"
        },
        {
            "a": "02时"
        },
        {
            "a": "03时"
        },
        {
            "a": "04时"
        },
        {
            "a": "05时"
        },
        {
            "a": "06时"
        },
        {
            "a": "07时"
        },
        {
            "a": "08时"
        },
        {
            "a": "09时"
        },
        {
            "a": "10时"
        },
        {
            "a": "11时"
        },
        {
            "a": "12时"
        },
        {
            "a": "13时"
        },
        {
            "a": "14时"
        },
        {
            "a": "15时"
        },
        {
            "a": "16时"
        },
        {
            "a": "17时"
        },
        {
            "a": "18时"
        },
        {
            "a": "19时"
        },
        {
            "a": "20时"
        },
        {
            "a": "21时"
        },
        {
            "a": "22时"
        },
        {
            "a": "23时"
        }
    ],
    [
        {
            "a": "00分"
        },
        {
            "a": "30分"
        }
    ]
];

scrollPicker.current?.confirm(); // 触发confirm

//html部分
<ScrollPicker
 ref={scrollPicker}
 selectedValue={[ 9, 0]} // 默认选中: 9点
 pickData={date}
   confirm={data => {
    console.log('confirm==', data);
   }}
/>
 类似资料: