skip to Main Content

I’ve been digging into the react native world. Totally new to this area.

And was trying to create a button component. Where the button is kind of circular, and have a specific color, title that will be send to it by parameters.

The complex thing I’m trying to do is trying to achieve a type of inside linear effect to the button, where as into the attached image, it looks like a thin inside circle.

Please find attached image as an example of what I’m trying to achieve:

Setting Image

I’ve tried the following code:

import React from 'react';
import { TouchableOpacity, Text, StyleSheet } from 'react-native';

const HomeButton = ({ title, color }) => {
  return (
    <TouchableOpacity style={[styles.button, { backgroundColor: color }]}>
      <Text style={styles.title}>{title}</Text>
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  button: {
    width: 100,
    height: 100,
    borderRadius: 100,
    justifyContent: 'center',
    alignItems: 'center',
    elevation: 5,
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 3.5,
  },
  title: {
    fontSize: 20,
    color: '#fff',
    textAlign: 'center',
  },
});

export default HomeButton;

This actually do create a circular button, but with very static color.

Tried also to install and use the following just to break the one color issue, but still didn’t work:

npm install react-native-linear-gradient

Where the code became something like this:

const HomeButton = ({ title, colors }) => {
  return (
    <TouchableOpacity style={styles.button}>
      <LinearGradient
        colors={colors}
        style={styles.gradient}
      >
        <View style={styles.innerCircle} />
        <Text style={styles.title}>{title}</Text>
      </LinearGradient>
    </TouchableOpacity>
  );
};

Is the design actually doable? Any help please?

2

