Сенсорные нажатия не срабатывают в контейнере с абсолютным позиционированием с zIndex (для раскрывающегося списка)

Я пытался настроить стили и порядок рендеринга элементов, чтобы Android и iOS вели себя одинаково. Провел много исследований, и текущие ответы, похоже, не решают проблему.

  • Товар: Я пишу кросс-платформенный раскрывающийся список
  • Проблема/Поведение: На iOS нажатия срабатывают корректно, а вот на андроиде отваливаются.
  • Пример: Вот codepen, показывающий, что он должен делатьПожалуйста, разветвите его, если вы хотите внести изменения в codepen.

Проблема этот точный код работает на iOS, но не на устройстве Android.

class Dropdown extends React.Component {
  constructor() {
    super()
    this.state = {
      open: false,
      selected: undefined,
    }
  }

  handleSelectPress = () => {
    this.setState({open: !this.state.open});
  }
  handleOptionPress = (item) => {
    this.setState({selected: item, open: !this.state.open});
    this.props.onSelectOption(item);
  }
  render () {
    const { selected = {}, open } = this.state
    const { placeholder = '', options = [] } = this.props

    return (
      <Select
        onPress = {this.handleSelectPress}
        value = {selected.name || placeholder}
        open = {open}
        options = {options}
        onOptionPress = {this.handleOptionPress} />
    )
  }
}

const shadowStyles = {
  ios: {
    shadowOpacity: 0.3,
    shadowRadius: 3,
    shadowOffset: {
        height: 1,
        width: 1,
    },
  },
  android: {
    elevation: 5,
  },
}

class Select extends React.PureComponent {
  render () {
    const { value, open, options, onOptionPress } = this.props
    const shadowStyle = shadowStyles[Platform.OS]
    return (
      <View style = {[shadowStyle, styles.wrap]}>
        <TouchableWithoutFeedback onPress = {this.props.onPress}>
          <View style = {styles.selectContainer}>
            <Text>{value}</Text>
          </View>
        </TouchableWithoutFeedback>
        {open && <View style = {styles.menu}>
          { options.map( (item, idx) => <Option value = {item} key = {idx} onPress = {onOptionPress} />)}
        </View>}
      </View>

    )
  }
}

class Option extends React.PureComponent {
  handlePress = () => {
    this.props.onPress(this.props.value)
  }
  render () {
    const { value } = this.props
    return (
      <TouchableOpacity onPress = {this.handlePress}>
        <View style = {styles.optionContainer}>
          <Text>{value.name}</Text>
        </View>
      </TouchableOpacity>
    )
  }
}

class App extends React.Component {
  render() {
    return (
      <View style = {styles.root}>
        <Dropdown
          placeholder = "--- SELECT ---"
          options = {
            [
              { name: "Press me!", id: "1" },
              { name: "No Me!", id: "2" },
              { name: "Cmon guys, here!", id: "3" },
            ]
          }
          onSelectOption = { (item) => console.info(`item pressed! ${item}`)}
        />
      </View>
    )
  }
}

const styles = StyleSheet.create({
    root: {
      width: 360,
      height: 640,
      backgroundColor: '#292c2e'
    },
    wrap: {
      position: 'relative',
      zIndex: 10,
      backgroundColor: 'rgb(73,75,77)'
    },
    selectContainer: {
      padding: 16,
    },
    menu: {
      backgroundColor: 'rgb(73,75,77)',
      position: 'absolute',
      top: '100%',
      left: 0,
      right: 0,
      maxHeight: 300
    },
    optionContainer: {
      padding: 16
    }

  });

Нативная версия React — 0.59.3

Несколько замечаний:

  • Я не ищу пакет, который уже делает это.
  • Я использую zIndex, чтобы элемент меню отображался поверх других элементов (отрисовывался после компонента раскрывающегося списка).
  • Я пробовал использовать TouchableHighlight, TouchableOpacity, TouchableWithoutFeedback и только для Android TouchableNativeFeedback
  • Я попытался изменить элемент «меню» между View и ScrollView (изначально была прокрутка)
  • Я поиграл с высотой и z-индексом в опции / touchables
  • Прикосновение к Select, которое переключает флаг open, работает правильно как на Android, так и на iOS, поэтому я почти уверен, что оно сузилось до родительского элемента с абсолютным позиционированием.
  • Я планирую добавить анимацию, отшлифовать стили... и т.д. (фактически используя стилизованные компоненты в проекте). Но сначала нужно, чтобы он работал :D

