前言(可跳過)
我在開發自己的APP時遇到了一個類似於qq聊天訊息氣泡拖拽訊息的需求,因為在網上沒有找到相關的元件,所以自己動手實現了一下
需求:對聊天訊息氣泡拖拽到一定長度鬆開時該氣泡會消失(可自行增加拖拽過程,以及消失的動畫)
解決思路:觸控下聊天氣泡時,以一種合理的方式確定該氣泡是當前氣泡所在聊天框後,再改變它的位置(樣式位置)鬆開後,取消且僅取消當前氣泡的渲染操作
解決方案:
0
建立一個足夠大的陣列(長度大於聊天框例項數量),初始化每個元素為React.createRef()
const badgeRegistryArr = new Array(99).fill(React.createRef())
1
渲染聊天列表時,對每個聊天框例項建立一個ref,並對映到陣列中(ref={MappingArr[index]})
ref={badgeRegistryArr[item.id-1]}
2
使用PanResponder建立一個觸控手勢例項以此來實現拖拽效果(即當onPanResponderMove發生時更改一次Animated的值,已將樣式部署到所有例項)
this.state = {
pan: new Animated.ValueXY(),
};
——————
const { pan,currentTargetId } = this.state
const [translateX, translateY] = [pan.x, pan.y];
const imageStyle = {transform: [{translateX}, {translateY}]}
——————
style={[imageStyle,{position:'relative',zIndex:999}]}
——————
onPanResponderGrant: (evt, gestureState) => {
// 設定初始位置
pan.setValue({x: 0, y: 0});
},
——————
// 使用拖拽的偏移量來定位
onPanResponderMove: Animated.event([
null, {dx: this.state.pan.x, dy: this.state.pan.y},
]),
——————
3
第2步操作會改變所有聊天氣泡的值,所以我們要甄別一下,在開始手勢操作事件中我們進行一次setState(得到當前的_nativeTag),然後重新整理聊天框例項列表,並將列表中每個元素的_nativeTag和當前_nativeTag進行比對,為true時,將樣式部署到且只部署到該例項。這樣單獨拖拽的效果就實現了
this.state = {
pan: new Animated.ValueXY(),
currentTargetId:0,
};
——————
onPanResponderGrant: (evt, gestureState) => {
// 開始手勢操作。給使用者一些視覺反饋,讓他們知道發生了什麼事情!
const { pan } = this.state
const { _nativeTag } = evt.currentTarget
this.setState({currentTargetId:_nativeTag})
pan.setValue({x: 0, y: 0});
},
——————
judgeBadgeMove = (item) => {
const { pan,currentTargetId } = this.state
const [translateX, translateY] = [pan.x, pan.y];
let _nativeTag = null
_nativeTag = badgeRegistryArr[item.id-1].current ? badgeRegistryArr[item.id-1].current._nativeTag:null
if(_nativeTag === currentTargetId){
return {transform: [{translateX}, {translateY}]}
}
}
——————
const imageStyle = this.judgeBadgeMove(item);
4
在state中建立一個陣列(用來實現拖拽後消失的效果,暫且稱為A陣列)所有元素初始化為true,陣列長度與0步中陣列相同,在使用者放開觸控點(視為手勢操作完成)事件中,當橫向或縱向移動長度超出指定值時,對陣列A中的匹配元素(目標元素下標即為在第3步中比對操作中的命中元素下標)的值setState為false這樣就實現了拖拽一定長度後消失的效果
this.state = {
pan: new Animated.ValueXY(),
currentTargetId:0,
badgeVisualArr:new Array(6).fill(true)
};
——————
onPanResponderRelease: (evt, gestureState) => {
const { _nativeTag } = evt.currentTarget
const { dx,dy } = gestureState
badgeRegistryArr.forEach((value,index) => {
if(_nativeTag === value.current._nativeTag){
let tempArr = this.state.badgeVisualArr
tempArr[index] = false
this.setState({badgeVisualArr:tempArr})
}
})
if(Math.abs(dx) > 80 || Math.abs(dy)){
this.setState({badgeVisualValue:false})
}
// 使用者放開了所有的觸控點,且此時檢視已經成為了響應者。
// 一般來說這意味著一個手勢操作已經成功完成。
},
——————
{
item.messageValue > 0 && badgeVisualArr[item.id-1] ? <Badge
value={item.messageValue}
status='error'
badgeStyle={{borderColor:'red'}}
></Badge>:null
}
——————
全部程式碼(以上程式碼塊來自這裡,刪除了無關程式碼)
點選檢視程式碼
import React, { Component } from 'react';
import { View, PanResponder,Animated } from 'react-native';
import { Badge} from 'react-native-elements';
import { FlatList } from 'react-native-gesture-handler';
const badgeRegistryArr = new Array(99).fill(React.createRef())
const data = [
{
id:1,
nickName:'wdnmd',
messageValue:0,
face:'../../../assets/images/boy.png',
},{
id:2,
nickName:'我帶你們打',
messageValue:10,
face:'../../../assets/images/boy.png',
},{
id:3,
messageValue:0,
nickName:'wdnmd',
messageValue:20,
face:'../../../assets/images/boy.png',
},{
id:4,
nickName:'wdnmd',
messageValue:1,
face:'../../../assets/images/boy.png',
},{
id:5,
nickName:'wdnmd',
messageValue:0,
face:'../../../assets/images/boy.png',
},{
id:6,
nickName:'wdnmd',
messageValue:999,
face:'../../../assets/images/boy.png',
}
]
class Direct extends Component {
constructor(props) {
super(props);
this.state = {
searchBarValue:null,
pan: new Animated.ValueXY(),
currentTargetId:0,
badgeVisualArr:new Array(99).fill(true)
};
}
componentWillMount = function (){
this._panResponder = PanResponder.create({
// 要求成為響應者:
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {
// 開始手勢操作。給使用者一些視覺反饋,讓他們知道發生了什麼事情!
const { pan } = this.state
const { _nativeTag } = evt.currentTarget
this.setState({currentTargetId:_nativeTag})
pan.setValue({x: 0, y: 0});
// gestureState.{x,y} 現在會被設定為0
},
onPanResponderMove: Animated.event([
null, {dx: this.state.pan.x, dy: this.state.pan.y},
]),
onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderRelease: (evt, gestureState) => {
const { _nativeTag } = evt.currentTarget
const { dx,dy } = gestureState
badgeRegistryArr.forEach((value,index) => {
if(_nativeTag === value.current._nativeTag){
let tempArr = this.state.badgeVisualArr
tempArr[index] = false
this.setState({badgeVisualArr:tempArr})
console.log('binngo')
}
})
if(Math.abs(dx) > 80 || Math.abs(dy)){
this.setState({badgeVisualValue:false})
}
// 使用者放開了所有的觸控點,且此時檢視已經成為了響應者。
// 一般來說這意味著一個手勢操作已經成功完成。
},
onPanResponderTerminate: (evt, gestureState) => {
// 另一個元件已經成為了新的響應者,所以當前手勢將被取消。
},
onShouldBlockNativeResponder: (evt, gestureState) => {
// 返回一個布林值,決定當前元件是否應該阻止原生元件成為JS響應者
// 預設返回true。目前暫時只支援android。
return true;
},
});
}
judgeBadgeMove = (item) => {
const { pan,currentTargetId } = this.state
const [translateX, translateY] = [pan.x, pan.y];
let _nativeTag = null
_nativeTag = badgeRegistryArr[item.id-1].current ? badgeRegistryArr[item.id-1].current._nativeTag:null
if(_nativeTag === currentTargetId){
return {transform: [{translateX}, {translateY}]}
}
}
renderItem = ({item}) => {
const { badgeVisualArr} = this.state
const imageStyle = this.judgeBadgeMove(item);
return (
<Animated.View
ref={badgeRegistryArr[item.id-1]}
{...this._panResponder.panHandlers}
style={[imageStyle,{position:'relative',zIndex:999}]}
>
{
item.messageValue > 0 && badgeVisualArr[item.id-1] ? <Badge
value={item.messageValue}
status='error'
badgeStyle={{borderColor:'red'}}
></Badge>:null
}
</Animated.View>
)
}
render() {
return (
<View style={styles.mainStyle}>
<FlatList
data={data}
renderItem={this.renderItem}
/>
</View>
);
}
}
export default Direct;