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

В этом уроке я расскажу вам о шагах, связанных с созданием кругового индикатора выполнения в вашем проекте React Native. Это обещает быть простым, легко интегрируемым и понятным. Я начну с создания нового проекта React Native и установки библиотек React Native Reanimated 2. Далее я определю структуру и внешний вид компонента кругового индикатора выполнения. Затем я углублюсь в реализацию индикатора выполнения с помощью React Native Reanimated 2, охватывая такие области, как анимация дуги выполнения и настройка внешнего вида компонента. Вы можете получить полный исходный код для этого проекта здесь.

Предпосылка

Это руководство потребует от вас понимания JavaScript и умения работать с React Native. Вы также должны были настроить свой компьютер, следуя инструкциям на странице Настройка среды React Native. Эта установка включает в себя установку и настройку эмулятора Android и iOS для тестирования вашего проекта React Native.

Подготовительные шаги

В этом проекте мы будем создавать круглый индикатор выполнения, который будет появляться и загружаться при нажатии кнопки, а через несколько секунд он будет отключен от экрана. Круги и галочки в этом проекте будут созданы с помощью библиотеки react-native-svg, а анимация будет реализована с помощью библиотеки react-native-reanimated, но перед этим нам потребуется запустить некоторые процессы. Эти шаги включают в себя:

  1. Создание нового проекта React Native: после установки React Native CLI вы можете создать новый проект React Native, выполнив следующую команду: npx react-native init projectName. Замените projectName названием вашего проекта.
  2. Установка react-native-reanimated 2 и react-native-svg: Чтобы использовать React Native Reanimated 2 и react-native-svg в своем проекте, вам необходимо установить их с помощью npm. Вы можете установить его, выполнив следующую команду в корневом каталоге вашего проекта: npm install react-native-reanimated react-native-svg. Чтобы завершить настройку, добавьте приведенный ниже код в файл babel.config.js.
module.exports = {
 //Add this
  plugins: ['react-native-reanimated/plugin']
  }

Запустите проект на эмуляторе Android, запустив npx react-native run-android.

3. Установка модулей (только для iOS). Если вы разрабатываете для iOS, вам необходимо установить необходимые модули с помощью CocoaPods. Вы можете сделать это, выполнив следующую команду в каталоге вашего проекта iOS: cd ios && pod install. Теперь вы должны запустить этот проект на своем эмуляторе iOS, используя команду npx react-native run-ios из корневого каталога вашего проекта.

Вы узнаете больше о начале работы с React Native здесь, но если вы правильно запустили процессы, ваши эмуляторы должны отображаться, как показано ниже:

Определение компонента индикатора выполнения «Круг»

Компонент Circle Progress Bar будет определен в файле app.js. Этот компонент будет состоять из внутреннего и внешнего круга и галочки. Сейчас мы создадим первый круг. Мы будем использовать модуль Dimensions React Native, чтобы получить ширину и высоту экрана и назначить их width и height соответственно.

После этого мы объявим и инициализируем постоянные переменные, чтобы определить форму и размер круга для индикатора выполнения. Это переменные Circle_Length и Radius. Circle_Length — длина окружности, а Radius — радиус окружности. Circle_Length присваивается значение 1000, которое представляет длину круга в пикселях. Radius рассчитывается как значение Circle_Length, деленное на 2, умноженное на число Пи ( 2 * Math.PI). Этот расчет используется для нахождения радиуса круга по его окружности. Код для реализации того, что было описано выше, выглядит следующим образом, но перед всем этим модули должны быть импортированы, как показано ниже:

import { Dimensions, View, TouchableOpacity } from "react-native";
import { Svg, Circle } from "react-native-svg";
import Animated, {
  useAnimatedProps,
  withSpring,
  useAnimatedStyle,
  useSharedValue,
  withDelay,
  withTiming,
} from "react-native-reanimated";

Добавьте следующее после операторов импорта вне компонента приложения.

const {width, height} = Dimensions.get('screen');
const Circle_Length = 1000;
const Radius = Circle_Length / (2 * Math.PI);

Далее мы определим два круга в компоненте SVG. Для каждого компонента круга:

