react native 开发

1.style

1.1 使用StyleSheet来定义样式

style 不支持 css 继承,使用StyleSheet来定义样式

import React from "react";
import { StyleSheet, Text, View } from "react-native";

const LotsOfStyles = () => {
  return (
    <View style={styles.container}>
      <Text style={styles.red}>just red</Text>
      <Text style={styles.bigBlue}>just bigBlue</Text>
      <Text style={[styles.bigBlue, styles.red]}>bigBlue, then red</Text>
      <Text style={[styles.red, styles.bigBlue]}>red, then bigBlue</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    marginTop: 50,
  },
  bigBlue: {
    color: "blue",
    fontWeight: "bold",
    fontSize: 30,
  },
  red: {
    color: "red",
  },
});

export default LotsOfStyles;

1.2 样式单位

不支持vh,vw,px

1.3 border 不支持复合写法

2.flexbox

flexbox 默认为flex-direction:'column

web 端为默认flex-direction:'row

3.响应式布局

3.1 dimensions(尺寸)

响应式布局计算字体大小

默认设计稿为 750px

import { Dimensions } from "react-native";

const { width, height } = Dimensions.get("window");
const mode = height > width ? "portrait" : "landscape";
const clientWidth = mode === "portrait" ? width : height;

export function font(num: number, designWidth?: number): number {
  designWidth = designWidth || 750;
  const fontSize = (clientWidth * 2) / designWidth;
  return num * fontSize;
}

const styles = StyleSheet.create({
  container: {
    height: 100,
  },
  text: {
    fontSize: font(12),
  },
});

4.整体架构

配置@别名babel.config.js

yarn add babel-plugin-module-resolver -D
module.exports = {
  presets: ["module:metro-react-native-babel-preset"],
  plugins: [
    [
      "module-resolver",
      {
        root: ["./src"],
        extensions: [".ios.js", ".android.js", ".js", ".ts", ".tsx", ".json"],
        alias: {
          "@": ["./src"],
        },
      },
    ],
  ],
};

配置 tsConfigts.config.json

{
  "extends": "@tsconfig/react-native/tsconfig.json",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

增加prettier format脚本

{
  "scripts": {
    "format": "npx prettier --write \"./src/**/*.ts\" \"./src/**/*.tsx\" "
  }
}

5.i18n 国际化

参考文献open in new window

安装i18nextreact-native-localize

yarn add react-i18next i18next
yarn add react-native-localize
# ios
npx pod-install

新建locales/en-us.json

{
  "hello": "hello"
}

新建locales/zh-cn.json

{
  "hello": "你好"
}

新建locales/index.ts

import i18next from "i18next";
import { initReactI18next } from "react-i18next";
import * as RNLocalize from "react-native-localize";
import storage from "./storage";
import { reset } from "./RootNavigation";

export const lngKey = "@lng";

const languageDetector = {
  type: "languageDetector",
  async: true,
  detect: function (callback) {
    // 获取上次选择的语言
    storage.get(lngKey, "locale").then((lng) => {
      // 如果是跟随本地,则获取系统语言
      if (lng === "locale") {
        callback(getSystemLanguage());
      } else {
        callback(lng);
      }
    });
  },
};

// 初始化i18next配置
i18next
  .use(languageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: "zh", // 切换语言失败时的使用的语言
    debug: __DEV__, // 开发环境开启调试
    // 资源文件
    resources: {
      en: {
        translation: require("./en-US.json"),
      },
      zh: {
        translation: require("./zh-CN.json"),
      },
    },
    react: {
      useSuspense: false,
    },
  });

/**
 * 获取当前系统语言
 * @returns
 */
export const getSystemLanguage = (): string => {
  const locales = RNLocalize.getLocales();
  return locales[0].languageCode;
};

/**
 * 切换语言
 * @param lng
 */
export const changeLanguage = async (lng?: "en" | "zh" | "locale") => {
  // 切换语言
  await i18next.changeLanguage(lng === "locale" ? getSystemLanguage() : lng);
  // 持久化当前选择
  await storage.set(lngKey, lng);
};

export default i18next;

app.js

import "@/locales";

react-native中使用

import React from "react";
import { Text } from "react-native";
import { useTranslation } from "react-i18next";

