Skip to main content

StickerSmash

跟官方教學做一次: https://docs.expo.dev/tutorial/introduction/

View completed code in Snack: https://snack.expo.dev/@expo-team-snacks/image-app

這個 app 有:

  • 選擇圖片

  • 加入 emoji

  • 觸摸手勢移動

  • 截圖

  • 儲存圖片

Create your first app

https://docs.expo.dev/tutorial/create-your-first-app/

npx create-expo-app@latest StickerSmash --template blank
cd StickerSmash
npx expo install react-dom react-native-web @expo/metro-runtime
npx expo start
# or
npx expo start --tunnel

Build a screen

https://docs.expo.dev/tutorial/build-a-screen/

npx expo install @expo/vector-icons
<View style={styles.xxx} ...

const styles = StyleSheet.create({
xxx: ...
})
<Image source={...}
<Pressable
style={[styles.xxx, { yyy: ... }]}
onPress={() => alert('You pressed a button.')}
>

Use an image picker

https://docs.expo.dev/tutorial/image-picker/

npx expo install expo-image-picker
import * as ImagePicker from 'expo-image-picker';

const pickImageAsync = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
quality: 1,
});

if (!result.canceled) {
setSelectedImage(result.assets[0].uri);
} else {
alert("You did not select any image.");
}
};

export default function ImageViewer({ placeholderImageSource, selectedImage }) {
const imageSource = selectedImage ? { uri: selectedImage } : placeholderImageSource;

return <Image source={imageSource} style={styles.image} />;
}

Create a modal

https://docs.expo.dev/tutorial/create-a-modal/

export default function EmojiPicker({ isVisible, children, onClose }) {
return (
<Modal animationType="slide" transparent={true} visible={isVisible}>

Add gestures

npx expo install react-native-gesture-handler react-native-reanimated
import { GestureHandlerRootView } from "react-native-gesture-handler";

<GestureHandlerRootView style={styles.container}>
import Animated from 'react-native-reanimated';

<Animated.Image
source={stickerSource}
resizeMode="contain"
style={{ width: imageSize, height: imageSize }}
/>
const scaleImage = useSharedValue(imageSize);
const doubleTap = Gesture.Tap()
.numberOfTaps(2)
.onStart(() => {
if (scaleImage.value !== imageSize * 2) {
scaleImage.value = scaleImage.value * 2;
}
});
const imageStyle = useAnimatedStyle(() => {
return {
width: withSpring(scaleImage.value),
height: withSpring(scaleImage.value),
};
});
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);

const drag = Gesture.Pan()
.onChange((event) => {
translateX.value += event.changeX;
translateY.value += event.changeY;
});

const containerStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateX: translateX.value,
},
{
translateY: translateY.value,
},
],
};
});

<GestureDetector gesture={drag}>
<Animated.View style={[containerStyle, { top: -350 }]}>
<GestureDetector gesture={doubleTap}>

Take a screenshot

https://docs.expo.dev/tutorial/screenshot/

npx expo install react-native-view-shot expo-media-library
import * as MediaLibrary from 'expo-media-library';

const [status, requestPermission] = MediaLibrary.usePermissions();

if (status === null) {
requestPermission();
}

https://github.com/gre/react-native-view-shot

const imageRef = useRef();

const onSaveImageAsync = async () => {
try {
const localUri = await captureRef(imageRef, {
height: 440,
quality: 1,
});

await MediaLibrary.saveToLibraryAsync(localUri);
if (localUri) {
alert("Saved!");
}
} catch (e) {
console.log(e);
}
};

<View ref={imageRef}

Handle platform differences

react-native-view-shot work in Android and iOS, but not in Web

Use dom-to-image for Web: https://github.com/tsayen/dom-to-image

npm install dom-to-image
const onSaveImageAsync = async () => {
if (Platform.OS !== 'web') {
...
} else {
try {
const dataUrl = await domtoimage.toJpeg(imageRef.current, {
quality: 0.95,
width: 320,
height: 440,
});

let link = document.createElement('a');
link.download = 'sticker-smash.jpeg';
link.href = dataUrl;
link.click();
} catch (e) {
console.log(e);
}
}

Configure status bar, splash screen and app icon

import { StatusBar } from 'expo-status-bar';

<StatusBar style="auto" />

app.json

{
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#25292e"
},
"icon": "./assets/images/icon.png"
}