需求描述: 要在页面中显示一个悬浮球,可以点击与拖动,拖动不能移出屏幕,松手会自动贴到靠近的左/右侧,并可以半吸附到左右侧
遇到的问题:
-
正常情况下拖动就会触发点击事件,如何区分二者?
解决方案:拖动操作正常,若松开与按下时间间隔足够短,则视为触发点击;
其他可能的方案:
- 拖动操作正常,若松开与按下时的位移足够小,则视为触发点击;
- 长按拖动(onLongPress),短按点击(onPress);
- …
-
如何确定合适的y坐标上下界?
解决方案:接收参数
top、bottom用于限制上下界,由父组件规定上下界,并根据bottom的变化调整y坐标
实现如下:
import React, { useEffect, useState } from "react";
import { View, StyleSheet, PanResponder, Dimensions } from "react-native";
const FloatingButton = ({handlePress,size=50,top,bottom}:{handlePress:Function,size:number,top:number,bottom:number|null}) => {
const screenWidth=Dimensions.get("window").width
const screenHeight=Dimensions.get("window").height
const [position, setPosition] = useState({ x: -size/2, y: screenHeight/2 /*可以根据实际情况调整初始y坐标*/ });
const [pressStartTime, setPressStartTime] = useState(0);
const panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: () => true,
onPanResponderMove: (e, gestureState) => {
let posX = gestureState.moveX - size/2;
if(posX<0)posX=-size/2;
else if(posX>screenWidth-size)posX=screenWidth-size/2;
let posY = gestureState.moveY - size/2;
if(posY<0)posY=-size/2;
setPosition({
x: posX,
y: posY,
});
},
onPanResponderGrant: () => {
setPressStartTime(Date.now());
},
onPanResponderRelease: () => {
const pressEndTime = Date.now();
const pressDuration = pressEndTime - pressStartTime;
//如果点击时间(松开时间与按下时间的差值)过短,则视作点击
if (pressDuration < 200 /*时间阈值,可以根据实际情况调整*/) {
handlePress();
}
let posX = position.x;
if(posX<0) posX=-size/2;
else if(posX>screenWidth-size) posX=screenWidth-size/2;
else if(posX<(screenWidth-size)/2) posX=0;
else if(posX>(screenWidth-size)/2) posX=screenWidth-size;
let posY = position.y;
if(posY<top)posY=top;
else if(bottom && posY>bottom-size) posY=bottom-size;
setPosition({
x: posX,
y: posY,
});
},
});
useEffect(()=>{
if(bottom && position.y>bottom-size) {
setPosition({
x: position.x,
y: bottom - size,
});
}
},[bottom])
return (
<>
<View
style={[styles.ball, { left: position.x, top: position.y, width: size, height: size, borderRadius: size/2,}]}
{...panResponder.panHandlers} />
</>
);
};
const styles = StyleSheet.create({
ball: {
backgroundColor: '#FFC88B',
position: 'absolute',
zIndex: 10000,
borderColor: 'grey',
borderWidth: 0.5,
opacity: 0.6,
},
});
export default FloatingButton;
使用示例:
<FloatingButton handlePress={handleOpenAI} size={50} bottom={scrollHeight} top={80}/>