может быть, попробовать использовать один из других сенсорных вариантов? например <TouchableOpacity> вместо <TouchableWithoutFeedback>

Mike M 08.04.2019 20:25

Я пробовал все эти @MikeM на самом деле редактировал вопрос, чтобы включить эту деталь, когда вы опубликовали: D

John Ruddell 08.04.2019 20:26

Итак, обработчик событий не работает, но как насчет визуальной обратной связи при нажатии, вы ее видели?

hackape 11.04.2019 06:10

Нет, пресса не стреляет/не распознается.

John Ruddell 11.04.2019 06:14
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
10
4
6 820
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Проблема с вашими стилями в styles.menu, а не с zIndex.

Вы дали position:'absolute' и top:'100%'.

Это означает, что ваше дочернее представление позиционируется как абсолютное по отношению к родительскому представлению, и ваше дочернее представление запустится сразу после завершения родительского представления из-за top:"100%".

Заключение: ваши параметры рендерятся из родителя. да, они видны, потому что ниже нет других компонентов и тем более, что zIndex выше. и вы не можете трогать ничего, что не входит в диапазон родителя.

Решение:

Смотрите рабочую закуску здесь

в styles.menu измените их,

  menu: {
    backgroundColor: 'rgb(73,75,77)',
    zIndex: 1005,
    top: 0,
    left: 0,
    right: 0,
    maxHeight: 300,
  },

и вы также можете удалить zIndex: 1000 и 1005 :)

Спасибо за Ваш ответ! (zIndex был предназначен для тестирования, план состоит в том, чтобы указать только один для родителя). Совсем забыл о нажатиях вне контейнера для андроида! Тем не менее, все еще есть проблема. Пожалуйста, сошлитесь на мой запрос о награде «Хотел бы иметь нормальный элемент раскрывающегося списка (меню складывается поверх других элементов... и т.д.)». С вашим ответом, как он ведет себя как раскрывающийся список, если он не абсолютно позиционирован? Он будет поднимать содержимое, если вы отображаете элементы после раскрывающегося списка.

John Ruddell 11.04.2019 19:29
Ответ принят как подходящий

Вы должны установить позицию для Select вместо Option

вот закуска: https://snack.expo.io/HkDHHo6YV

измените стиль wrap и menu на приведенный ниже:

wrap: {
  position: 'absolute',
  zIndex: 10,
  width: "100%",
  backgroundColor: 'rgb(73,75,77)',
},
menu: {
  backgroundColor: 'rgb(73,75,77)',
  maxHeight: 300,
},

Изменить, рабочее решение

Недостающая часть должна была учитывать пространство под раскрывающимся списком, поскольку он был полностью позиционирован. Мне пришлось изменить компонент Select, чтобы отобразить элемент «заполнение» для учета пространства.

class Select extends React.PureComponent {
  render() {
    const { value, open, options, onOptionPress } = this.props;
    const shadowStyle = shadowStyles[Platform.OS];
    return (
      <>
        <View style = {{height: 60}}></View>
        <View style = {[shadowStyle, styles.wrap]}>
          <TouchableWithoutFeedback onPress = {this.props.onPress}>
            <View style = {styles.selectContainer}>
              <Text>{value}</Text>
            </View>
          </TouchableWithoutFeedback>
          {open && (
            <View style = {styles.menu}>
              {options.map((item, idx) => (
                <Option value = {item} key = {idx} onPress = {onOptionPress} />
              ))}
            </View>
          )}
        </View>
      </>
    );
  }
}

Затем отрегулируйте высоту styles.selectContainer, чтобы она была одинаковой высоты 60

selectContainer: {
  padding: 16,
  height: 60
},

Полный рабочий пример

import * as React from 'react';
import {
  Text,
  Button,
  View,
  StyleSheet,
  Platform,
  TouchableWithoutFeedback,
  TouchableOpacity,
} from 'react-native';

class Dropdown extends React.Component {
  constructor() {
    super();
    this.state = {
      open: false,
      selected: undefined,
    };
  }