export function MyComponent() {
  const { t, i18n } = useTranslation();
  // or const [t, i18n] = useTranslation();

  return <Text>{t("hello")}</Text>;
}

6.生成二维码

参考文献open in new window

使用react-native-qrcode-svg react-native-svg

添加依赖

yarn add react-native-qrcode-svg react-native-svg

简单使用:

import QRCode from "react-native-qrcode-svg";

function Page() {
  return <QRCode value="This is the value in the QRcode" />;
}

7.启动时闪屏react-native-splash-screen

参考资料open in new window

安装:

yarn add react-native-splash-screen

8.路由navigation

基本路由:

import {
  createStackNavigator,
  CardStyleInterpolators,
  CommonActions,
  HeaderStyleInterpolators,
} from "@react-navigation/stack";
import { NavigationContainer } from "@react-navigation/native";

import React from "react";
import Login from "@/pages/user/Login";

<NavigationContainer>
  <Stack.Navigator
    initialRouteName="Login"
    screenOptions={{
      headerTitleAlign: "center",
      headerStyleInterpolator: HeaderStyleInterpolators.forUIKit,
      cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS,
      gestureEnabled: true,
      gestureDirection: "horizontal",
    }}
  >
    <Stack.Screen
      name="Login"
      options={{
        headerShown: false,
      }}
      component={Login}
    />
    <Stack.Screen
      name="Home"
      options={{
        headerShown: false,
      }}
      component={Home}
    />
  </Stack.Navigator>
</NavigationContainer>;

reset

navigation.reset({
  index: 0,
  routes: [
    {
      name: "Home",
      params: { someParam: "Param1" },
    },
  ],
});

函数式 reset:

import { CommonActions } from "@react-navigation/native";

navigation.dispatch(
  CommonActions.reset({
    index: 1,
    routes: [
      { name: "Home" },
      {
        name: "Profile",
        params: { user: "jane" },
      },
    ],
  })
);

9.服务端事件SSE

服务端:sever.js

const express = require("express");
const http = require("http");
const { Server } = require("http");
const { EventEmitter } = require("events");

const app = express();
const server = http.createServer(app);
const eventEmitter = new EventEmitter();

app.use(express.static("public"));

app.get("/api/events", (req, res) => {
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.setHeader("Connection", "keep-alive");

  const listener = (data) => {
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  };

  eventEmitter.on("message", listener);

  req.on("close", () => {
    eventEmitter.off("message", listener);
  });
});

// Endpoint to send messages to clients
app.post("/api/send-message", (req, res) => {
  const message = { text: "Hello from server!" };
  console.log("/api/send-message");
  eventEmitter.emit("message", message);
  res.json({ success: true });
});

setInterval(() => {
  const message = "rolling counter:" + Date.now();
  console.log(message);
  eventEmitter.emit("message", message);
}, 3000);

const PORT = 3000;
server.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

web 客户端

<script setup lang="ts">
const eventSource = new EventSource("/api/events");

eventSource.addEventListener("message", (event) => {
  const data = JSON.parse(event.data);
  console.log("Received message in Web Browser:", data);
});

// Example of sending a message to the server

const click = () => {
  fetch("/api/send-message", {
    method: "POST",
  });
};
</script>

<template>
  <div @click="click">clickclickclick</div>
</template>

<style scoped></style>

react-native示例

import EventSource from "react-native-sse";
import "react-native-url-polyfill/auto"; // Use URL polyfill in React Native

