在 React Native 中用 Expo Camera 和 MaskedView 实现抠图相机效果
了解如何使用 Expo Camera 打造模板镂空效果,把实时画面变成磨砂玻璃背景。
中文
复制

本文是 Mehdi Davoodi 的客座文章——他做 React Native 和 Expo 的 UI 实验,并在 𝕏 上分享自己的心得:@mehdi_made
...
我想做点东西,让相机画面透过文字和 UI 元素显示出来,做出一种镂空的效果。思路很简单:把实时相机画面当作动态背景,但只让它从 UI 的特定部分透出来。
下面就是我用 Expo Camera、MaskedView 和 Expo Blur 实现的方法。
我们要做的东西
本质上就是一个显示用户列表的界面。但背景不是纯色,而是让相机画面透过文字和头像显示出来。上面再叠一层模糊,做出磨砂玻璃的质感。整体效果就是一个有生命力、独特、让人记得住的 UI。
几个关键部分:
-
expo-camera负责实时相机画面 -
expo-blur提供磨砂玻璃层
配置 Expo Camera
先安装这个包:
npx expo install expo-camera
expo-camera 这个包主要给你两样东西:一个 CameraView 组件和一个 useCameraPermissions hook。hook 才是重点;不先申请权限,你没法直接渲染相机。
基本配置如下:
import { CameraView, useCameraPermissions } from 'expo-camera';
const App = () => {
const [permission, requestPermission] = useCameraPermissions();
if (!permission) {
// Still loading
return <View />;
}
if (!permission.granted) {
// Need to ask
return (
<Button title="Grant Access" onPress={requestPermission} />
);
}
return <CameraView style={{ flex: 1 }} />;
};
这是最低限度的写法。但有个问题:权限对象的状态不止「已授权」和「未授权」两种。
理解相机权限
来自 useCameraPermissions 的 permission 对象有两个重要属性:
-
granted——你是否拥有权限 -
canAskAgain——你是否还能再次向用户弹出申请
这样就有了四种实际状态:
permission 为 null → 还在检查中
permission.granted 为 true → 相机已就绪
permission.granted 为 false 且 canAskAgain 为 true → 用户拒绝了,但你可以再问一次
permission.granted 为 false 且 canAskAgain 为 false → 用户在设置里彻底禁掉了
最后一种很关键。如果用户拒绝了权限并勾选了「不再询问」,你就没法再弹出申请了。你只能让他们自己去设置里手动开启。
我把这套逻辑封装进了一个 hook:
import { useEffect, useCallback } from 'react';
import { useCameraPermissions } from 'expo-camera';
export const useCamera = (autoRequest = true) => {
const [permission, requestCameraPermission] = useCameraPermissions();
const getStatus = () => {
if (!permission) return 'loading';
if (permission.granted) return 'granted';
if (!permission.canAskAgain) return 'blocked';
return 'denied';
};
const status = getStatus();
const requestPermission = useCallback(async () => {
try {
await requestCameraPermission();
} catch (error) {
console.error('Failed to request camera permission:', error);
}
}, [requestCameraPermission]);
// Auto-request on mount
useEffect(() => {
if (autoRequest && status === 'denied') {
requestPermission();
}
}, [autoRequest, status, requestPermission]);
return {
status,
isReady: status === 'granted',
requestPermission,
};
};
现在我的组件只要检查 isReady 就行,不用操心那些细节。
和 MaskedView 结合
MaskedView 是一个组件,可以让你拿另一个组件当遮罩。把它想成一块模板——你抠出一些形状,后面的东西就从这些形状里透出来。
工作原理:
MaskedView 由两部分组成:
maskElement —— 模板(定义形状的部分)
children —— 透过模板显示的内容

遮罩依据不透明度工作。遮罩中完全不透明的部分(比如黑色)会显示子视图,透明的部分则将其隐藏。灰色值对应部分透明。
import MaskedView from '@react-native-masked-view/masked-view';
<MaskedView
maskElement={
<View style={{ backgroundColor: 'transparent', flex: 1 }}>
<Text style={{ color: 'black', fontSize: 60 }}>Hello</Text>
</View>
}
>
{/* This is what shows through */}
<View style={{ flex: 1, backgroundColor: 'blue' }} />
</MaskedView>
配合摄像头使用
有趣的地方来了。遮罩背后不再是纯色,而是实时摄像头画面:
<MaskedView
maskElement={
<Text style={{ color: 'black', fontSize: 40 }}>Hello</Text>
}
>
<CameraView style={{ flex: 1 }} />
</MaskedView>
摄像头画面只从文字中透出,其余部分都是透明的。
添加模糊效果
我想要毛玻璃质感,而不是原始的摄像头画面。于是我把 BlurView 从 expo-blur 叠在摄像头之上:
<MaskedView
style={{ flex: 1 }}
maskElement={<MaskContent />}
>
<BlurView style={StyleSheet.absoluteFillObject} intensity={100} />
<CameraView style={StyleSheet.absoluteFillObject} />
</MaskedView>
模糊层位于摄像头之上(通过 zIndex 或渲染顺序实现),遮罩则同时穿透两者。所以你透过遮罩形状看到的是模糊后的摄像头画面。

这里是仓库地址。希望对你有所帮助。如果你真的基于这篇博客里学到的东西做出了什么,我很乐意看到你分享给我。
另外,如果你有其他想让我动手实现并分享的 UI 实验,欢迎私信告诉我。感谢阅读!