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

Вполне естественно, что веб-разработчики, обращаясь к React Native, будут искать тот же набор инструментов при попытке реализовать безумные идеи своих дизайнеров, которые наверняка придут в форме SVG.

React Native поставляется со встроенной SVG-подобной библиотекой под названием ART. Что он делает, так это в основном сопоставляет компоненты React с собственными классами в iOS и Android, которые рисуют на экране с помощью компонентов платформы (Core Graphics в iOS и android.graphics в Android). Однако я хотел бы порекомендовать использовать отличный пакет под названием react-native-svg, который доступен как в Expo, так и в react-native-community. Преимущества использования response-native-svg:

  1. Написание SVG точно так же, как для Интернета (тот же синтаксис)
  2. Поддержка сенсорных событий для каждого элемента SVG (Компонент)

Итак, без дальнейших подробностей, давайте погрузимся в то, как реализовать красивую круговую диаграмму, и в конечном итоге мы также будем анимировать ее с помощью библиотеки React Native Animated. Вот как будет выглядеть конечный результат:

App.js

import SVG, {G} from 'react-native-svg';
...
import Slice from "./Slice";
const AnimatedSlice = Animated.createAnimatedComponent(Slice);
const demoData = [
    {
        number: 60,
        color: '#0d2f51'
    },
    {
        number: 20,
        color: '#28BD8B'
    },
    {
        number: 20,
        color: '#F66A6A'
    }
];
export default class App extends Component<Props> {

    constructor(props) {
        super(props);
        this.state = {
            animValue: new Animated.Value(0.1),
        };

    }

    resetPie = ()=>{
        this.state.animValue.setValue(0.1);
    };

    animate = ()=>{

        Animated.timing(
            this.state.animValue,
            {
                toValue: 2,
                duration: 500,
                easing: Easing.inOut(Easing.quad)
            }
        ).start(()=>{
            setTimeout(this.resetPie, 2000);
        });
    };

    render() {
        let endAngle = Animated.multiply(this.state.animValue, Math.PI);
        return (
            <View style={styles.container}>
                <Svg
                    width={200}
                    style={styles.pieSVG}
                    height={200}
                    viewBox={`-100 -100 200 200`}
                >
                    <G>
                        {
                            demoData.map( (item, index) =>{
                                return (
                                    <AnimatedSlice
                                        index={index}
                                        endAngle={endAngle}
                                        color={item.color}
                                        data={demoData}
                                        key={'pie_shape_' + index}
                                    />
                                )
                            })
                        }
                    </G>
                </Svg>
                <View style={{marginTop: 20}}>
                    <Button onPress={this.animate}/>
                </View>

            </View>
        );
    }
}

В App.js мы берем образцы данных (demoData) и рисуем их на круговой диаграмме. Для этого мы используем несколько компонентов react-native-svg (SVG, G и Path). Круговая диаграмма будет построена из элементов контура (элемент контура для каждого среза круговой диаграммы). Мы оборачиваем компонент ‹Path› в настраиваемый элемент под названием Slice и создаем из него анимированный компонент с помощью createAnimatedComponent.

Вот код:

Slice.js

import React, {Component} from 'react';
import {Path} from 'react-native-svg';
import * as shape from 'd3-shape';
const d3 = {shape};

export default class Slice extends Component {
    constructor(props) {
        super(props);
        this.state = {};
        this.arcGenerator = d3.shape.arc()
            .outerRadius(100)
            .padAngle(0)
            .innerRadius(0);
    }

    createPieArc = (index, endAngle, data) => {

        const arcs = d3.shape.pie()
            .value((item)=>item.number)
            .startAngle(0)
            .endAngle(endAngle)
            (data);

        let arcData = arcs[index];

        return this.arcGenerator(arcData);
    };


    render() {

        const {
            endAngle,
            color,
            index,
            data
        } = this.props;
        let val = data[index].number;

        return (
            <Path
                onPress={()=>alert('value is: '+val)}
                d={this.createPieArc(index, endAngle, data)}
                fill={color}
            />
        )

    }
}

Вы можете видеть, что для вычисления среза пирога мы используем библиотеку d3, которая с версии V4 стала использоваться для React Native и других небраузерных сред (например, новый модуль d3-shape).

Анимация пирога будет сделана простым рисованием его от угла 0 до угла 360 красивым анимированным способом. Теперь мы хотели бы как можно больше использовать библиотеку Animated React Native, поскольку это официальный и задокументированный способ создания анимации в RN.

В идеале мы бы просто использовали элемент ‹Path› и снабдили его свойством d, указывающим на Animated.Value. Однако это не сработает, поскольку способ реализации Path.js в react-native-svg делает невозможным анализ значений из Animated.Value в примитив.

Вот почему мы обернули компонент ‹Path› нашим компонентом ‹Slice›, и, создав из него анимированный компонент, теперь мы можем передавать анимированное значение.

Используя эту технику, можно легко оживить любую форму, воплощая в жизнь самые безумные идеи дизайнеров!