const SSEExample = () => {
  useEffect(() => {
    console.log("SSEExample");

    const es = new EventSource("http://192.168.1.9:3000/api/events");

    es.addEventListener("message", (event) => {
      const data = JSON.parse(event?.data);
      console.log("Received message in React Native:", data);
    });

    // const es = new EventSource('http://192.168.1.9:3000/api/event');

    // es.addEventListener('open', event => {
    //   console.log('Open SSE connection.');
    // });

    // es.addEventListener('message', event => {
    //   const data = JSON.parse(event.data);
    //   console.log('Received message in React Native:', data);
    // });

    // es.addEventListener('error', event => {
    //   if (event.type === 'error') {
    //     console.error('Connection error:', event.message);
    //   } else if (event.type === 'exception') {
    //     console.error('Error:', event.message, event.error);
    //   }
    // });

    // es.addEventListener('close', event => {
    //   console.log('Close SSE connection.');
    // });

    return () => {
      es.removeAllEventListeners();
      es.close();
    };
  }, []);

  function sendMessage() {
    console.log("sendMessage");
    fetch("http://192.168.1.9:3000/api/send-message", { method: "POST" });
  }

  return (
    <View>
      <Text>SSE</Text>
      <TouchableOpacity
        onPress={sendMessage}
        style={{ backgroundColor: "#ccc", width: "100%" }}
      >
        <Text style={{ textAlign: "center", lineHeight: 80 }}>sendMessage</Text>
      </TouchableOpacity>
    </View>
  );
};

关于react-native 的 sse 在 android 被拦截的问题,参见react-native/issues/28835open in new window

Try to disable Flipper network interceptor. Go to android/app/src/debug/java//ReactNativeFlipper.java and comment next lines of code:

  // try to comment this code
  NetworkingModule.setCustomClientBuilder(
    new NetworkingModule.CustomClientBuilder() {
      @Override
      public void apply(OkHttpClient.Builder builder) {
        builder.addNetworkInterceptor(new     FlipperOkhttpInterceptor(networkFlipperPlugin));
      }
    }
  );

10 lottie 动画

success.jsonopen in new window

安装 lottie

yarn add lottie-react-native
import LottieView from "lottie-react-native";

const animationRef = useRef<LottieView>(null);

function play() {
  animationRef?.current?.play?.();
  console.log("onAnimationplay");
}

function onAnimationFinish() {
  console.log("onAnimationFinish");
  animationRef?.current?.pause?.();
}

<LottieView
  ref={animationRef}
  style={{ width: 200, height: 200 }}
  source={require("@/assets/animation/success.json")}
  duration={4000}
  loop={false}
  autoPlay={false}
  onAnimationFinish={onAnimationFinish}
/>;

11 Header

<Header
  title={"第二级详情页"}
  onLeftPress={() => {
    navigation.goBack();
  }}
/>
export const X_WIDTH = 375;
export const X_HEIGHT = 812;

export const X12_WIDTH = 390;
export const X12_HEIGHT = 844;

export const XSMAX_WIDTH = 414;
export const XSMAX_HEIGHT = 896;

export const PRO_MAX_12_WIDTH = 428;
export const PRO_MAX_12_HEIGHT = 926;
import { Dimensions, Platform, StatusBar } from "react-native";
import {
  PRO_MAX_12_HEIGHT,
  PRO_MAX_12_WIDTH,
  X12_HEIGHT,
  X12_WIDTH,
  X_HEIGHT,
  X_WIDTH,
  XSMAX_HEIGHT,
  XSMAX_WIDTH,
} from "./Constants";

const { height, width } = Dimensions.get("window");

export const isIPhoneX = () =>
  Platform.OS === "ios" && !Platform.isPad && !Platform.isTVOS
    ? (width === X_WIDTH && height === X_HEIGHT) ||
      (width === XSMAX_WIDTH && height === XSMAX_HEIGHT) ||
      (width === X12_WIDTH && height === X12_HEIGHT) ||
      (width === PRO_MAX_12_WIDTH && height === PRO_MAX_12_HEIGHT)
    : false;

export const StatusBarHeight = Platform.select({
  ios: isIPhoneX() ? 44 : 20,
  android: StatusBar.currentHeight,
  default: 0,
});
import {
  Platform,
  SafeAreaView,
  StatusBar,
  View,
  Text,
  StyleSheet,
} from "react-native";
import React from "react";
import { StatusBarHeight } from "../util";

interface HeaderProps {
  translucent?: boolean;
  backgroundColor?: string;
  title?: string;
  leftComponent?: React.ReactNode;
  centerComponent?: React.ReactNode;
  rightComponent?: React.ReactNode;
  onLeftPress: () => void;
}