cx: Эта опора определяет x-координату центра круга. В этом случае координата x устанавливается равной половине ширины экрана, что позволяет расположить центр круга горизонтально посередине экрана.

cy: Эта опора определяет координату y центра круга. В этом случае координата y устанавливается равной половине высоты экрана, что позволяет расположить центр круга вертикально посередине экрана.

r: Этот реквизит устанавливает радиус круга. В этом случае установлено значение Radius, которое рассчитывается как Circle_Length, деленное на 2, умноженное на число пи.

stroke: Этот реквизит устанавливает цвет обводки (контура) круга. В этом случае для двух кругов используются два разных цвета: «# 404258» и «# 82CD47».

fill: Этот реквизит устанавливает цвет для заполнения внутренней части круга. В этом случае один круг заполнен белым «#fff», а другой установлен в «прозрачный», чтобы фон был виден.

strokeWidth: Этот реквизит устанавливает ширину обводки круга. В этом случае один круг имеет ширину 35, а другой — 15.

strokeDasharray: Этот реквизит устанавливает длину тире штриха, превращая его в пунктирную линию. В данном случае установлено значение Circle_Length, чтобы черточка соответствовала всей окружности круга.

strokeLinecap: Этот реквизит устанавливает стиль концов тире. В этом случае для него установлено значение «круглый», чтобы придать концам тире закругленный вид. Код ниже:

const App = () => {
return (
  <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
    <Svg>
      <Circle
        cx={width / 2}
        cy={height / 2}
        r={Radius}
        stroke="#404258"
        fill="#fff"
        strokeWidth={35}
      />
      <Circle
        cx={width / 2}
        cy={height / 2}
        r={Radius}
        stroke="#82CD47"
        strokeWidth={15}
        fill="transparent"
        strokeDasharray={Circle_Length}
        strokeLinecap="round"
      />
    </Svg>
  </View>
);
};

К концу этой реализации ваши эмуляторы должны выглядеть так:

Наконец, прежде чем анимировать некоторые компоненты, мы должны создать галочку, и это будет сделано с помощью модуля Path из библиотеки SVG. Чтобы разместить галочку на круглом светло-зеленом фоне, мы создадим представление, применим к нему сплошной цвет и установим радиус границы 150, чтобы сделать его круглым. Затем следующим компонентом SVG будет путь, который мы будем использовать для создания галочки. Код для реализации, как описано ниже. Вставьте этот код после предыдущего блока кода:

<View
    style={{
      height: 250,
      position: "absolute",
      width: 250,
      bottom: 210,
      backgroundColor: "#54B435",
      borderRadius: 150,
    }}
  />
  <Svg
    viewBox="0 0 40 40"
    style={{
      height: 110,
      position: "absolute",
      width: 110,
      bottom: 280,
      backgroundColor: "transparent",
      transform: [{ scale: 2 }],
    }}
  >
    <Path
      d="M12.5 20l5 5 9-9"
      stroke={"#fff"}
      strokeWidth={3}
      fill="#54B435"
      strokeLinecap="round"
    />
  </Svg>

Если вы следовали соответствующим образом, ваши эмуляторы должны показывать что-то похожее на экраны ниже:

При внимательном рассмотрении вы заметите, что к последнему компоненту представления и компоненту SVG добавлен дополнительный стиль bottom. Причина этого в том, что внутренняя галочка и ее фон имеют абсолютную позицию и не отображаются должным образом на Android, но с помощью верхнего, правого, левого или нижнего стиля мы можем заставить их отображаться так, как они предполагаемый. В то время как iOS содержит и отображает правильно, это не то же самое для Android. Из-за этой проблемы, если вы работаете на iOS, у вас будут некоторые искажения, но удалив стиль, bottom исправит это для вас, и вы должны получить тот же результат, как показано.

Создание и интеграция анимации в компоненты

Компоненты для этого проекта все на месте, но без анимации нет живости. Прежде всего, внешний круг должен отображаться так, как будто он загружается. Чтобы это было реализовано, нам нужно понять некоторые концепции круга. Этими понятиями являются strokeDashArray и strokeDashOffset.

