FloatingActionButton

화면위에 떠있는 버튼

FAB 버튼이라고 한다.

아래에 추가하여 버튼형태로 만드는 것임.

거의 커스터마이징을 하나씩 해주는데, 이거 그냥 이미 있는거 쓰면 안되나 하는생각이 강하게 든다.

import {
	Pressable,
	StyleSheet,
	Text,
	TextInput,
	View,
	useWindowDimensions,
} from 'react-native';
import PropTypes from 'prop-types';
import { PRIMARY, WHITE } from '../colors';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { useRef, useState } from 'react';

const InputFAB = () => {
	const [text, setText] = useState('');
	const [isOpened, setIsOpened] = useState(false);
	const inputRef = useRef(null);
	const windowWidth = useWindowDimensions().width;

	const open = () => {
		setIsOpened(true);
		inputRef.current.focus();
	};

	const close = () => {
		setIsOpened(false);
		inputRef.current.blur();
	};

	const onPressButton = () => (isOpened ? close() : open());
	return (
		<>
			<View
				style={[
					styles.container,
					isOpened && { width: windowWidth - 20 },
				]}
			>
				<TextInput
					ref={inputRef}
					value={text}
					onChangeText={setText}
					style={styles.input}
					autoCapitalize="none"
					autoCorrect={false}
					textContentType="none"
					keyboardAppearance="light"
					returnKeyType="done"
					onBlur={close}
				/>
			</View>
			<Pressable
				style={({ pressed }) => [
					styles.container,
					pressed && { backgroundColor: PRIMARY.DARK },
				]}
				onPress={onPressButton}
			>
				<MaterialCommunityIcons name="plus" size={24} color={WHITE} />
			</Pressable>
		</>
	);
};

const styles = StyleSheet.create({
	container: {
		position: 'absolute',
		bottom: 30,
		right: 10,
		width: 60,
		height: 60,
		borderRadius: 30,
		backgroundColor: PRIMARY.DEFAULT,
		justifyContent: 'center',
		alignItems: 'center',
	},
	input: {
		color: WHITE,
		paddingLeft: 2,
		paddingRight: 70,
	},
});
export default InputFAB;

Keyboard Add Listener

다만 위와 같이 했을 때 키보드가 버튼을 가려버림(Ios의 경우만)

useEffect(() => {
	Keyboard.addListener('keyboardWillShow', e => {
		console.log(';show:', e);
	});
}, []);

위와 같이 사용할 시, 이벤트 리스너를 추가해주는 것이므로 로그아웃 후에 다시 들어온다고 가정하면 그 때마다 이벤트 리스너가 추가되어 반복해서 실행해주는 것을 볼 수 있음.

그렇게 되면 안되기 때문에 정리함수라는 것을 써준다.

이벤트 리스너 마지막에

return () => {};

이렇게만 넣어주면 된다.

만약에 useEffect 안에 상태변수가 있을 시, 그 상태변수가 바뀌는 것을 따라서, 정리, 다시 실행 되는 식으로 동작함을 알 수 있다.

useEffect(() => {
	const show = Keyboard.addListener('keyboardWillShow', e => {
		console.log(';show:', e);
	});

	return () => {
		show.remove();
	};
}, []);

결론적으로 위와 같이 해주면 이벤트를 제거해줄 수 있다.

그림자 만들기

그림자 설정하는 방법

  • shadowColor
  • shadowOffset (ios)
  • shadowOpacity (ios)
  • shadowRadius (ios)

플랫폼에 제한이 됨

Android에서는 elevation 이라는 애를 사용해야함.

애니메이션 만들기

Animated 를 사용하면 됨 value는 useRef 를 사용해서

const inputWidth = useRef(new Animated.Value(BUTTON_WIDTH)).current;

위와 같이 사용해주면 됨

그 다음 View 컴포넌트를

Animated.View 로 변경해주면 됨.

timing이라는 함숨를 통해서 시간을 정할 수 있음.

open, close 함수 코딩

const open = () => {
	setIsOpened(true);
	Animated.timing(inputWidth, {
		toValue: windowWidth - 20,
		useNativeDriver: false,
		duration: 300,
	}).start(() => {
		inputRef.current.focus();
	});
};

const close = () => {
	setIsOpened(false);
	Animated.timing(inputWidth, {
		toValue: BUTTON_WIDTH,
		useNativeDriver: false,
		duration: 300,
	}).start(() => {
		inputRef.current.blur();
	});
};

spring을 써서 버튼 돌리기

  • 버튼을 돌려서 X 로 만들어보겠다.
const buttonRotation = useRef(new Animated.Value('0deg')).current;

그러나 Animated에는 문자가 올수 없음 그러므로 interpolate 를 써야 함.

buttonRotation.interpolate({
	inputRange: [0, 1],
	outputRange: [0, 10],
});

위와 같이 Value가 0이면 0 Value가 1이면 10으로 매칭 시켜주는 것임.

결국 아래와 같이 작업

const buttonRotation = useRef(new Animated.Value(0)).current;
buttonRotation.interpolate({
	inputRange: [0, 1],
	outputRange: ['0deg', '315deg'],
});