export const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
  const {
    translucent = false,
    backgroundColor,
    title = "标题",
    leftComponent,
    centerComponent,
    rightComponent,
    onLeftPress,
  } = props;
  return (
    <>
      <StatusBar
        barStyle={"dark-content"}
        translucent={translucent}
        backgroundColor={"transparent"}
      />
      <View
        style={{
          backgroundColor: backgroundColor,
        }}
      >
        <SafeAreaView
          style={{
            height: Platform.OS === "ios" ? 44 + StatusBarHeight : 44,
            marginTop: translucent ? StatusBar.currentHeight : 0,
          }}
        >
          <View style={styles.container}>
            {leftComponent || (
              <Text style={{ width: 30 }} onPress={onLeftPress}>
                {"<"}
              </Text>
            )}
            {centerComponent || (
              <View
                style={{
                  flex: 1,
                  alignItems: "center",
                }}
              >
                <Text>{title}</Text>
              </View>
            )}
            {rightComponent || <Text style={{ width: 30 }}>{""}</Text>}
          </View>
        </SafeAreaView>
      </View>
    </>
  );
};

const styles = StyleSheet.create({
  container: {
    paddingHorizontal: 16,
    flexDirection: "row",
    alignItems: "center",
    height: 44,
  },
});
# react native 开发

## 1.style

### 1.1 使用`StyleSheet`来定义样式

style 不支持 css 继承,使用`StyleSheet`来定义样式

