React native Skia animation filling a segment of a circle instead of rectangle

78 Views Asked by At

I'm trying to make this animation be a circle segment instead of a rectangle that gets filled where the degrees of the segment could be picked e.g 40 like:

image of example segment

This is what the animation currently looks like:

image of current animation

I've tried using <Group clip={...}> but I couldn't find a way to make it work without the whole circle segment jiggling.

The code comes from this video: https://www.youtube.com/watch?v=I6elFawLceY&t=372s

Thank you!

import React from "react";
import {
  Alert,
  Dimensions,
  SafeAreaView,
  View,
  StyleSheet,
  Text as RNText,
  TouchableOpacity,
} from "react-native";

import {
  Skia,
  Canvas,
  Path,
  vec,
  useComputedValue,
  useClockValue,
  useValue,
  useTouchHandler,
  LinearGradient,
  Text,
  useFont,
  rrect,rect,
} from "@shopify/react-native-skia";

import { line, curveBasis } from "d3";

const dimens = Dimensions.get("screen");
const width = 150;
const frequency = 2;
const initialAmplitude = 5;
const verticalShiftConst = 100;
const height = 600;
const horizontalShift = (dimens.width - width) / 2;
const indicatorArray = Array.from({ length: 11 }, (_, i) => i);

export const WaveMeter = () => {
  const verticalShift = useValue(verticalShiftConst);
  const amplitude = useValue(initialAmplitude);
  const clock = useClockValue();


  const touchHandler = useTouchHandler({
    onActive: ({ y }) => {
      if (y > verticalShiftConst) {
        verticalShift.current = Math.min(height, y);
        amplitude.current = Math.max(
          0,
          (height - verticalShift.current) * 0.025
        );
      }
    },
  });

  const createWavePath = (phase = 20) => {
    let points = Array.from({ length: width + horizontalShift }, (_, index) => {
      const angle =
        ((index - horizontalShift) / width) * (Math.PI * frequency) + phase;
      return [
        index,
        amplitude.current * Math.sin(angle) + verticalShift.current,
      ];
    });

    const shiftedPoints = points.slice(horizontalShift, 300);

    const lineGenerator = line().curve(curveBasis);
    const waveLine = lineGenerator(shiftedPoints);
    const bottomLine = `L${
      width + horizontalShift
    },${height} L${horizontalShift},${height}`;
    const extendedWavePath = `${waveLine} ${bottomLine} Z`;
    return extendedWavePath;
  };

  const animatedPath = useComputedValue(() => {
    const speedFactor = 0.5;
    const current = (clock.current / 225) % 225;
    const start = Skia.Path.MakeFromSVGString(createWavePath(current));
    const end = Skia.Path.MakeFromSVGString(createWavePath(Math.PI * current * speedFactor));
  
    return start.interpolate(end, 0.5);
  }, [clock, verticalShift]);
  

  const trianglePath = useComputedValue(() => {
    return [
      vec(horizontalShift * 2.6, verticalShift.current - 20),
      vec(horizontalShift * 2.6, verticalShift.current + 20),
      vec(horizontalShift * 2.3, verticalShift.current),
    ];
  }, [verticalShift]);


  const font = useFont( require('../../../../shared/assets/fonts/Hammersmith_One/HammersmithOne-Regular.ttf'))

  const gradientStart = useComputedValue(() => {
    return vec(0, verticalShift.current);
  }, [verticalShift]);

  const gradientEnd = useComputedValue(() => {
    return vec(0, verticalShift.current + 150);
  }, [verticalShift]);

  const getLabelYValueOffset = (position) => {
    return verticalShiftConst + 50 * position;
  };

  const getYLabelValue = (position) => {
    return `${(100 - position * 10)/10}`;
  };

  const alertValue = () => {
    const adjustedShift =
      (verticalShiftConst - verticalShift.current) /
        (height - verticalShiftConst) +
      1;

    Alert.alert("VALUE", `Your value is: ${Math.round(adjustedShift * 10)}`);
  };


  const size = 600
  const r = size / 2;
  const padding = size / 30;
  const innerCircleSize = size - padding * 2;

  const roundedRectangle = 
  rrect(
    rect(padding, padding, innerCircleSize, innerCircleSize),
    r/2,
    r,
  );


  return (
    <SafeAreaView style={styles.container}>
      <Canvas style={styles.canvas} onTouch={touchHandler}>


        <Path path={animatedPath} style="fill">
          <LinearGradient
            start={gradientStart}
            end={gradientEnd}
            colors={['cyan', 'blue']}
          />
        </Path>


      </Canvas>
      <TouchableOpacity style={styles.buttonContainer} onPress={alertValue}>
        <RNText style={styles.buttonText}>GET VALUE</RNText>
      </TouchableOpacity>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  canvas: {
    flex: 1,
  },
  buttonContainer: {
    height: 60,
    borderRadius: 8,
    backgroundColor: "#FF5349",
    marginHorizontal: 50,
    marginBottom: 20,
    justifyContent: "center",
    alignItems: "center",
  },
  buttonText: {
    color: "white",
    fontSize: 20,
    fontWeight: "bold",
  },
});
0

There are 0 best solutions below