RN-可拖动的悬浮按钮
2021-09-06 本文已影响0人
你家毕老师
RN-可拖动的悬浮按钮
需求
分享悬浮按钮,需要展示在特定页面,并且支持拖动(防止遮挡页面内容)
解决方案思路
1. 封装原生组件,为RN提供原生桥
- 缺点:两端都需要开发,而且原生组件的位置、大小改变很难适应后续需求、两端代码需要二次封装以对外提供一致的方法和属性。
- 优点:调用现有组件,不需要额外开发组件相关内容。
2. 第三方组件-react-native-interactable
- 缺点:需要原生链接一下库,代码比较老(最后维护日期是17年),可能会与当前环境不兼容或出现莫名其妙的bug。
- 优点:直接集成,瞬间完成开发,节省时间。
3. 参考别人写的案例造轮子-RN开发图标拖动效果实现
- 缺点:贴的代码太多太长了。。。
- 优点:。。。
4. 自己搞个RN组件
- 缺点:完全没思路,不确定多久可以搞定。
- 优点:组件bug可控,可以通过看文档造轮子学习熟悉一下相关知识。
开整
1. 了解RN中的滑动手势-PanResponder
PanResponder.gif真是意外发现,官方给出的例子还有代码,已经基本实现了想要的功能。。
首先以当前例子为基础直接贴到我们的demo里慢慢改
运行一下,发现没问题,到目前发现自己搞并不难。所以我们决定更进一步优化,做个松手后自动边缘吸附的效果吧,这样又可以与原生保持一致,而且会有比较好的效果。
2. 通过RN中的动画做吸附效果-Animated.ValueXY
AnimatedValueXY.gif看到这个demo,发现运气太好了已经成功了99%啊。这就是想要的效果啊。
3. 计算松手后的位置,实现边缘吸附
/*
* @Author: 毕帅
* @Date: 2021-09-03 09:42:36
* @Last Modified by: 毕帅
* @Last Modified time: 2021-09-06 09:45:00
*/
import React, { Component } from "react";
import { Animated, PanResponder } from "react-native";
import { currentScreen } from "../../utils/Utils";
class GJSuspendView extends Component {
pan = new Animated.ValueXY();
panResponder = PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
this.pan.setOffset({
x: this.pan.x._value,
y: this.pan.y._value
});
},
onPanResponderMove: Animated.event([
null,
{ dx: this.pan.x, dy: this.pan.y }
]),
onPanResponderRelease: (e, gestureState) => {
// 距边界的距离
let space = 10;
// 屏幕宽高
let screenW = currentScreen().width;
let screenH = currentScreen().height;
let { locationX, locationY, pageX, pageY } = e.nativeEvent;
let { dx, dy } = gestureState;
// locationX locationY 触摸点相对于组件的位置
// pageX pageY 触摸点相对于根元素也就是屏幕的位置
// dx dy 拖动了多远
// 1.计算出最后位置是应该吸附左侧还是右侧
// 手势结束时组件的x坐标
let finalX = pageX - locationX;
let finalY = pageY - locationY;
let isLeft = screenW / 2 > (finalX + this.viewW / 2);
// 2.计算最终偏移量
// 最终x轴偏移量
let offsetX = 0;
// 判断应该吸附在左边还是右边
if (isLeft) {
offsetX = dx - finalX + space;
} else {
offsetX = dx - (finalX + this.viewW + space - screenW);
}
let offsetY = dy;
// 判断view是否超出顶部
if (finalY < space) {
offsetY -= (finalY - space);
}
// 判断view是否超出底部
if (finalY + this.viewH + space > screenH) {
offsetY -= (finalY + this.viewH + space - screenH);
}
Animated.spring(
this.pan, // Auto-multiplexed
{
toValue: { x: offsetX, y: offsetY },
speed: 150,
bounciness: 0
}
).start(() => {
console.log('结束');
this.pan.flattenOffset();
});
}
});
render() {
let { renderContentView } = this.props;
return (
<Animated.View
onLayout={(event) => {
this.viewW = event.nativeEvent.layout.width;
this.viewH = event.nativeEvent.layout.height;
this.viewX = event.nativeEvent.layout.x;
this.viewY = event.nativeEvent.layout.y;
}}
style={{
transform: [{ translateX: this.pan.x }, { translateY: this.pan.y }]
}}
{...this.panResponder.panHandlers}
>
{renderContentView()}
</Animated.View>
);
}
}
export default GJSuspendView;
4. 总结
RPReplay_Final1630898614.gif我们在官方例子里加上一点四则运算,计算出最终的偏移量,最终达到了想要的效果,有时候官网已经帮我们把代码写好了😂