Это был мой первый нативный проект React для девелоперской компании, и задача заключалась в том, чтобы выделить выбранный этаж здания на колесе выбора. (Кстати, вы можете проверить рабочий пример этого приложения (IOS, Android), пока язык только грузинский.) Вы также можете скачать этот пакет из NPM.

Но не было никакой надежды найти какой-либо пакет, поддерживающий реакцию, или помощь StackOverflow. Все настраиваемые средства выбора и собственные средства выбора IOS также выполняют обратный вызов только в конце прокрутки. Я всегда стараюсь писать собственный код и не использовать пакеты, но на этот раз я подумал, что это сложная задача, которая займет много времени. Часы и энергия, потраченные на поиски, сказали мне, что я должен все делать сам. К счастью, многие разработчики React Native искали аналогичную функциональность, и по их стопам в Google я нашел пакет react-native-swipe-picker, в котором FlatList или ScrollView использовались в качестве средства выбора, так что это был шанс исправить мою проблема.

Я добавил обратные вызовы прокрутки, исправил некоторые ошибки и улучшил функциональность, сделав ее более удобной для разработчиков.

Простой пример использования компонента DynamicallySelectedPicker

import React, {useState} from 'react';
import {StyleSheet, View, Text} from 'react-native';
import {Colors} from 'react-native/Libraries/NewAppScreen';
import DynamicallySelectedPicker from './src/components/DynamicallySelectedPicker';
const App = () => {
  const [selectedValue, setSelectedValue] = useState(0);
  return (
    <View style={styles.body}>
      <View style={{margin: 30}}>
        <Text>Item index {selectedValue}</Text>
      </View>
      <DynamicallySelectedPicker
        items={[
          {
            value: 1,
            label: 'Item 1',
          },
          {
            value: 2,
            label: 'Item 2',
          },
          {
            value: 3,
            label: 'Item 3',
          },
          {
            value: 4,
            label: 'Item 4',
          },
          {
            value: 5,
            label: 'Item 5',
          },
        ]}
        width={300}
        height={300}
        onScroll={(selected) => setSelectedValue(selected.index)}
      />
    </View>
  );
};
const styles = StyleSheet.create({
  body: {
    backgroundColor: Colors.white,
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
});
export default App;

Это пример React Native с одним большим компонентом (его можно разделить на небольшие функциональные компоненты). Чтобы запустить его с Expo, вам нужно изменить

react-native-linear-gradient в expo-linear-gradient

import React from 'react';
import PropTypes from 'prop-types';
import {StyleSheet, View, ScrollView, Platform, Text} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import PickerListItem from './PickerListItem';
export default class DynamicallySelectedPicker extends React.Component {
  constructor(props) {
    super(props);
    // set picker item height for android and ios
    const {height, transparentItemRows, initialSelectedIndex} = props;
    let itemHeight = height / (transparentItemRows * 2 + 1);
    // In ios we have to manually ceil items height to eliminate distortion in the visualization, when we have big data.
    if (Platform.OS === 'ios') {
      itemHeight = Math.ceil(itemHeight);
    }
    this.state = {
      itemHeight: itemHeight,
      itemIndex: initialSelectedIndex,
    };
  }
  /**
   * Generate fake items for picker top and bottom.
   * @param n
   * @returns {[]}
   */
  fakeItems(n = 3) {
    const itemsArr = [];
    for (let i = 0; i < n; i++) {
      itemsArr[i] = {
        value: -1,
        label: '',
      };
    }
    return itemsArr;
  }
  /**
   * Get extended picker items length.
   * @returns {number}
   */
  allItemsLength() {
    return this.extendedItems().length - this.props.transparentItemRows * 2;
  }
  /**
   *
   * @param event
   */
  onScroll(event) {
    const {items, onScroll} = this.props;
    const tempIndex = this.getItemTemporaryIndex(event);
    if (
      this.state.itemIndex !== tempIndex &&
      tempIndex >= 0 &&
      tempIndex < this.allItemsLength()
    ) {
      this.setItemIndex(tempIndex);
      onScroll({index: tempIndex, item: items[tempIndex]});
    }
  }
  /**
   *
   * @param event
   * @returns {number}
   */
  getItemTemporaryIndex(event) {
    return Math.round(
      event.nativeEvent.contentOffset.y / this.state.itemHeight,
    );
  }
  /**
   *
   * @param index
   */
  setItemIndex(index) {
    this.setState({
      itemIndex: index,
    });
  }
  /**
   * Add fake items to make picker almost like IOS native wheel picker.
   * @returns {*[]}
   */
  extendedItems() {
    const {transparentItemRows} = this.props;
    return [
      ...this.fakeItems(transparentItemRows),
      ...this.props.items,
      ...this.fakeItems(transparentItemRows),
    ];
  }
  /**
   *
   * @param item
   * @param index
   * @returns {*}
   */
  renderPickerListItem(item, index) {
    const {itemHeight} = this.state;
    const {allItemsColor, itemColor} = this.props;
    return (
      <View
        key={index}
        style={[
          styles.listItem,
          {
            height: itemHeight,
          },
        ]}>
        <Text
          style={{
            color: itemColor ? itemColor : allItemsColor,
          }}>
          {item.label}
        </Text>
      </View>
    );
  }
  render() {
    const {itemIndex, itemHeight} = this.state;
    const {
      width,
      height,
      topGradientColors,
      bottomGradientColors,
      selectedItemBorderColor,
      transparentItemRows,
    } = this.props;
    return (
      <View style={{height: height, width: width}}>
        <ScrollView
          showsVerticalScrollIndicator={false}
          showsHorizontalScrollIndicator={false}
          onScroll={(event) => {
            this.onScroll(event);
          }}
          scrollEventThrottle
          initialScrollIndex={itemIndex}
          snapToInterval={itemHeight}>
          {this.extendedItems().map((item, index) => {
            return this.renderPickerListItem(item, index);
          })}
        </ScrollView>
        <View
          style={[
            styles.gradientWrapper,
            {
              top: 0,
              borderBottomWidth: 1,
              borderBottomColor: selectedItemBorderColor,
            },
          ]}
          pointerEvents="none">
          <LinearGradient
            colors={topGradientColors}
            style={[
              styles.pickerGradient,
              {
                height: transparentItemRows * itemHeight,
              },
            ]}
          />
        </View>
        <View
          style={[
            styles.gradientWrapper,
            {
              bottom: 0,
              borderTopWidth: 1,
              borderTopColor: selectedItemBorderColor,
            },
          ]}
          pointerEvents="none">
          <LinearGradient
            colors={bottomGradientColors}
            style={[
              styles.pickerGradient,
              {height: transparentItemRows * itemHeight},
            ]}
          />
        </View>
      </View>
    );
  }
}
DynamicallySelectedPicker.defaultProps = {
  items: [{value: 0, label: 'No items', itemColor: 'red'}],
  onScroll: () => {},
  width: 300,
  height: 300,
  initialSelectedIndex: 0,
  transparentItemRows: 3,
  allItemsColor: '#000',
  selectedItemBorderColor: '#cecece',
  topGradientColors: [
    'rgba( 255, 255, 255, 1 )',
    'rgba( 255, 255, 255, 0.9 )',
    'rgba( 255, 255, 255, 0.7 )',
    'rgba( 255, 255, 255, 0.5 )',
  ],
  bottomGradientColors: [
    'rgba( 255, 255, 255, 0.5 )',
    'rgba( 255, 255, 255, 0.7 )',
    'rgba( 255, 255, 255, 0.9 )',
    'rgba( 255, 255, 255, 1 )',
  ],
};
DynamicallySelectedPicker.propTypes = {
  items: PropTypes.arrayOf(
    PropTypes.shape({
      value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
      label: PropTypes.string,
      itemColor: PropTypes.string,
    }),
  ),
  onScroll: PropTypes.func,
  initialSelectedIndex: PropTypes.number,
  height: PropTypes.number,
  width: PropTypes.number,
  allItemsColor: PropTypes.string,
  selectedItemBorderColor: PropTypes.string,
  topGradientColors: PropTypes.array,
  bottomGradientColors: PropTypes.array,
};
const styles = StyleSheet.create({
  listItem: {
    alignItems: 'center',
    justifyContent: 'center',
  },
  gradientWrapper: {
    position: 'absolute',
    width: '100%',
  },
  pickerGradient: {
    width: '100%',
  },
});

Я надеюсь, что кто-то воспользуется им и не будет тратить зря время, как я. Также, если у вас есть вопросы или замечания, пожалуйста, не стесняйтесь общаться.

Отчеты о вкладе или проблемах в репозитории GitHub (он немного другой, с большим количеством реквизитов и обратных вызовов.) Было бы здорово.

Еще раз извините за мой плохой английский и спасибо.