StrokeDashArray — это способ определить шаблон штрихов или пробелов в штрихе. Это может быть маленький размер или большой патрон. Мы установили StrokeDashArray для внешнего круга, который мы анимируем, равным длине круга. Это потому, что мы хотим, чтобы внешний круг состоял из одной черточки. Это облегчает нам его анимацию, потому что мы можем, например, анимировать его от 0 до 1, и весь процесс может быть анимацией. Теперь вторая концепция — это strokeDashOffset. Это свойство используется для определения части фигуры, которая рисуется изначально и может быть анимирована с течением времени, что сделает всю фигуру нарисованной.

Чтобы установить это на место, мы создадим значение прогресса, используя ловушку useSharedValue, затем, используя ловушку u seAnimatedProps, мы создадим набор анимированных свойств. Константа Circle_Length — это длина окружности, а progressCircle.value — это общее значение, которое содержит текущий ход анимации. Произведение Circle_Length и progressCircle.value используется для вычисления значения strokeDashoffset, которое определяет, какая часть штриха контура должна быть смещена, а не отрисована.

Наконец, чтобы эти эффекты были видны, мы будем использовать ловушку useEffect для определения анимации таким образом, чтобы progressCircle.value постепенно менялось от 0 до 1 в течение 2 секунд. Код для реализации всего этого приведен ниже, но прежде чем это можно будет применить к компоненту, нам нужно преобразовать его из обычного компонента в анимированный компонент.

Чтобы преобразовать компонент круга, вставьте приведенный ниже код перед компонентом приложения. Этот код создает новый компонент с именем AnimatedCircle, который является анимированной версией компонента Circle.

const AnimatedCircle = Animated.createAnimatedComponent(Circle);

Теперь обновите второй компонент круга до AnimatedCircle вместо просто Circle. Затем мы объявим константу с именем progressCircle и animationProps, прикрепим анимацию Props в качестве значения к пропсуanimatedProps в компоненте AnimatedCircle и определим анимацию в ловушке useEffect.

const progressCircle = useSharedValue(1);
const animatedProps= useAnimatedProps(() => ({
  strokeDashoffset: Circle_Length * progressCircle.value,
}))

React.useEffect(() => {
  progressCircle.value = withTiming(0, {duration: 2000})
}, [])

Обновите компонент AnimatedCircle соответствующим образом.

...
<AnimatedCircle
  cx={width / 2}
  cy={height / 2}
  r={Radius}
  stroke="#82CD47"
  strokeWidth={15}
  fill="transparent"
  strokeDasharray={Circle_Len}
  animatedProps={animatedProps}
  strokeLinecap="round"
/>;
...

Если вы следовали инструкциям, показанным и описанным выше, на ваших эмуляторах должно отображаться что-то вроде этого:

Воспроизведение сеанса для разработчиков

Раскройте разочарования, выявите ошибки и устраните замедления работы, как никогда раньше, с помощью OpenReplay — набора для воспроизведения сеансов с открытым исходным кодом для разработчиков. Его можно разместить самостоятельно за несколько минут, что дает вам полный контроль над данными ваших клиентов.

Удачной отладки!

Исправление клеща

Планируется, что галочка лопнет, когда ход круга округляется. Он будет иметь сплошной цвет фона, который будет масштабироваться и всплывать, пока его непрозрачность анимируется от нуля до 1.

Первая область, над которой нужно поработать, — создать общее значение для фона галочки, масштаба галочки и непрозрачности галочки. Код:

const tickScale = useSharedValue(1);
const bckgrd = useSharedValue(0);
const tickOpacity = useSharedValue(0);

Далее мы преобразуем компонент Path в компонент AnimatedPath. Код:

const AnimatedPath = Animated.createAnimatedComponent(Path);

Затем с помощью хука useAnimatedStyle мы объявим два анимированных стиля, которые мы прикрепим к фону галочки и пути соответственно.

const bckgrdAnimatedStyle = useAnimatedStyle(() => {
  return {
    opacity: bckgrdOpacity.value,
    transform: [{scale: tickScale.value}]
  }
})


const tickAnimatedStyle = useAnimatedStyle(() => {
  return {
    opacity: tickOpacity.value,
  }
});