Answers


  1. If you have a severely tricky component like in the example you provided you can try to download it as an image and create a button in the following way:

    interface Props extends TouchableOpacityProps {
      title: string;
    }
    
    const HomeButton: React.FC<Props> = ({title, style, ...restProps}) => (
      <TouchableOpacity style={[{alignSelf: 'center'}, style]} {...restProps}>
        <ImageBackground
          source={require('@assets/images/button.jpeg')}
          style={{justifyContent: 'center', alignItems: 'center', width: 100, height: 100, padding: 10}}>
          <Text style={{color: 'white', textDecorationLine: 'underline', flexWrap: 'wrap'}}>{title}</Text>
        </ImageBackground>
     </TouchableOpacity>
    )
    

    Gif how does work example from the code snippet

    Login or Signup to reply.
  2. You can use react native svg. I started by creating a circle component

    import { Circle, Path } from 'react-native-svg';
    import { useRef } from 'react';
    import { polarToCartesian, drawArc } from '../helpers/path';
    
    const SvgCircle = ({
      color,
      isOutline,
      strokeWidth = 3,
      radius,
      center,
      // where shadow angle should start and end on circle
      shadowAnglePairs = [],
      isDebugging,
      // try playing around with this value
      steps = 5,
      // play around with these values to change how thick
      // the shadows can get
      xNoiseAmplitude = 1,
      yNoiseAmplitude = 2,
    }) => {
      const pathRef = useRef(null);
      const [x, y] = center;
      const shadows = shadowAnglePairs.map(
        ([startAngleInDegrees, endAngleInDegrees]) => {
          const start = polarToCartesian(x, y, radius, startAngleInDegrees);
          const end = polarToCartesian(x, y, radius, endAngleInDegrees);
          const degreeStep = (endAngleInDegrees - startAngleInDegrees) / steps;
          // trace arc along circle
          let d = drawArc(x, y, radius, startAngleInDegrees, endAngleInDegrees);
          // get points along circle
          const stepPoints = Array(steps)
            .fill(null)
            .map((_, i) =>
              polarToCartesian(x, y, radius, startAngleInDegrees + degreeStep * i)
            );
          // add noise along each point
          stepPoints.forEach(({ x, y }, i) => {
            d += `L ${x + Math.random() * xNoiseAmplitude} ${
              y - Math.random() * yNoiseAmplitude
            }`;
          });
          return { start, end, d, stepPoints };
        }
      );
      return (
        <>
          <Circle
            cx={x}
            cy={y}
            r={radius}
            fill={isOutline ? 'none' : color}
            stroke={isOutline ? color : 'none'}
          />
          {shadows.map(({ start, end, d, stepPoints }) => {
            return (
              <>
                <Path
                  d={d}
                  fill={color}
                  stroke={color}
                  ref={pathRef}
                  strokeWidth={strokeWidth}
                />
    
                {
                  // visual for start,end, & step points
                  isDebugging &&
                    [...stepPoints, start, end].map(({ x, y }, i) => (
                      <Circle
                        cx={x}
                        cy={y}
                        r={1}
                        fill={i > stepPoints.length - 1 ? 'red' : 'black'}
                      />
                    ))
                }
              </>
            );
          })}
        </>
      );
    };
    
    export default SvgCircle;
    
    

    Now you can draw your circles and decide where shadows should appear:

    import { useState } from 'react';
    import { TouchableOpacity, StyleSheet, View } from 'react-native';
    import { colorManipulators } from '@phantom-factotum/colorutils';
    import { Svg, Text, Circle, G, Path } from 'react-native-svg';
    import SvgCircle from './SvgCircle';
    
    const HomeButton = ({ title, color, onPress, size = 100, isDebugging }) => {
      const darkColor = colorManipulators.darkenColor(color, 0.5);
      const radius = size / 2;
      const center = [size / 2, size / 2];
      return (
        <TouchableOpacity onPress={onPress}>
          <View
            style={[
              styles.button,
              {
                borderRadius: size,
                width: size,
                height: size,
              },
            ]}>
            <Svg viewBox={`0 0 ${size} ${size}`}>
              {/*background color circle*/}
              <SvgCircle color={color} radius={radius * 0.85} center={center} />
              {/*outline rings*/}
              <SvgCircle
                color={darkColor}
                isOutline
                strokeWidth={1}
                radius={radius * 0.95}
                center={center}
                shadowAnglePairs={[
                  [290, 360],
                  [110, 180],
                ]}
                isDebugging={isDebugging}
              />
              <SvgCircle
                color={darkColor}
                isOutline
                strokeWidth={1}
                radius={radius * 0.9}
                center={center}
                angleOffset={2}
                shadowAnglePairs={[
                  [300, 330],
                  [120, 160],
                ]}
                isDebugging={isDebugging}
              />
              <SvgCircle
                color={darkColor}
                isOutline
                strokeWidth={1}
                radius={radius * 0.45}
                center={center}
                shadowAnglePairs={[
                  [300, 330],
                  [120, 160],
                ]}
                isDebugging={isDebugging}
              />
              <SvgCircle
                color={darkColor}
                isOutline
                strokeWidth={1}
                radius={radius * 0.5}
                center={center}
                shadowAnglePairs={[
                  [300, 330],
                  [120, 160],
                ]}
                isDebugging={isDebugging}
              />
              <Text
                fill="#fff"
                stroke="#fff"
                fontSize="20"
                x={center[0]}
                y={center[1]}
                textAnchor="middle"
                dominantBaseline="middle">
                {title}
              </Text>
            </Svg>
          </View>
        </TouchableOpacity>
      );
    };
    
    const styles = StyleSheet.create({
      button: {
        width: 100,
        height: 100,
        bordersize: 100,
        justifyContent: 'center',
        alignItems: 'center',
      },
      title: {
        fontSize: 20,
        color: '#fff',
        textAlign: 'center',
      },
    });
    
    export default HomeButton;
    

    Usage:

    import { Text, SafeAreaView, StyleSheet } from 'react-native';
    import LabeledSwitch from './components/LabeledSwitch';
    import { useState } from 'react';
    import SvgButton from './components/SvgButton';
    
    export default function App() {
      const [isDebugging, setIsDebugging] = useState(false);
      return (
        <SafeAreaView style={styles.container}>
          <LabeledSwitch
            value={isDebugging}
            onValueChange={setIsDebugging}
            label={'Toggle debugging'}
            style={{ width: '50%',margin:5 }}
          />
          <SvgButton
            color="lightblue"
            onPress={() => console.log('I was pressed')}
            title="Home"
            size={100}
            isDebugging={isDebugging}
          />
        </SafeAreaView>
      );
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems:'center',
        backgroundColor: '#ecf0f1',
        padding: 8,
      },
      paragraph: {
        margin: 24,
        fontSize: 18,
        fontWeight: 'bold',
        textAlign: 'center',
      },
    });
    

    demo

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search