React Native + Expo でカクッと動く動作(haptic feedback)を実現する

「ブッ」とスマホが振動するような動きをするアプリは多々あります。「カッ」と表現した方が良いかもしれません。この動作を haptic feedback (触覚フィードバック)と呼びます。

Expo + React Native でできたプロジェクトで、 haptic feedback を実現する方法を紹介します。

必要なライブラリのインストール

shell
npx expo install expo-haptics

実装方法

Expo のサンプルそのままで、非常にシンプルに動作します。

Haptic.tsx
Haptics.notificationAsync(
                Haptics.NotificationFeedbackType.Error
              )

自分の場合は、 React Navigator で「+」のタブをタップしたときに「カクッ」と触覚フィードバックが走るようにしました。

全体のコードを載せます。

TopTabNavigator.tsx
import MemoListScreen from '../screens/MemoListScreen';
import TopTabAddScreen from '../screens/TopTabAddScreen';
import TopTabSettingScreen from '../screens/TopTabSettingScreen';
import { Entypo, SimpleLineIcons } from '@expo/vector-icons';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
import * as Haptics from 'expo-haptics';
import { Text } from 'react-native';

const Tab = createMaterialTopTabNavigator();

interface TabScreenProp {
  name: string;
  tabId: string;
}

const tabScreenProps: TabScreenProp[] = [
  { name: 'TodoList', tabId: '123' },
  { name: 'Memo', tabId: '456' },
  { name: 'Tasks', tabId: '789' },
  { name: '買い物', tabId: '101112' },
  { name: '下書き', tabId: '131415' },
  {
    name: '長い長いタブの名前はどうなるのかな?',
    tabId: '131415',
  },
];
export default function TopTabNavigator() {
  return (
    <Tab.Navigator
      screenOptions={{
        tabBarScrollEnabled: true,
        tabBarItemStyle: { maxWidth: 160 },
      }}
    >
      {tabScreenProps.map((tabScreenProp) => (
        <Tab.Screen
          key={tabScreenProp.name}
          name={tabScreenProp.name}
          component={MemoListScreen}
          initialParams={{ tabId: tabScreenProp.tabId }}
          options={{
            tabBarLabel: ({ focused, color }) => (
              <Text numberOfLines={1} ellipsizeMode="tail" style={{ color }}>
                {tabScreenProp.name}
              </Text>
            ),
          }}
        />
      ))}
      <Tab.Screen
        key={'add-uuid'}
        name={'add-uuid'}
        component={TopTabAddScreen}
        initialParams={{ tabId: 'add' }}
        options={{
          tabBarLabel: ({ focused, color }) => <Entypo name="plus" size={24} color={color} />,
        }}
        listeners={{
          tabPress: async (e) => {
            await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
          },
        }}
      />
      <Tab.Screen
        key={'setting-uuid'}
        name={'setting-uuid'}
        component={TopTabSettingScreen}
        initialParams={{ tabId: 'add' }}
        options={{
          tabBarLabel: ({ focused, color }) => (
            <SimpleLineIcons
              name="settings"
              size={24}
              color="black"
              onPress={async () => {
                await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
              }}
            />
          ),
        }}
        listeners={{
          tabPress: async (e) => {
            Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
          },
        }}
      />
    </Tab.Navigator>
  );
}

以下の「+」をタップしたときに震えます。

余談ですが、 GPT-4に聞いたら React Navigation で onPress で動作する機能はありません、みたいに回答があったのですが、普通に検索したら StackOverflow で回答が見つかったので、検索もまだまだ活用しなければなりませんね笑

React Navigation の Tab.Screen をタップしたときに onPress で関数を実行する方法

TabScreen.tsx
<Tab.Screen
  name="Settings2"
  component={SettingsScreen}
  options={{
    tabBarButton: props => (
      <TouchableOpacity {...props} onPress={() => alert(123)} />
    ),
  }}
/>