import { ImageContentFit, ImageContentPosition, Image, ImageProps } from 'expo-image'; import React from 'react'; import { Dimensions, Image as RNImage, ScrollView, StyleSheet, Text, TextInput, View, } from 'react-native'; import { PanGestureHandler, PanGestureHandlerGestureEvent } from 'react-native-gesture-handler'; import Animated, { useAnimatedGestureHandler, useAnimatedProps, useAnimatedStyle, useDerivedValue, useSharedValue, } from 'react-native-reanimated'; import { FunctionParameter, useArguments } from '../../components/FunctionDemo'; import Configurator from '../../components/FunctionDemo/Configurator'; import { Colors } from '../../constants'; type CustomViewProps = React.PropsWithChildren; type ContextType = { x: number; y: number; }; const PADDING = 20; const HANDLE_SIZE = 25; const HANDLE_SLOP = 10; const WINDOW_DIMENSIONS = Dimensions.get('window'); const MAX_WIDTH = WINDOW_DIMENSIONS.width - 2 * PADDING; const MAX_HEIGHT = WINDOW_DIMENSIONS.height - 330; const ResizableView: React.FC = ({ children }) => { const width = useSharedValue(300); const height = useSharedValue(300); const panGestureEvent = useAnimatedGestureHandler({ onStart: (_, context) => { context.x = width.value; context.y = height.value; }, onActive: (event, context) => { width.value = Math.max(HANDLE_SIZE, Math.min(event.translationX + context.x, MAX_WIDTH)); height.value = Math.max(HANDLE_SIZE, Math.min(event.translationY + context.y, MAX_HEIGHT)); }, }); const canvasStyle = useAnimatedStyle(() => { return { width: width.value, height: height.value, }; }, [width, height]); const text = useDerivedValue(() => `${Math.round(width.value)}x${Math.round(height.value)}`); const animatedProps = useAnimatedProps(() => { return { text: text.value, // Here we use any because the text prop is not available in the type } as any; }); const AnimatedTextInput = Animated.createAnimatedComponent(TextInput); return ( Move the handle above to resize the image canvas and see how it lays out in different components, sizes and resize modes {children} ); }; const parameters: FunctionParameter[] = [ { name: 'Use React Native Image', type: 'boolean', initial: false, }, { name: 'Size', type: 'enum', values: [ { name: '1500x1000', value: '1500/1000' }, { name: '1000x1500', value: '1000/1500' }, { name: '300x300', value: '300/300' }, { name: '100x100', value: '100/100' }, ], }, { name: 'Content fit', type: 'enum', values: [ { name: 'cover', value: 'cover' }, { name: 'contain', value: 'contain' }, { name: 'fill', value: 'fill' }, { name: 'none', value: 'none' }, { name: 'scale-down', value: 'scale-down' }, ], }, { name: 'Content position', type: 'enum', values: [ { name: 'top 50%, left 50%', value: { top: '50%', left: '50%' } }, { name: 'top 0, right 0', value: { top: 0, right: 0 } }, { name: 'top 100, left 50', value: { top: 100, left: 50 } }, { name: 'bottom 10%, right 25%', value: { bottom: '10%', right: '25%' } }, { name: 'bottom 0, right 10', value: { bottom: 0, right: 10 } }, ], }, { name: 'Use responsive sources', type: 'boolean', initial: false, }, ]; function mapContentFitToResizeMode(contentFit: ImageContentFit): ImageProps['resizeMode'] { if (!contentFit) { return 'cover'; } switch (contentFit) { case 'cover': case 'contain': return contentFit; case 'fill': return 'stretch'; case 'none': case 'scale-down': return 'center'; } } export default function ImageResizableScreen() { const [seed] = React.useState(1 + Math.round(Math.random() * 10)); const [args, updateArgument] = useArguments(parameters); const [showReactNativeComponent, size, contentFit, contentPosition, useResponsiveSources] = args as [boolean, string, ImageContentFit, ImageContentPosition, boolean]; const ImageComponent: React.ElementType = showReactNativeComponent ? RNImage : Image; const source = useResponsiveSources ? [ { uri: `https://picsum.photos/id/238/800/800`, width: 800, height: 800 }, { uri: `https://picsum.photos/id/237/500/500`, width: 500, height: 500 }, { uri: `https://picsum.photos/id/236/300/300`, width: 300, height: 300 }, ] : { uri: `https://picsum.photos/seed/${seed}/${size}` }; return ( ); } const styles = StyleSheet.create({ container: { flex: 1, }, resizableView: { margin: PADDING, width: MAX_WIDTH, height: MAX_HEIGHT, borderWidth: 1, borderColor: Colors.border, backgroundColor: '#eef', }, configurator: { flex: 1, paddingHorizontal: 15, }, canvas: { margin: -1, minWidth: HANDLE_SIZE, minHeight: HANDLE_SIZE, maxWidth: MAX_WIDTH, maxHeight: MAX_HEIGHT, backgroundColor: '#00f2', borderWidth: 2, borderStyle: 'dotted', borderColor: Colors.tintColor, borderRadius: 3, }, resizeHandle: { padding: HANDLE_SLOP, position: 'absolute', bottom: -HANDLE_SIZE / 2 - HANDLE_SLOP, right: -HANDLE_SIZE / 2 - HANDLE_SLOP, }, resizeHandleChild: { width: HANDLE_SIZE, height: HANDLE_SIZE, borderRadius: HANDLE_SIZE, borderWidth: 3, borderColor: Colors.tintColor, backgroundColor: '#fff', }, hintText: { color: Colors.secondaryText, textAlign: 'center', position: 'absolute', right: 10, left: 10, bottom: 16, }, sizeText: { position: 'absolute', zIndex: 1, top: -PADDING + 8, right: PADDING - 4, color: Colors.secondaryText, }, image: { flex: 1, }, });