```tsx
import React from "react";
import { StyleSheet, Text, View } from "react-native";

const LotsOfStyles = () => {
  return (
    <View style={styles.container}>
      <Text style={styles.red}>just red</Text>
      <Text style={styles.bigBlue}>just bigBlue</Text>
      <Text style={[styles.bigBlue, styles.red]}>bigBlue, then red</Text>
      <Text style={[styles.red, styles.bigBlue]}>red, then bigBlue</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    marginTop: 50,
  },
  bigBlue: {
    color: "blue",
    fontWeight: "bold",
    fontSize: 30,
  },
  red: {
    color: "red",
  },
});

export default LotsOfStyles;

1.2 样式单位

不支持vh,vw,px

1.3 border 不支持复合写法

2.flexbox

flexbox 默认为flex-direction:'column

web 端为默认flex-direction:'row

3.响应式布局

3.1 dimensions(尺寸)

响应式布局计算字体大小

默认设计稿为 750px

import { Dimensions } from "react-native";

const { width, height } = Dimensions.get("window");
const mode = height > width ? "portrait" : "landscape";
const clientWidth = mode === "portrait" ? width : height;

export function font(num: number, designWidth?: number): number {
  designWidth = designWidth || 750;
  const fontSize = (clientWidth * 2) / designWidth;
  return num * fontSize;
}

const styles = StyleSheet.create({
  container: {
    height: 100,
  },
  text: {
    fontSize: font(12),
  },
});

4.整体架构

配置@别名babel.config.js

yarn add babel-plugin-module-resolver -D
module.exports = {
  presets: ["module:metro-react-native-babel-preset"],
  plugins: [
    [
      "module-resolver",
      {
        root: ["./src"],
        extensions: [".ios.js", ".android.js", ".js", ".ts", ".tsx", ".json"],
        alias: {
          "@": ["./src"],
        },
      },
    ],
  ],
};

配置 tsConfigts.config.json

{
  "extends": "@tsconfig/react-native/tsconfig.json",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

增加prettier format脚本

{
  "scripts": {
    "format": "npx prettier --write \"./src/**/*.ts\" \"./src/**/*.tsx\" "
  }
}

5.i18n 国际化

参考文献open in new window

安装i18nextreact-native-localize

yarn add react-i18next i18next
yarn add react-native-localize
# ios
npx pod-install

新建locales/en-us.json

{
  "hello": "hello"
}

新建locales/zh-cn.json

{
  "hello": "你好"
}

新建locales/index.ts

import i18next from "i18next";
import { initReactI18next } from "react-i18next";
import * as RNLocalize from "react-native-localize";
import storage from "./storage";
import { reset } from "./RootNavigation";

export const lngKey = "@lng";

const languageDetector = {
  type: "languageDetector",
  async: true,
  detect: function (callback) {
    // 获取上次选择的语言
    storage.get(lngKey, "locale").then((lng) => {
      // 如果是跟随本地,则获取系统语言
      if (lng === "locale") {
        callback(getSystemLanguage());
      } else {
        callback(lng);
      }
    });
  },
};

// 初始化i18next配置
i18next
  .use(languageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: "zh", // 切换语言失败时的使用的语言
    debug: __DEV__, // 开发环境开启调试
    // 资源文件
    resources: {
      en: {
        translation: require("./en-US.json"),
      },
      zh: {
        translation: require("./zh-CN.json"),
      },
    },
    react: {
      useSuspense: false,
    },
  });

/**
 * 获取当前系统语言
 * @returns
 */
export const getSystemLanguage = (): string => {
  const locales = RNLocalize.getLocales();
  return locales[0].languageCode;
};

/**
 * 切换语言
 * @param lng
 */
export const changeLanguage = async (lng?: "en" | "zh" | "locale") => {
  // 切换语言
  await i18next.changeLanguage(lng === "locale" ? getSystemLanguage() : lng);
  // 持久化当前选择
  await storage.set(lngKey, lng);
};

export default i18next;

app.js

import "@/locales";

react-native中使用

import React from "react";
import { Text } from "react-native";
import { useTranslation } from "react-i18next";

export function MyComponent() {
  const { t, i18n } = useTranslation();
  // or const [t, i18n] = useTranslation();

  return <Text>{t("hello")}</Text>;
}

6.生成二维码

参考文献open in new window

使用react-native-qrcode-svg react-native-svg

添加依赖

yarn add react-native-qrcode-svg react-native-svg

简单使用:

import QRCode from "react-native-qrcode-svg";

function Page() {
  return <QRCode value="This is the value in the QRcode" />;
}

7.启动时闪屏react-native-splash-screen

参考资料open in new window

安装:

yarn add react-native-splash-screen

8.路由navigation

基本路由:

import {
  createStackNavigator,
  CardStyleInterpolators,
  CommonActions,
  HeaderStyleInterpolators,
} from "@react-navigation/stack";
import { NavigationContainer } from "@react-navigation/native";

import React from "react";
import Login from "@/pages/user/Login";

<NavigationContainer>
  <Stack.Navigator
    initialRouteName="Login"
    screenOptions={{
      headerTitleAlign: "center",
      headerStyleInterpolator: HeaderStyleInterpolators.forUIKit,
      cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS,
      gestureEnabled: true,
      gestureDirection: "horizontal",
    }}
  >
    <Stack.Screen
      name="Login"
      options={{
        headerShown: false,
      }}
      component={Login}
    />
    <Stack.Screen
      name="Home"
      options={{
        headerShown: false,
      }}
      component={Home}
    />
  </Stack.Navigator>
</NavigationContainer>;

reset

navigation.reset({
  index: 0,
  routes: [
    {
      name: "Home",
      params: { someParam: "Param1" },
    },
  ],
});

函数式 reset:

import { CommonActions } from "@react-navigation/native";

navigation.dispatch(
  CommonActions.reset({
    index: 1,
    routes: [
      { name: "Home" },
      {
        name: "Profile",
        params: { user: "jane" },
      },
    ],
  })
);

9.服务端事件SSE

服务端:sever.js

const express = require("express");
const http = require("http");
const { Server } = require("http");
const { EventEmitter } = require("events");

const app = express();
const server = http.createServer(app);
const eventEmitter = new EventEmitter();

app.use(express.static("public"));

app.get("/api/events", (req, res) => {
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.setHeader("Connection", "keep-alive");

  const listener = (data) => {
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  };

  eventEmitter.on("message", listener);

  req.on("close", () => {
    eventEmitter.off("message", listener);
  });
});

// Endpoint to send messages to clients
app.post("/api/send-message", (req, res) => {
  const message = { text: "Hello from server!" };
  console.log("/api/send-message");
  eventEmitter.emit("message", message);
  res.json({ success: true });
});

setInterval(() => {
  const message = "rolling counter:" + Date.now();
  console.log(message);
  eventEmitter.emit("message", message);
}, 3000);

const PORT = 3000;
server.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

web 客户端

<script setup lang="ts">
const eventSource = new EventSource("/api/events");

eventSource.addEventListener("message", (event) => {
  const data = JSON.parse(event.data);
  console.log("Received message in Web Browser:", data);
});

// Example of sending a message to the server

const click = () => {
  fetch("/api/send-message", {
    method: "POST",
  });
};
</script>

<template>
  <div @click="click">clickclickclick</div>
</template>

<style scoped></style>

react-native示例

import EventSource from "react-native-sse";
import "react-native-url-polyfill/auto"; // Use URL polyfill in React Native

const SSEExample = () => {
  useEffect(() => {
    console.log("SSEExample");

    const es = new EventSource("http://192.168.1.9:3000/api/events");

    es.addEventListener("message", (event) => {
      const data = JSON.parse(event?.data);
      console.log("Received message in React Native:", data);
    });

    // const es = new EventSource('http://192.168.1.9:3000/api/event');

    // es.addEventListener('open', event => {
    //   console.log('Open SSE connection.');
    // });

    // es.addEventListener('message', event => {
    //   const data = JSON.parse(event.data);
    //   console.log('Received message in React Native:', data);
    // });

    // es.addEventListener('error', event => {
    //   if (event.type === 'error') {
    //     console.error('Connection error:', event.message);
    //   } else if (event.type === 'exception') {
    //     console.error('Error:', event.message, event.error);
    //   }
    // });

    // es.addEventListener('close', event => {
    //   console.log('Close SSE connection.');
    // });

    return () => {
      es.removeAllEventListeners();
      es.close();
    };
  }, []);

  function sendMessage() {
    console.log("sendMessage");
    fetch("http://192.168.1.9:3000/api/send-message", { method: "POST" });
  }

  return (
    <View>
      <Text>SSE</Text>
      <TouchableOpacity
        onPress={sendMessage}
        style={{ backgroundColor: "#ccc", width: "100%" }}
      >
        <Text style={{ textAlign: "center", lineHeight: 80 }}>sendMessage</Text>
      </TouchableOpacity>
    </View>
  );
};

关于react-native 的 sse 在 android 被拦截的问题,参见react-native/issues/28835open in new window

Try to disable Flipper network interceptor. Go to android/app/src/debug/java//ReactNativeFlipper.java and comment next lines of code:

  // try to comment this code
  NetworkingModule.setCustomClientBuilder(
    new NetworkingModule.CustomClientBuilder() {
      @Override
      public void apply(OkHttpClient.Builder builder) {
        builder.addNetworkInterceptor(new     FlipperOkhttpInterceptor(networkFlipperPlugin));
      }
    }
  );

10 lottie 动画

success.jsonopen in new window

安装 lottie

yarn add lottie-react-native
import LottieView from "lottie-react-native";

const animationRef = useRef<LottieView>(null);

function play() {
  animationRef?.current?.play?.();
  console.log("onAnimationplay");
}

function onAnimationFinish() {
  console.log("onAnimationFinish");
  animationRef?.current?.pause?.();
}

<LottieView
  ref={animationRef}
  style={{ width: 200, height: 200 }}
  source={require("@/assets/animation/success.json")}
  duration={4000}
  loop={false}
  autoPlay={false}
  onAnimationFinish={onAnimationFinish}
/>;

11 Header

<Header
  title={"第二级详情页"}
  onLeftPress={() => {
    navigation.goBack();
  }}
/>

header

import {
  Platform,
  SafeAreaView,
  StatusBar,
  View,
  Text,
  StyleSheet,
  type ViewStyle,
  TouchableWithoutFeedback,
} from "react-native";
import React from "react";
import { StatusBarHeight } from "@/utils/navbar";
import { useNavigation } from "@react-navigation/native";

interface HeaderProps {
  translucent?: boolean;
  backgroundColor?: string;
  title?: string;
  leftStyle?: ViewStyle;
  centerStyle?: ViewStyle;
  rightStyle?: ViewStyle;
  leftComponent?: React.ReactNode;
  centerComponent?: React.ReactNode;
  rightComponent?: React.ReactNode;
  onLeftPress: () => void;
  onRightPress: () => void;
  children?: React.ReactNode;
}

export default function Header(props: HeaderProps) {
  const navigation = useNavigation();
  const canGoBack = navigation.canGoBack();
  const {
    translucent = false,
    backgroundColor,
    title,
    leftComponent,
    centerComponent,
    rightComponent,
    onLeftPress,
    onRightPress,
  } = props;

  const mainStyle = {
    height: Platform.OS === "ios" ? 44 + StatusBarHeight : 44,
    marginTop: translucent ? StatusBar.currentHeight : 0,
  };

  return (
    <>
      <StatusBar
        barStyle={"dark-content"}
        translucent={translucent}
        backgroundColor={"transparent"}
      />
      <View
        style={{
          backgroundColor: backgroundColor,
        }}
      >
        <SafeAreaView style={mainStyle}>
          <View style={styles.container}>
            {leftComponent || (
              <TouchableWithoutFeedback onPress={onLeftPress}>
                <View style={[styles.leftStyle, props.leftStyle]}>
                  {canGoBack ? <Text>{"<"}</Text> : <Text>{"<"}</Text>}
                </View>
              </TouchableWithoutFeedback>
            )}
            {centerComponent || (
              <View style={[styles.centerStyle, props.centerStyle]}>
                <Text style={{ color: "#333" }}>{title}</Text>
              </View>
            )}
            {rightComponent || (
              <TouchableWithoutFeedback onPress={onRightPress}>
                <View style={[styles.rightStyle, props.centerStyle]} />
              </TouchableWithoutFeedback>
            )}
          </View>
        </SafeAreaView>
        {props.children}
      </View>
    </>
  );
}

Header.defaultProps = {
  title: "2222",
};

const styles = StyleSheet.create({
  container: {
    paddingHorizontal: 16,
    flexDirection: "row",
    alignItems: "center",
    height: 44,
  },
  leftStyle: {
    width: 50,
    flexDirection: "row",
    alignItems: "center",
    backgroundColor: "#aaa",
  },
  centerStyle: {
    flex: 1,
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "center",
    backgroundColor: "#bbb",
  },
  rightStyle: {
    width: 50,
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "flex-end",
    backgroundColor: "#ccc",
  },
});

utils/navbar

import {Dimensions, Platform, StatusBar} from 'react-native';


export const X_WIDTH = 375;
export const X_HEIGHT = 812;

export const X12_WIDTH = 390;
export const X12_HEIGHT = 844;

export const XSMAX_WIDTH = 414;
export const XSMAX_HEIGHT = 896;

export const PRO_MAX_12_WIDTH = 428;
export const PRO_MAX_12_HEIGHT = 926;

const {height, width} = Dimensions.get('window');

export const isIPhoneX = () =>
  Platform.OS === 'ios' && !Platform.isPad && !Platform.isTV
    ? (width === X_WIDTH && height === X_HEIGHT) ||
      (width === XSMAX_WIDTH && height === XSMAX_HEIGHT) ||
      (width === X12_WIDTH && height === X12_HEIGHT) ||
      (width === PRO_MAX_12_WIDTH && height === PRO_MAX_12_HEIGHT)
    : false;

export const StatusBarHeight = Platform.select({
  ios: isIPhoneX() ? 44 : 20,
  android: StatusBar.currentHeight,
  default: 0,
});

12.tabbar

function HomeTabs() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="Setting" component={SettingsScreen} />
    </Tab.Navigator>
  );
}

 <Stack.Screen name="Home" component={HomeTabs} />
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { Animated, Dimensions, Image, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import 'react-native-gesture-handler';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

// Plus...
import plus from './assets/plus.png'

// Font Awesome Icons...
import { FontAwesome5 } from '@expo/vector-icons'
import { useRef } from 'react';

const Tab = createBottomTabNavigator();

// Hiding Tab Names...
export default function App() {
  // Animated Tab Indicator...
  const tabOffsetValue = useRef(new Animated.Value(0)).current;
  return (
    <NavigationContainer>
      <Tab.Navigator tabBarOptions={{
        showLabel: false,
        // Floating Tab Bar...
        style: {
          backgroundColor: 'white',
          position: 'absolute',
          bottom: 40,
          marginHorizontal: 20,
          // Max Height...
          height: 60,
          borderRadius: 10,
          // Shadow...
          shadowColor: '#000',
          shadowOpacity: 0.06,
          shadowOffset: {
            width: 10,
            height: 10
          },
          paddingHorizontal: 20,
        }
      }}>

        {
          // Tab Screens....

          // Tab ICons....
        }
        <Tab.Screen name={"Home"} component={HomeScreen} options={{
          tabBarIcon: ({ focused }) => (
            <View style={{
              // centring Tab Button...
              position: 'absolute',
              top: 20
            }}>
              <FontAwesome5
                name="home"
                size={20}
                color={focused ? 'red' : 'gray'}
              ></FontAwesome5>
            </View>
          )
        }} listeners={({ navigation, route }) => ({
          // Onpress Update....
          tabPress: e => {
            Animated.spring(tabOffsetValue, {
              toValue: 0,
              useNativeDriver: true
            }).start();
          }
        })}></Tab.Screen>

        <Tab.Screen name={"Search"} component={SearchScreen} options={{
          tabBarIcon: ({ focused }) => (
            <View style={{
              // centring Tab Button...
              position: 'absolute',
              top: 20
            }}>
              <FontAwesome5
                name="search"
                size={20}
                color={focused ? 'red' : 'gray'}
              ></FontAwesome5>
            </View>
          )
        }} listeners={({ navigation, route }) => ({
          // Onpress Update....
          tabPress: e => {
            Animated.spring(tabOffsetValue, {
              toValue: getWidth(),
              useNativeDriver: true
            }).start();
          }
        })}></Tab.Screen>


        {

          // Extra Tab Screen For Action Button..
        }

        <Tab.Screen name={"ActionButton"} component={EmptyScreen} options={{
          tabBarIcon: ({ focused }) => (

            <TouchableOpacity>
              <View style={{
                width: 55,
                height: 55,
                backgroundColor: 'red',
                borderRadius: 30,
                justifyContent: 'center',
                alignItems: 'center',
                marginBottom: Platform.OS == "android" ? 50 : 30
              }}>
                <Image source={plus} style={{
                  width: 22,
                  height: 22,
                  tintColor: 'white',
                }}></Image>
              </View>
            </TouchableOpacity>
          )
        }}></Tab.Screen>

        <Tab.Screen name={"Notifications"} component={NotificationScreen} options={{
          tabBarIcon: ({ focused }) => (
            <View style={{
              // centring Tab Button...
              position: 'absolute',
              top: 20
            }}>
              <FontAwesome5
                name="bell"
                size={20}
                color={focused ? 'red' : 'gray'}
              ></FontAwesome5>
            </View>
          )
        }} listeners={({ navigation, route }) => ({
          // Onpress Update....
          tabPress: e => {
            Animated.spring(tabOffsetValue, {
              toValue: getWidth() * 3,
              useNativeDriver: true
            }).start();
          }
        })}></Tab.Screen>

        <Tab.Screen name={"Settings"} component={SettingsScreen} options={{
          tabBarIcon: ({ focused }) => (
            <View style={{
              // centring Tab Button...
              position: 'absolute',
              top: 20
            }}>
              <FontAwesome5
                name="user-alt"
                size={20}
                color={focused ? 'red' : 'gray'}
              ></FontAwesome5>
            </View>
          )
        }} listeners={({ navigation, route }) => ({
          // Onpress Update....
          tabPress: e => {
            Animated.spring(tabOffsetValue, {
              toValue: getWidth() * 4,
              useNativeDriver: true
            }).start();
          }
        })}></Tab.Screen>

      </Tab.Navigator>

      <Animated.View style={{
        width: getWidth() - 20,
        height: 2,
        backgroundColor: 'red',
        position: 'absolute',
        bottom: 98,
        // Horizontal Padding = 20...
        left: 50,
        borderRadius: 20,
        transform: [
          { translateX: tabOffsetValue }
        ]
      }}>

      </Animated.View>
    </NavigationContainer>
  );
}

function getWidth() {
  let width = Dimensions.get("window").width

  // Horizontal Padding = 20...
  width = width - 80

  // Total five Tabs...
  return width / 5
}

function EmptyScreen() {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
    </View>
  );
}

function SettingsScreen() {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Settings!</Text>
    </View>
  );
}

function HomeScreen() {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Home!</Text>
    </View>
  );
}

function NotificationScreen() {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Notifications!</Text>
    </View>
  );
}

function SearchScreen() {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Search!</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});
Contributors: masecho