react native 开发
1.style
StyleSheet
来定义样式
1.1 使用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 国际化
安装i18next
与react-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.生成二维码
使用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" />;
}
react-native-splash-screen
7.启动时闪屏安装:
yarn add react-native-splash-screen
navigation
8.路由基本路由:
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" },
},
],
})
);
SSE
9.服务端事件服务端: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/28835
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 动画
安装 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 国际化
安装i18next
与react-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.生成二维码
使用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" />;
}
react-native-splash-screen
7.启动时闪屏安装:
yarn add react-native-splash-screen
navigation
8.路由基本路由:
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" },
},
],
})
);
SSE
9.服务端事件服务端: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/28835
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 动画
安装 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',
},
});