Commit 9052caa2 by tungnq

IMPORTANT: FAB di chuyển được

parent faec694d
...@@ -16,6 +16,8 @@ export const FAB_BACKGROUND_COLOR = '#2F6BFF'; ...@@ -16,6 +16,8 @@ export const FAB_BACKGROUND_COLOR = '#2F6BFF';
// Khoảng cách margin của FAB với viền màn hình // Khoảng cách margin của FAB với viền màn hình
export const FAB_MARGIN = 10; export const FAB_MARGIN = 10;
export const LEFT_EDGE_SPACING = 110;
// -------- Trạng thái khi mở FAB -------- // -------- Trạng thái khi mở FAB --------
......
import React, {useState, useEffect} from 'react'; import React from 'react';
import { import { StyleSheet, useWindowDimensions } from 'react-native';
StyleSheet, import { PanGestureHandler } from 'react-native-gesture-handler';
useWindowDimensions,
DeviceEventEmitter,
} from 'react-native';
import {
FAB_BACKGROUND_COLOR,
FAB_BORDER_RADIUS,
FAB_HEIGHT,
FAB_WIDTH,
FAB_MARGIN,
FAB_CHILDREN_OPACITY_OPEN,
FAB_CHILDREN_POSITION_Y_OPEN,
FAB_CHILDREN_OPACITY_CLOSE,
FAB_CHILDREN_POSITION_Y_CLOSE,
FAB_ROTATION_CLOSE,
FAB_ROTATION_OPEN,
FAB_STARTING_POSITION,
FAB_PLUS_TRANSLATE_Y_OPEN,
FAB_PLUS_TRANSLATE_Y_CLOSE,
SUBBTN_TAP_EVENT,
} from './constants';
import {
PanGestureHandler,
State,
TapGestureHandler,
} from 'react-native-gesture-handler';
import Animated, { import Animated, {
useAnimatedGestureHandler, useAnimatedGestureHandler,
useAnimatedStyle, useAnimatedStyle,
useSharedValue, useSharedValue,
withSpring, withSpring,
withTiming,
} from 'react-native-reanimated'; } from 'react-native-reanimated';
const FAB = props => { import {
// State: quản lý trạng thái mở/đóng của FAB FAB_BORDER_RADIUS,
const [opened, setOpened] = useState(false); FAB_WIDTH,
FAB_MARGIN,
FAB_STARTING_POSITION,
LEFT_EDGE_SPACING,
} from './constants';
// Lấy kích thước màn hình hiện tại (để xử lý drag sang trái/phải) const FAB = (props) => {
const { width } = useWindowDimensions(); const { width } = useWindowDimensions();
// Nhận children (SubButton) truyền vào component
const { children } = props; const { children } = props;
/** // Vị trí drag
* Các biến dùng chung để animate:
* - fabPositionX, fabPositionY: vị trí FAB khi kéo (drag)
* - fabRotation: góc xoay của FAB (+ thành x)
* - fabPlusTranslateY: chỉnh lại vị trí của dấu "+"
*/
const fabPositionX = useSharedValue(0); const fabPositionX = useSharedValue(0);
const fabPositionY = useSharedValue(0); const fabPositionY = useSharedValue(0);
const fabRotation = useSharedValue(FAB_ROTATION_CLOSE);
const fabPlusTranslateY = useSharedValue(FAB_PLUS_TRANSLATE_Y_CLOSE);
/** // Kéo & snap trái/phải (giữ nguyên logic bạn đang dùng)
* Quản lý animation cho view chứa SubButton(s)
* - childrenYPosition: vị trí Y khi show/hide
* - childrenOpacity: độ mờ khi show/hide
*/
const childrenYPosition = useSharedValue(FAB_CHILDREN_POSITION_Y_CLOSE);
const childrenOpacity = useSharedValue(FAB_CHILDREN_OPACITY_CLOSE);
// Khi tap vào FAB thì toggle mở/đóng
const _onTapHandlerStateChange = ({nativeEvent}) => {
if (nativeEvent.state === State.END) {
opened ? _close() : _open();
}
};
// Hàm mở FAB -> show sub button với animation
function _open() {
setOpened(true);
childrenOpacity.value = withTiming(FAB_CHILDREN_OPACITY_OPEN, {
duration: 300,
});
childrenYPosition.value = withTiming(FAB_CHILDREN_POSITION_Y_OPEN, {
duration: 200,
});
fabRotation.value = withSpring(FAB_ROTATION_OPEN);
fabPlusTranslateY.value = withSpring(FAB_PLUS_TRANSLATE_Y_OPEN);
}
// Hàm đóng FAB -> ẩn sub button với animation
function _close() {
childrenOpacity.value = withTiming(FAB_CHILDREN_OPACITY_CLOSE, {
duration: 300,
});
childrenYPosition.value = withTiming(FAB_CHILDREN_POSITION_Y_CLOSE, {
duration: 300,
});
fabRotation.value = withSpring(FAB_ROTATION_CLOSE);
fabPlusTranslateY.value = withSpring(FAB_PLUS_TRANSLATE_Y_CLOSE);
// delay để chờ animation chạy xong rồi mới setOpened(false)
setTimeout(() => {
setOpened(false);
}, 300);
}
/**
* useEffect: đăng ký listener SUBBTN_TAP_EVENT
* Khi nhấn SubButton -> tự động đóng FAB
* cleanup: gỡ listener khi unmount
*/
useEffect(() => {
let listener = DeviceEventEmitter.addListener(SUBBTN_TAP_EVENT, () => {
_close();
});
return () => listener.remove();
}, []);
/**
* Xử lý gesture kéo thả FAB
* - onStart: lưu vị trí ban đầu
* - onActive: update vị trí khi kéo
* - onEnd: snap FAB về trái/phải màn hình
*/
const _onPanHandlerStateChange = useAnimatedGestureHandler({ const _onPanHandlerStateChange = useAnimatedGestureHandler({
onStart: (_, ctx) => { onStart: (_, ctx) => {
ctx.startX = fabPositionX.value; ctx.startX = fabPositionX.value;
ctx.startY = fabPositionY.value; ctx.startY = fabPositionY.value;
}, },
onActive: (event, ctx) => { onActive: (event, ctx) => {
fabPositionX.value = ctx.startX + event.translationX; fabPositionX.value = ctx.startX + event.translationX;
fabPositionY.value = ctx.startY + event.translationY; fabPositionY.value = ctx.startY + event.translationY;
}, },
onEnd: _ => { onEnd: _ => {
if (fabPositionX.value > -width / 2) { const SNAP_RIGHT = FAB_STARTING_POSITION; // 0
fabPositionX.value = withSpring(FAB_STARTING_POSITION); const SNAP_LEFT = -width + FAB_WIDTH + FAB_MARGIN * 2 + LEFT_EDGE_SPACING;
fabPositionY.value = withSpring(FAB_STARTING_POSITION);
} else {
fabPositionX.value = withSpring(-width + FAB_WIDTH + FAB_MARGIN * 2);
fabPositionY.value = withSpring(FAB_STARTING_POSITION);
}
},
});
// Style động cho FAB khi drag fabPositionX.value =
const animatedRootStyles = useAnimatedStyle(() => { Math.abs(fabPositionX.value - SNAP_LEFT) < Math.abs(fabPositionX.value - SNAP_RIGHT)
return { ? withSpring(SNAP_LEFT)
transform: [ : withSpring(SNAP_RIGHT);
{ translateX: fabPositionX.value },
{ translateY: fabPositionY.value },
],
};
});
// Style động cho children (ẩn/hiện + trượt lên/xuống) // nếu muốn luôn bám dưới, có thể reset Y về 0:
const animatedChildrenStyles = useAnimatedStyle(() => { // fabPositionY.value = withSpring(FAB_STARTING_POSITION);
return { },
opacity: childrenOpacity.value,
transform: [{ translateY: childrenYPosition.value }],
};
});
// Style động cho FAB (xoay dấu +)
const animatedFABStyles = useAnimatedStyle(() => {
return {
transform: [{ rotate: `${fabRotation.value}deg` }],
};
}); });
// Style động cho dấu "+" (chỉnh vị trí Y) const animatedRootStyles = useAnimatedStyle(() => ({
const animatedPlusText = useAnimatedStyle(() => { transform: [
return { { translateX: fabPositionX.value },
transform: [{ translateY: fabPlusTranslateY.value }], { translateY: fabPositionY.value },
}; ],
}); }));
return ( return (
<PanGestureHandler onHandlerStateChange={_onPanHandlerStateChange}> <PanGestureHandler onHandlerStateChange={_onPanHandlerStateChange}>
<Animated.View style={[styles.rootStyles, animatedRootStyles]}> <Animated.View style={[styles.rootStyles, animatedRootStyles]}>
{/* Hiện sub button khi opened = true */} {/* Luôn hiển thị SubButton */}
{opened && ( <Animated.View style={styles.childrenStyles}>
<Animated.View {children}
style={[styles.childrenStyles, animatedChildrenStyles]}> </Animated.View>
{children}
</Animated.View>
)}
{/* FAB chính (nút tròn màu xanh + dấu cộng) */}
<TapGestureHandler onHandlerStateChange={_onTapHandlerStateChange}>
<Animated.View style={[styles.fabButtonStyles, animatedFABStyles]}>
<Animated.Text style={[styles.plus, animatedPlusText]}>
+
</Animated.Text>
</Animated.View>
</TapGestureHandler>
</Animated.View> </Animated.View>
</PanGestureHandler> </PanGestureHandler>
); );
...@@ -198,33 +69,18 @@ const FAB = props => { ...@@ -198,33 +69,18 @@ const FAB = props => {
export default FAB; export default FAB;
// Style cho các phần tử trong FAB
const styles = StyleSheet.create({ const styles = StyleSheet.create({
// Vị trí gốc của FAB (bottom-right) // Neo dưới-phải
rootStyles: { rootStyles: {
borderRadius: FAB_BORDER_RADIUS,
position: 'absolute', position: 'absolute',
bottom: FAB_MARGIN, bottom: 30,
right: FAB_MARGIN, right: 0,
},
// Style cho nút FAB chính
fabButtonStyles: {
alignItems: 'center',
justifyContent: 'center',
backgroundColor: FAB_BACKGROUND_COLOR,
width: FAB_WIDTH,
height: FAB_HEIGHT,
borderRadius: FAB_BORDER_RADIUS, borderRadius: FAB_BORDER_RADIUS,
}, },
// Style cho container của SubButton // Container SubButton (xếp dọc, lệch phải)
childrenStyles: { childrenStyles: {
width: FAB_WIDTH, width: FAB_WIDTH,
alignItems: 'center', alignItems: 'flex-end',
marginBottom: 20, marginBottom: 10,
},
// Style cho dấu "+"
plus: {
fontSize: 20,
color: '#EFFBFA',
}, },
}); });
...@@ -149,6 +149,7 @@ const DetailIncomingDocumentView = props => { ...@@ -149,6 +149,7 @@ const DetailIncomingDocumentView = props => {
<SubButton onPress={() => Alert.alert('Pressed 1!')} label="Thêm bút phê" images={R.images.icEdit} backgroundColor={R.colors.orange}/> <SubButton onPress={() => Alert.alert('Pressed 1!')} label="Thêm bút phê" images={R.images.icEdit} backgroundColor={R.colors.orange}/>
<SubButton onPress={() => Alert.alert('Pressed 2!')} label="Tạo công việc" images={R.images.icMenuEdit} backgroundColor={R.colors.blue}/> <SubButton onPress={() => Alert.alert('Pressed 2!')} label="Tạo công việc" images={R.images.icMenuEdit} backgroundColor={R.colors.blue}/>
</FAB> </FAB>
</View> </View>
</ScrollView> </ScrollView>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment