Представление по центру в абсолютно позиционированном Animated.View внутри положения сдвига ScrollView

Код для отображения TabList:

import React, { Children, useEffect } from 'react';
import { LayoutChangeEvent, View } from 'react-native';
import {
  ScrollView,
  TouchableWithoutFeedback,
} from 'react-native-gesture-handler';
import Animated, {
  Easing,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';

import { isValidChild } from '@utils';

import { useTabIndex } from '../tab-context';

import { useStyle } from './tab-list.styles';
import { TabListProps } from './tab-list.type';

const animConfig = {
  duration: 200,
  easing: Easing.bezier(0.25, 0.1, 0.25, 1),
};

const TabList: React.FC<TabListProps> = props => {
  const styles = useStyle();
  const { children, onChange } = props;
  const selectedTabIndex = useTabIndex();
  const animatedTabIndicatorPosition = useSharedValue(0);

  // Save layout of the container
  const [containerLayout, setContainerLayout] = React.useState({
    x: 0,
    y: 0,
    width: 0,
    height: 0,
  });

  const onContainerLayout = (event: LayoutChangeEvent) => {
    const { x, y, width, height } = event.nativeEvent.layout;
    setContainerLayout({ x, y, width, height });
  };

  // get children length
  const childrenLength = Children.count(children);
  const tabWidth =
    childrenLength > 3
      ? containerLayout.width / 3
      : containerLayout.width / childrenLength;

  const renderChildren = () => {
    // Render only children of component type TabList
    return Children.map(children, child => {
      // Check if child is a valid React element and has type TabList
      if (isValidChild(child, 'Tab')) {
        return (
          <TouchableWithoutFeedback
            containerStyle = {{ width: tabWidth }}
            onPress = {() => onChange((child as JSX.Element)?.props.tabIndex)}
          >
            {child}
          </TouchableWithoutFeedback>
        );
      }

      // Throw error if child is not a TabList
      throw new Error('TabList component can only have children of type Tab');
    });
  };

  useEffect(() => {
    animatedTabIndicatorPosition.value = selectedTabIndex * tabWidth;
  }, [selectedTabIndex]);

  const indicatorAnimatedStyle = useAnimatedStyle(() => {
    return {
      width: tabWidth,
      transform: [
        {
          translateX: withTiming(
            animatedTabIndicatorPosition.value,
            animConfig,
          ),
        },
      ],
    };
  }, []);

  return (
    <View onLayout = {onContainerLayout} style = {styles.container}>
      <ScrollView
        horizontal
        showsHorizontalScrollIndicator = {false}
        testID = "TestID__component-TabList"
      >
        <Animated.View
          style = {[styles.indicatorContainer, indicatorAnimatedStyle]}
        >
          <View
            style = {[
              styles.indicator,
              {
                width: tabWidth - 4,
              },
            ]}
          />
        </Animated.View>
        {renderChildren()}
      </ScrollView>
    </View>
  );
};

export default TabList;

Стили для элементов компонента:

import { createUseStyle } from '@theme';

// createUseStyle basically returns (fn) => useStyle(fn)
export const useStyle = createUseStyle(theme => ({
  container: {
    position: 'relative',
    flexGrow: 1,
    backgroundColor: theme.palette.accents.color8,
    height: 32,
    borderRadius: theme.shape.borderRadius(4.5),
  },

  indicatorContainer: {
    position: 'absolute',
    height: 32,
    justifyContent: 'center',
    alignItems: 'center',
  },

  indicator: {
    height: 28,
    backgroundColor: theme.palette.background.main,
    borderRadius: theme.shape.borderRadius(4),
  },
}));

Я использую react-native-reanimated для анимации индикатора вкладки. Что я заметил, так это то, что при перезагрузке приложения начальная позиция индикатора вкладки продолжает меняться, как видно из прикрепленного мной GIF-файла. Иногда он расположен там, где должен быть, а иногда половина окна скрыта за контейнером прокрутки. Когда я удаляю alignItems: center из Animated.View, все работает, как и ожидалось.

Я недоумеваю, почему позиция постоянно меняется из-за align-items?

Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Введение в CSS
Введение в CSS
CSS является неотъемлемой частью трех основных составляющих front-end веб-разработки.
Как выровнять Div по центру?
Как выровнять Div по центру?
Чтобы выровнять элемент <div>по горизонтали и вертикали с помощью CSS, можно использовать комбинацию свойств и значений CSS. Вот несколько методов,...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
CSS: FlexBox
CSS: FlexBox
Ранее разработчики использовали макеты с помощью Position и Float. После появления flexbox сценарий полностью изменился.
0
0
90
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Редактировать: не обращайте внимания на приведенное ниже, поведение не связано с горячей перезагрузкой! Я оставлю это, если у кого-то еще есть такое же заблуждение


Горячая перезагрузка ненадежна с Reanimated — в собственных потоках есть значения, которые не будут обновляться. Это не влияет на окончательное приложение.

Чтобы проверить, действительно ли он работает, просто встряхните устройство/сим-карту и нажмите «Перезагрузить» после внесения изменений. Этого достаточно, чтобы очистить все залипшие значения. Если ваш компонент по-прежнему не делает то, что вы хотите, вы можете с уверенностью отредактировать его и убедиться, что он выглядит правильно.

Может быть, GIF создает впечатление, что приложение перезагружается в горячем режиме. Но я нажимаю «R», чтобы полностью перезагрузить.

shet_tayyy 25.11.2022 06:29

Это быстрая перезагрузка! Виноват

Abe 25.11.2022 09:02
Ответ принят как подходящий

Проблема заключалась в том, что дочерний компонент индикатора не был заключен в границы контейнера индикатора. Я решил эту проблему, добавив flexWrap: 'wrap' в контейнер родительского индикатора.

Итак, новый стиль выглядит так:

import { createUseStyle } from '@theme';

// createUseStyle basically returns (fn) => useStyle(fn)
export const useStyle = createUseStyle(theme => ({
  container: {
    position: 'relative',
    flexGrow: 1,
    backgroundColor: theme.palette.accents.color8,
    height: 32,
    borderRadius: theme.shape.borderRadius(4.5),
  },

  indicatorContainer: {
    position: 'absolute',
    height: 32,
    justifyContent: 'center',
    alignItems: 'center',
    flexWrap: 'wrap'
  },

  indicator: {
    height: 28,
    backgroundColor: theme.palette.background.main,
    borderRadius: theme.shape.borderRadius(4),
  },
}));

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