Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
A
AppUms_Lecturer
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
tungnq
AppUms_Lecturer
Commits
faec694d
Commit
faec694d
authored
Aug 27, 2025
by
tungnq
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
TODOL Điều chỉnh giao diện FAB
parent
379de4f5
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
115 additions
and
64 deletions
+115
-64
menuEdit.png
src/assets/icon/icon_png/menuEdit.png
+0
-0
images.js
src/assets/images.js
+1
-0
constants.js
src/components/FAB/constants.js
+47
-5
fab.js
src/components/FAB/fab.js
+37
-43
sub_button.js
src/components/FAB/sub_button.js
+28
-12
view.js
src/screens/incoming_document/detail/view.js
+2
-4
No files found.
src/assets/icon/icon_png/menuEdit.png
0 → 100644
View file @
faec694d
664 Bytes
src/assets/images.js
View file @
faec694d
...
...
@@ -8,6 +8,7 @@ const images = {
bg_cannot_connect
:
require
(
'./images/bg_cannot_connect.png'
),
icGallery
:
require
(
'./images/icGallery.png'
),
icNoData
:
require
(
'./icon/icon_png/icon_no_data.png'
),
icMenuEdit
:
require
(
'./icon/icon_png/menuEdit.png'
),
//HomeScreen
icLichDay
:
require
(
'./icon/lich_day.png'
),
icBaoBu
:
require
(
'./icon/bao_bu.png'
),
...
...
src/components/FAB/constants.js
View file @
faec694d
// Vị trí bắt đầu của FAB (Floating Action Button)
export
const
FAB_STARTING_POSITION
=
0
;
export
const
FAB_WIDTH
=
60
;
// Kích thước chiều rộng của nút FAB
export
const
FAB_WIDTH
=
50
;
// Kích thước chiều cao của nút FAB (bằng với chiều rộng để tạo nút tròn)
export
const
FAB_HEIGHT
=
FAB_WIDTH
;
export
const
FAB_BORDER_RADIUS
=
FAB_WIDTH
/
2
;
export
const
FAB_BACKGROUND_COLOR
=
'#2EC4B6'
;
export
const
FAB_MARGIN
=
30
;
// Bán kính bo tròn (FAB tròn hoàn hảo)
export
const
FAB_BORDER_RADIUS
=
FAB_WIDTH
/
2
;
// Màu nền mặc định của FAB
export
const
FAB_BACKGROUND_COLOR
=
'#2F6BFF'
;
// Khoảng cách margin của FAB với viền màn hình
export
const
FAB_MARGIN
=
10
;
// -------- Trạng thái khi mở FAB --------
// Góc xoay của biểu tượng FAB khi mở (xoay 225 độ, thường là dấu "+" xoay thành "x")
export
const
FAB_ROTATION_OPEN
=
225
;
// Độ mờ của các nút con khi FAB mở (1 = hiện rõ)
export
const
FAB_CHILDREN_OPACITY_OPEN
=
1
;
// Vị trí Y của các nút con khi mở (0 = sát với FAB chính)
export
const
FAB_CHILDREN_POSITION_Y_OPEN
=
0
;
// Độ dịch chuyển Y của dấu "+" khi FAB mở (để canh giữa khi xoay)
export
const
FAB_PLUS_TRANSLATE_Y_OPEN
=
-
3
;
// -------- Trạng thái khi đóng FAB --------
// Độ mờ của các nút con khi FAB đóng (0 = ẩn hoàn toàn)
export
const
FAB_CHILDREN_OPACITY_CLOSE
=
0
;
// Vị trí Y của các nút con khi đóng (cách FAB chính 30px)
export
const
FAB_CHILDREN_POSITION_Y_CLOSE
=
30
;
// Góc xoay của FAB khi đóng (0 độ = trạng thái ban đầu)
export
const
FAB_ROTATION_CLOSE
=
0
;
// Độ dịch chuyển Y của dấu "+" khi FAB đóng
export
const
FAB_PLUS_TRANSLATE_Y_CLOSE
=
-
2
;
// -------- Cấu hình nút con (sub button) --------
// Chiều rộng nút con
export
const
SUBBTN_WIDTH
=
FAB_WIDTH
;
// Chiều cao nút con (bằng chiều rộng để tạo hình tròn)
export
const
SUBBTN_HEIGHT
=
SUBBTN_WIDTH
;
// Bán kính bo tròn (nút con hình tròn)
export
const
SUBBTN_BORDER_RADIUS
=
SUBBTN_WIDTH
/
2
;
export
const
SUBBTN_BACKGROUND_COLOR
=
'#1B746B'
;
// Màu nền mặc định của nút con
export
const
SUBBTN_BACKGROUND_COLOR
=
'#2F6BFF'
;
// Tên sự kiện khi nhấn vào nút con
export
const
SUBBTN_TAP_EVENT
=
'SUBBTN_TAP_EVENT'
;
src/components/FAB/fab.js
View file @
faec694d
...
...
@@ -38,23 +38,20 @@ import Animated, {
}
from
'react-native-reanimated'
;
const
FAB
=
props
=>
{
// State: quản lý trạng thái mở/đóng của FAB
const
[
opened
,
setOpened
]
=
useState
(
false
);
// Lấy kích thước màn hình hiện tại (để xử lý drag sang trái/phải)
const
{
width
}
=
useWindowDimensions
();
/**
* Destructure the children prop for the SubButton(s)
*/
// Nhận children (SubButton) truyền vào component
const
{
children
}
=
props
;
/**
* (X,Y) position of the FAB. We use these
* for keeping track of the button when dragging it.
*
* We also rotate the button to change the + to a x
* when the children view is visible. The plus text is
* also offset to accomodate for the anchor point of the
* rotation not being in the center of the +
* 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
fabPositionY
=
useSharedValue
(
0
);
...
...
@@ -62,18 +59,21 @@ const FAB = props => {
const
fabPlusTranslateY
=
useSharedValue
(
FAB_PLUS_TRANSLATE_Y_CLOSE
);
/**
*
The opacity and Y position of the children container for the
*
SubButton(s). We use this to show a sliding fade in/out animation when
*
the user taps the FAB button
*
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
,
{
...
...
@@ -86,14 +86,7 @@ const FAB = props => {
fabPlusTranslateY
.
value
=
withSpring
(
FAB_PLUS_TRANSLATE_Y_OPEN
);
}
/**
* Method called when we want to hide the SubButton(s)
*
* This is essentially the same function as _open(), but in reverse.
* However, we need to delay setting the opened state to false to let
* the closing animation play out because as soon as we set that state
* to false, the component will unmount.
*/
// Hàm đóng FAB -> ẩn sub button với animation
function
_close
()
{
childrenOpacity
.
value
=
withTiming
(
FAB_CHILDREN_OPACITY_CLOSE
,
{
duration
:
300
,
...
...
@@ -103,17 +96,16 @@ const FAB = props => {
});
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
);
}
/**
* A useEffect (componentDidMount) that adds an
* event listener to the FAB so when we tap any SubButton we close
* the SubButton container.
*
* The return statement (componentWillUnmount) removes the listener.
* 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
,
()
=>
{
...
...
@@ -122,6 +114,12 @@ const FAB = props => {
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
({
onStart
:
(
_
,
ctx
)
=>
{
ctx
.
startX
=
fabPositionX
.
value
;
...
...
@@ -142,6 +140,7 @@ const FAB = props => {
},
});
// Style động cho FAB khi drag
const
animatedRootStyles
=
useAnimatedStyle
(()
=>
{
return
{
transform
:
[
...
...
@@ -151,11 +150,7 @@ const FAB = props => {
};
});
/**
* The animated styles hook that is used in the
* style prop for the children view. The opacity of the children
* and the y-position update depending on the shared values used.
*/
// Style động cho children (ẩn/hiện + trượt lên/xuống)
const
animatedChildrenStyles
=
useAnimatedStyle
(()
=>
{
return
{
opacity
:
childrenOpacity
.
value
,
...
...
@@ -163,23 +158,14 @@ const FAB = props => {
};
});
/**
* The animated styles hook that is used in the
* style prop for the FAB. It updates the rotation value
* when the fabRotation shared value is changed.
*/
// Style động cho FAB (xoay dấu +)
const
animatedFABStyles
=
useAnimatedStyle
(()
=>
{
return
{
transform
:
[{
rotate
:
`
${
fabRotation
.
value
}
deg`
}],
};
});
/**
* The animated styles hook that is used in the
* style prop for the plus text in the FAB. It update
* the y-position for the text when the fabPlusTranslatey shared value
* is changed.
*/
// Style động cho dấu "+" (chỉnh vị trí Y)
const
animatedPlusText
=
useAnimatedStyle
(()
=>
{
return
{
transform
:
[{
translateY
:
fabPlusTranslateY
.
value
}],
...
...
@@ -189,12 +175,15 @@ const FAB = props => {
return
(
<
PanGestureHandler
onHandlerStateChange
=
{
_onPanHandlerStateChange
}
>
<
Animated
.
View
style
=
{[
styles
.
rootStyles
,
animatedRootStyles
]}
>
{
/* Hiện sub button khi opened = true */
}
{
opened
&&
(
<
Animated
.
View
style
=
{[
styles
.
childrenStyles
,
animatedChildrenStyles
]}
>
{
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
]}
>
...
...
@@ -209,13 +198,16 @@ const FAB = props => {
export
default
FAB
;
// Style cho các phần tử trong FAB
const
styles
=
StyleSheet
.
create
({
// Vị trí gốc của FAB (bottom-right)
rootStyles
:
{
borderRadius
:
FAB_BORDER_RADIUS
,
position
:
'absolute'
,
bottom
:
FAB_MARGIN
,
right
:
FAB_MARGIN
,
},
// Style cho nút FAB chính
fabButtonStyles
:
{
alignItems
:
'center'
,
justifyContent
:
'center'
,
...
...
@@ -224,13 +216,15 @@ const styles = StyleSheet.create({
height
:
FAB_HEIGHT
,
borderRadius
:
FAB_BORDER_RADIUS
,
},
// Style cho container của SubButton
childrenStyles
:
{
width
:
FAB_WIDTH
,
alignItems
:
'center'
,
marginBottom
:
20
,
},
// Style cho dấu "+"
plus
:
{
fontSize
:
36
,
fontSize
:
20
,
color
:
'#EFFBFA'
,
},
});
src/components/FAB/sub_button.js
View file @
faec694d
import
React
from
'react'
;
import
{
DeviceEventEmitter
,
StyleSheet
,
Text
}
from
'react-native'
;
import
{
DeviceEventEmitter
,
Image
,
StyleSheet
,
Text
}
from
'react-native'
;
import
{
TapGestureHandler
,
State
}
from
'react-native-gesture-handler'
;
import
Animated
,
{
...
...
@@ -15,29 +15,41 @@ import {
SUBBTN_WIDTH
,
SUBBTN_TAP_EVENT
,
}
from
'./constants'
;
import
R
from
'../../assets/R'
;
// Component SubButton (nút con của FAB)
const
SubButton
=
props
=>
{
const
{
label
,
onPress
}
=
props
;
const
{
label
,
onPress
,
images
,
backgroundColor
}
=
props
;
// Biến sharedValue để quản lý độ mờ (opacity) khi nhấn giữ
const
buttonOpacity
=
useSharedValue
(
1
);
// Style động: cập nhật opacity theo buttonOpacity
const
animatedStyles
=
useAnimatedStyle
(()
=>
{
return
{
opacity
:
buttonOpacity
.
value
,
};
});
// Hàm xử lý sự kiện khi người dùng tap SubButton
function
_onTapHandlerStateChange
({
nativeEvent
})
{
switch
(
nativeEvent
.
state
)
{
case
State
.
BEGAN
:
{
// Khi bắt đầu nhấn -> giảm opacity để tạo hiệu ứng feedback
buttonOpacity
.
value
=
0.5
;
break
;
}
case
State
.
END
:
{
// Khi nhấn xong -> phát sự kiện đóng FAB
DeviceEventEmitter
.
emit
(
SUBBTN_TAP_EVENT
);
// Trả opacity về lại bình thường
buttonOpacity
.
value
=
1.0
;
// Gọi hàm onPress được truyền từ props
onPress
&&
onPress
();
break
;
}
case
State
.
CANCELLED
:
{
// Nếu hủy tap -> trả opacity về 1
buttonOpacity
.
value
=
1.0
;
break
;
}
...
...
@@ -50,13 +62,14 @@ const SubButton = props => {
break
;
}
}
}
return
(
// Bọc SubButton trong TapGestureHandler để xử lý cử chỉ chạm
<
TapGestureHandler
onHandlerStateChange
=
{
_onTapHandlerStateChange
}
>
<
Animated
.
View
style
=
{[
styles
.
subButton
,
animatedStyles
]}
>
<
Animated
.
View
style
=
{[
styles
.
subButton
,{
backgroundColor
:
backgroundColor
}
,
animatedStyles
]}
>
<
Text
style
=
{
styles
.
label
}
>
{
label
}
<
/Text
>
<
Image
source
=
{
images
}
style
=
{{
width
:
20
,
height
:
20
}}
resizeMode
=
"contain"
tintColor
=
{
R
.
colors
.
white
}
/
>
<
/Animated.View
>
<
/TapGestureHandler
>
);
...
...
@@ -64,18 +77,21 @@ const SubButton = props => {
export
default
SubButton
;
// Style cho SubButton
const
styles
=
StyleSheet
.
create
({
subButton
:
{
width
:
SUBBTN_WIDTH
,
height
:
SUBBTN_HEIGHT
,
borderRadius
:
SUBBTN_BORDER_RADIUS
,
backgroundColor
:
SUBBTN_BACKGROUND_COLOR
,
width
:
150
,
// Chiều rộng nút con
height
:
35
,
// Chiều cao nút con
borderRadius
:
SUBBTN_BORDER_RADIUS
,
// Bo tròn (tròn hoàn hảo)
backgroundColor
:
SUBBTN_BACKGROUND_COLOR
,
// Màu nền nút
justifyContent
:
'center'
,
// Canh giữa dọc
marginTop
:
10
,
// Khoảng cách giữa các SubButton4
flexDirection
:
'row'
,
alignItems
:
'center'
,
justifyContent
:
'center'
,
marginTop
:
10
,
},
label
:
{
color
:
'#EFFBFA'
,
fontSize
:
24
,
marginRight
:
3
,
color
:
'#FFFFFF'
,
// Màu chữ (trắng xanh nhạt)
fontSize
:
12
,
// Kích thước chữ
},
});
src/screens/incoming_document/detail/view.js
View file @
faec694d
...
...
@@ -146,10 +146,8 @@ const DetailIncomingDocumentView = props => {
keyExtractor
=
{(
item
,
index
)
=>
index
.
toString
()}
/
>
<
FAB
>
<
SubButton
onPress
=
{()
=>
Alert
.
alert
(
'Pressed 1!'
)}
label
=
"1"
/>
<
SubButton
onPress
=
{()
=>
Alert
.
alert
(
'Pressed 2!'
)}
label
=
"2"
/>
<
SubButton
onPress
=
{()
=>
Alert
.
alert
(
'Pressed 3!'
)}
label
=
"3"
/>
<
SubButton
onPress
=
{()
=>
Alert
.
alert
(
'Pressed 4!'
)}
label
=
"4"
/>
<
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
}
/
>
<
/FAB
>
<
/View
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment