在高仿“掘金”客户端的那个项目中,你会发现在打开和关闭“首页展示标签”中,我并没有实现可拖拽换位item的效果。不过在自己新写的Gank.io项目中,将这一功能实现了一把,在此记录一下。先上效果图
对,就是这样~
在实现这个效果前,我的思路是这样的,布局->item可点击突出显示->可移动item->可交换item->抬起手指恢复正确的位置。下面一一解释。
忘了说了,由于这个界面的item的元素较少,并且为了方便起见,我并没有采用ListView控件去实现这个list,而是使用数组map返回一个个itemView。
render(){
return(
{this.names.map((item, i)=>{
return (
this.items[i] = ref}
key={i}
style={[styles.item, {top: (i+1)*49}]}>
{item}
);
})}
);
}
item: {
flexDirection: 'row',
height: px2dp(49),
width: theme.screenWidth,
alignItems: 'center',
backgroundColor: '#fff',
paddingLeft: px2dp(20),
borderBottomColor: theme.segment.color,
borderBottomWidth: theme.segment.width,
position: 'absolute',
},
不用在意其他的props,最关键的最起作用的就是position属性,一旦设置,该View的位置就不会受控于flexbox的布局了,直接浮动受控于top,left这几个参数。对于{...this._panResponder.panHandlers} 这个属性,就会谈到react-native中的手势,也就是我们下一个内容。
onStartShouldSetPanResponder: (evt, gestureState) => true, //开启手势响应
onMoveShouldSetPanResponder: (evt, gestureState) => true, //开启移动手势响应
onPanResponderGrant: (evt, gestureState) => { //手指触碰屏幕那一刻触发
},
onPanResponderMove: (evt, gestureState) => { //手指在屏幕上移动触发
},
onPanResponderTerminationRequest: (evt, gestureState) => true, //当有其他不同手势出现,响应是否中止当前的手势
onPanResponderRelease: (evt, gestureState) => { //手指离开屏幕触发
},
onPanResponderTerminate: (evt, gestureState) => { //当前手势中止触发
},
onPanResponderGrant: (evt, gestureState) => {
const {pageY, locationY} = evt.nativeEvent; //1
this.index = this._getIdByPosition(pageY); //2
this.preY = pageY - locationY; //3
//get the taped item and highlight it
let item = this.items[this.index]; //4
item.setNativeProps({ //5
style: {
shadowColor: "#000", //6
shadowOpacity: 0.3, //6
shadowRadius: 5, //6
shadowOffset: {height: 0, width: 2}, //6
elevation: 5 //7
}
});
},
_getIdByPosition(pageY){
var id = -1;
const height = px2dp(49);
if(pageY >= height && pageY < height*2)
id = 0;
else if(pageY >= height*2 && pageY < height*3)
id = 1;
else if(pageY >= height*3 && pageY < height*4)
id = 2;
else if(pageY >= height*4 && pageY < height*5)
id = 3;
else if(pageY >= height*5 && pageY < height*6)
id = 4;
return id;
}
onPanResponderMove: (evt, gestureState) => {
let top = this.preY + gestureState.dy;
let item = this.items[this.index];
item.setNativeProps({
style: {top: top}
});
},
onPanResponderMove: (evt, gestureState) => {
let top = this.preY + gestureState.dy;
let item = this.items[this.index];
item.setNativeProps({
style: {top: top}
});
let collideIndex = this._getIdByPosition(evt.nativeEvent.pageY); //获取当前的位置上item的id
if(collideIndex !== this.index && collideIndex !== -1) { //判断是否和手上的item的id一样
let collideItem = this.items[collideIndex];
collideItem.setNativeProps({
style: {top: this._getTopValueYById(this.index)} //将collideItem的位置移动到手上的item的位置
});
//swap two values
[this.items[this.index], this.items[collideIndex]] = [this.items[collideIndex], this.items[this.index]];
this.index = collideIndex;
}
},
onPanResponderRelease: (evt, gestureState) => {
const shadowStyle = {
shadowColor: "#000",
shadowOpacity: 0,
shadowRadius: 0,
shadowOffset: {height: 0, width: 0,},
elevation: 0
};
let item = this.items[this.index];
//go back the correct position
item.setNativeProps({
style: {...shadowStyle, top: this._getTopValueYById(this.index)}
});
},
_getTopValueYById(id){
const height = px2dp(49);
return (id + 1) * height;
}
{this.names.map((item, i)=>{
this.order.push(item); //add code at here
return (
this.items[i] = ref}
key={i}
style={[styles.item, {top: (i+1)*49}]}>
{item}
);
})}
//swap two values
[this.items[this.index], this.items[collideIndex]] = [this.items[collideIndex], this.items[this.index]];
[this.order[this.index], this.order[collideIndex]] = [this.order[collideIndex], this.order[this.index]]; //add code at here
this.index = collideIndex;
/** * Created by wangdi on 27/11/16. */ 'use strict'; import React, {Component, PropTypes} from 'react'; import {StyleSheet, View, Text, PanResponder} from 'react-native'; import Icon from 'react-native-vector-icons/Ionicons'; import BackPageComponent from '../BackPageComponent'; import NavigationBar from '../../components/NavigationBar'; import px2dp from '../../utils/px2dp'; import theme from '../../constants/theme'; export default class OrderContentPage extends BackPageComponent{ constructor(props){ super(props); this.names = ['Android','iOS','前端','拓展资源','休息视频']; this.items = []; this.order = []; } render(){ return(style={styles.container}> ); } componentWillMount(){ this._panResponder = PanResponder.create({ onStartShouldSetPanResponder: (evt, gestureState) => true, onMoveShouldSetPanResponder: (evt, gestureState) => true, onPanResponderGrant: (evt, gestureState) => { const {pageY, locationY} = evt.nativeEvent; this.index = this._getIdByPosition(pageY); this.preY = pageY - locationY; //get the taped item and highlight it let item = this.items[this.index]; item.setNativeProps({ style: { shadowColor: "#000", shadowOpacity: 0.3, shadowRadius: 5, shadowOffset: {height: 0, width: 2}, elevation: 5 } }); }, onPanResponderMove: (evt, gestureState) => { let top = this.preY + gestureState.dy; let item = this.items[this.index]; item.setNativeProps({ style: {top: top} }); let collideIndex = this._getIdByPosition(evt.nativeEvent.pageY); if(collideIndex !== this.index && collideIndex !== -1) { let collideItem = this.items[collideIndex]; collideItem.setNativeProps({ style: {top: this._getTopValueYById(this.index)} }); //swap two values [this.items[this.index], this.items[collideIndex]] = [this.items[collideIndex], this.items[this.index]]; [this.order[this.index], this.order[collideIndex]] = [this.order[collideIndex], this.order[this.index]]; this.index = collideIndex; } }, onPanResponderTerminationRequest: (evt, gestureState) => true, onPanResponderRelease: (evt, gestureState) => { const shadowStyle = { shadowColor: "#000", shadowOpacity: 0, shadowRadius: 0, shadowOffset: {height: 0, width: 0,}, elevation: 0 }; let item = this.items[this.index]; //go back the correct position item.setNativeProps({ style: {...shadowStyle, top: this._getTopValueYById(this.index)} }); console.log(this.order); }, onPanResponderTerminate: (evt, gestureState) => { // Another component has become the responder, so this gesture // should be cancelled } }); } _getIdByPosition(pageY){ var id = -1; const height = px2dp(49); if(pageY >= height && pageY < height*2) id = 0; else if(pageY >= height*2 && pageY < height*3) id = 1; else if(pageY >= height*3 && pageY < height*4) id = 2; else if(pageY >= height*4 && pageY < height*5) id = 3; else if(pageY >= height*5 && pageY < height*6) id = 4; return id; } _getTopValueYById(id){ const height = px2dp(49); return (id + 1) * height; } } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: theme.pageBackgroundColor }, item: { flexDirection: 'row', height: px2dp(49), width: theme.screenWidth, alignItems: 'center', backgroundColor: '#fff', paddingLeft: px2dp(20), borderBottomColor: theme.segment.color, borderBottomWidth: theme.segment.width, position: 'absolute', }, itemTitle: { fontSize: px2dp(15), color: '#000', marginLeft: px2dp(20) } });title="首页内容展示顺序" isBackBtnOnLeft={true} leftBtnIcon="arrow-back" leftBtnPress={this._handleBack.bind(this)} /> {this.names.map((item, i)=>{ this.order.push(item); return ( {...this._panResponder.panHandlers} ref={(ref) => this.items[i] = ref} key={i} style={[styles.item, {top: (i+1)*49}]}> ); })}name="ios-menu" size={px2dp(25)} color="#ccc"/> style={styles.itemTitle}>{item}