  handleSelectPress = () => {
    this.setState({ open: !this.state.open });
  };
  handleOptionPress = item => {
    this.setState({ selected: item, open: !this.state.open });
    this.props.onSelectOption(item);
  };
  render() {
    const { selected = {}, open } = this.state;
    const { placeholder = '', options = [] } = this.props;

    return (
      <Select
        onPress = {this.handleSelectPress}
        value = {selected.name || placeholder}
        open = {open}
        options = {options}
        onOptionPress = {this.handleOptionPress}
      />
    );
  }
}

const shadowStyles = {
  ios: {
    shadowOpacity: 0.3,
    shadowRadius: 3,
    shadowOffset: {
      height: 1,
      width: 1,
    },
  },
  android: {
    elevation: 5,
  },
};

class Select extends React.PureComponent {
  render() {
    const { value, open, options, onOptionPress } = this.props;
    const shadowStyle = shadowStyles[Platform.OS];
    return (
      <>
        <View style = {{height: 60}}></View>
        <View style = {[shadowStyle, styles.wrap]}>
          <TouchableWithoutFeedback onPress = {this.props.onPress}>
            <View style = {styles.selectContainer}>
              <Text>{value}</Text>
            </View>
          </TouchableWithoutFeedback>
          {open && (
            <View style = {styles.menu}>
              {options.map((item, idx) => (
                <Option value = {item} key = {idx} onPress = {onOptionPress} />
              ))}
            </View>
          )}
        </View>
      </>
    );
  }
}

class Option extends React.PureComponent {
  handlePress = () => {
    this.props.onPress(this.props.value);
  };
  render() {
    const { value } = this.props;
    return (
      <TouchableOpacity onPress = {this.handlePress}>
        <View style = {styles.optionContainer}>
          <Text>{value.name}</Text>
        </View>
      </TouchableOpacity>
    );
  }
}

export default class App extends React.Component {
  render() {
    return (
      <View style = {styles.root}>
        <Dropdown
          placeholder = "--- SELECT ---"
          options = {[
            { name: 'Press me!', id: '1' },
            { name: 'No Me!', id: '2' },
            { name: 'Cmon guys, here!', id: '3' },
          ]}
          onSelectOption = {item => console.info(`item pressed! ${item}`)}
        />
        <Text>hellof</Text>
        <View style = {{backgroundColor: "red", height: 250}} />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  root: {
    width: 360,
    height: 640,
    backgroundColor: '#292c2e',
    marginTop: 100
  },
  wrap: {
    position: 'absolute',
    zIndex: 10,
    width: "100%",
    backgroundColor: 'rgb(73,75,77)',
  },
  selectContainer: {
    padding: 16,
    height: 60
  },
  menu: {
    backgroundColor: 'rgb(73,75,77)',
    maxHeight: 300,
  },
  optionContainer: {
    padding: 16,
    height: 60
  },
});

Спасибо за Ваш ответ! Все выглядит так, как будто это работает, я перенесу это в свое приложение и проверю очень быстро. Выглядит многообещающе!

John Ruddell 12.04.2019 05:44

Таким образом, глядя на стили (еще не запуская их в приложении), элементы попадут под раскрывающийся список, потому что они полностью позиционированы. Это не проблема, потому что я могу поместить элемент под компонентом Dropdown той же высоты. Я отредактирую ваш ответ с этими деталями, когда напишу его <3

John Ruddell 12.04.2019 06:09

Спасибо за вашу помощь, чтобы получить меня в нужное место! После ответа Джейдипа я, вероятно, понял это, но у меня не было времени поработать над этим. Обновил свой ответ рабочим примером!

John Ruddell 12.04.2019 07:37

Отлично, я только что обновил ссылку на закуску с примером, который вы привели.

Shashin Bhayani 12.04.2019 07:43

Был еще один вопрос, который я пытался выяснить. Думаю, это ошибка в том, как styled-components применяет стили. Похоже, что zIndex не устанавливался на компоненте в стиле Wrap. Очень странно, но я просто собираюсь использовать метод StyleSheet для этого элемента :) Еще раз спасибо, наградил вас $$ :D

John Ruddell 12.04.2019 08:27

@ShashinBhayani, а что, если мне нужно это место? Есть ли решение для этого случая? <View style = {{height: 60}}></View>

Artem Z. 16.12.2021 13:47

Другие вопросы по теме