React Native のプロジェクトに複数の Floating Action Button を表示する(react-native-floating-actionを使う)

React Native のプロジェクトで、複数の浮いたボタンを表示させる方法を紹介します。
Star が多いライブラリは react-native-floating-actionreact-native-action-button と の2つです。

どちらも最終更新からだいぶ時間が経っていますが、react-native-floating-action の方は最終が「2年前」で、 react-native-action-button の「4年前」に比べるとまだ新しいかな、というのと、
ボタンをタップしたときに背景が暗くなるのが良いなと思ったので、react-native-floating-action を使いました。

以下のコマンドで react-native-floating-action をインストールしてください。

shell
npm i react-native-floating-action --save

以下のようなコンポーネントを作成しました。 react-native-floating-action はボタンの部分です。

自分でアイコンを選んで設定したい場合は、 @expo-vector-icons を使います。

アイコンの一覧は以下から選択できます。
https://icons.expo.fyi/

公式のドキュメントは以下です。
https://docs.expo.dev/guides/icons/

基本的な使い方

以下のように、ボタンをタップしたときに表示するアイコンの配列を定義します。
positionで配置する順番を設定できます。

App.tsx
interface FloatingActionProps {
  text: string;
  icon: JSX.Element;
  name: string;
  position: number;
}

const actions: FloatingActionProps[] = [
  {
    text: 'Register repeating tasks',
    icon: <Feather name="repeat" size={20} color="white" />,
    name: 'repeat_tasks',
    position: 1,
  },
  {
    text: 'Documenting Results',
    icon: <Feather name="download" size={20} color="white" />,
    name: 'documenting_results',
    position: 2,
  },
  {
    text: 'Reset all time',
    icon: <FontAwesome name="repeat" size={20} color="white" />,
    name: 'reset_all_time',
    position: 3,
  },
];

コンポーネントの中に以下のような FloatingAction を配置してください。

App.tsx
<FloatingAction
  actions={actions}
  onPressItem={(name) => {
    if (name !== undefined) {
      console.log(`selected button: ${name}`);
    }
  }}
  onOpen={() => {
    setIsButtonOpen(true);
  }}
  onClose={() => {
    setIsButtonOpen(false);
  }}
/>

console.log(selected button: ${name}); に渡される nameactions で設定した name: 'reset_all_time', のような name です。

オリジナルのアイコンを設定する

デフォルトでは「+」マークのアイコンとなります。

ボタンを「+」=「追加」以外の用途で使いたいケースもあるでしょう。そういうときは、floatingIcon プロパティにアイコンを指定すればOKです。 @expo/vector-icons から import したアイコンをそのまま設定できます。

App.tsx
<FloatingAction
    actions={actions}
    onPressItem={(name) => {
      console.log(`selected button:`);
    }}
    floatingIcon={
        <Entypo name="documents" size={24} color="white" />
    }
    onOpen={() => {
      setIsButtonOpen(true);
    }}
    onClose={() => {
      setIsButtonOpen(false);
    }}
/>

オリジナルのアイコンを回転させる

上記で設定したオリジナルアイコンは、デフォルトの「+」アイコンのようなアニメーションが効きませんでした。デフォルトの場合は、タップしたタイミングで90度回転して、タップされたことがわかりやすくなっています。
全く回転しない場合、ユーザーへのフィードバックがやや足りない印象を受けます。

Animated を使って、タップしたタイミングで90度回転できるようにしましょう。
https://reactnative.dev/docs/animated

App.tsx
  const [isButtonOpen, setIsButtonOpen] = useState(false);
  const rotateAnim = useRef(new Animated.Value(0)).current; 

  useEffect(() => {
    Animated.timing(rotateAnim, {
      toValue: isButtonOpen ? 1 : 0,
      duration: 200,
      useNativeDriver: true,
      easing: Easing.linear,
    }).start();
  }, [isButtonOpen]);

  const rotation = rotateAnim.interpolate({
    inputRange: [0, 1],
    outputRange: ['0deg', '90deg'],
  });

return (
<FloatingAction
  actions={actions}
  onPressItem={(name) => {
    if (name !== undefined) {
      console.log(`selected button: ${name}`);
    }
  }}
  floatingIcon={
    <Animated.View style={{ transform: [{ rotate: rotation }] }}>
      <Entypo name="documents" size={24} color="white" />
    </Animated.View>
  }
  onOpen={() => {
    setIsButtonOpen(true);
  }}
  onClose={() => {
    setIsButtonOpen(false);
  }}
/>
)

上記のように Animated を指定すれば、アイコンをタップしたときにクルっと90度回転させることができます。

この記事を書いているときに作ったアプリ