Затем мы обновим компоненты до Animated.View для фона тика и AnimatedPath для компонента пути, после чего объявленные bckgrdAnimatedStyle и tickAnimatedStyle будут прикреплены к соответствующим компонентам. Код:

<Animated.View 
 style={[{
    ...
    },
    bckgrdAnimatedStyle,
    ]}/>
    <Svg viewBox='0 0 40 40' 
     style={{
     ...
     transform: [{scale: 2}] 
      }}>
    <AnimatedPath
     ...
     style={tickAnimatedStyle}
     strokeLinecap="round"
     />
</Svg>

Теперь обновите ловушку useEffect кодом ниже:

...
tickScale.value = withDelay(1000, withSpring(80));
  bckgrdOpacity.value = withDelay(1000, withTiming(1));
  tickOpacity.value = withDelay(1500, withTiming(1, {duration: 500}) );
  ...

Если вы следовали, как показано, ваши эмуляторы должны отображать что-то вроде этого:

Добавление кнопки

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

Для этого воспользуемся хуком React useState для создания состояния загрузки и функцией setLoading. По умолчанию это состояние будет ложным, но как только кнопка будет нажата, оно станет истинным, что откроет нашу анимацию, а через 3 секунды состояние загрузки будет установлено на ложное, и анимация будет размонтирована.

Добавьте приведенный ниже код в компонент приложения вверху:

const [loading, setLoading] = React.useState(false);

Чтобы создать кнопку, используйте код ниже:

<TouchableOpacity
  onPress={() => mountAnimation()}
  style={{
    bottom: 80,
    position: "absolute",
    width: width * 0.7,
    height: 60,
    backgroundColor: "#54B435",
    borderRadius: 15,
    alignItems: "center",
    justifyContent: "center",
  }}
>
  <Text style={{ fontSize: 20, color: "white", textTransform: "uppercase" }}>
    Show Animation
  </Text>
</TouchableOpacity>;

Так как мы используем кнопку, очистим хук useEffect и добавим код ниже:

const mountAnimation = () => {
  progressCircle.value = withTiming(0, { duration: 2000 });
  tickScale.value = withDelay(1000, withSpring(80));
  bckgrdOpacity.value = withDelay(1000, withTiming(1));
  tickOpacity.value = withDelay(1500, withTiming(1, { duration: 500 }));
  setLoading(true);
  setTimeout(() => {
    setLoading(false);
    progressCircle.value = withTiming(1, { duration: 500 });
    tickScale.value = withDelay(500, withSpring(0));
    bckgrdOpacity.value = withDelay(500, withTiming(0));
    tickOpacity.value = withDelay(500, withTiming(0, { duration: 500 }));
  }, 3000);
};

Эта функция устанавливает для состояния загрузки значение true, а через 3 секунды — значение false и сбрасывает все значения по умолчанию для анимации.

Затем, используя условный рендеринг, мы организуем компоненты следующим образом:

return (
  <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
    {loading ? <>...</> : null}
    <TouchableOpacity
      onPress={() => mountAnimation()}
      style={{
        bottom: 80,
        position: "absolute",
        width: width * 0.7,
        height: 60,
        backgroundColor: "#54B435",
        borderRadius: 15,
        alignItems: "center",
        justifyContent: "center",
      }}
    >
      <Text
        style={{ fontSize: 20, color: "white", textTransform: "uppercase" }}
      >
        Show Animation
      </Text>
    </TouchableOpacity>
  </View>
);

Вот что должны показать вам ваши эмуляторы Android и iOS, если вы их выполнили.

Заключение

Наконец, создание пользовательской круговой анимации в React Native может быть интересной и сложной задачей. Хотя вы можете просто установить модуль для реализации этого, вы должны знать, как создать его для себя и никогда не зависеть от какого-либо модуля для него, и я уверен, что, следуя шагам, описанным в этой статье, вы сможете создать собственный круговой цикл. анимация, которая добавит уникальности вашему приложению и улучшит общий пользовательский опыт. Немного творчества — и возможности безграничны, так что вперед и воплощайте свои идеи в жизнь!

Первоначально опубликовано на https://blog.openreplay.com.