需求
有一個圖片上傳的功能,選擇完圖片之后會按照選擇順序進行排序,系統(tǒng)會默認前5張為系統(tǒng)展示圖片,其他的圖片留存在系統(tǒng)內(nèi)供運營商使用. 圖片選擇完之后可以進行拖拽,調(diào)整順序.
先上效果圖
GIF.gif
1. 響應(yīng)手勢事件,綁定相關(guān)的方法.
componentWillMount() {
this._panResponder = PanResponder.create({
//用戶開始觸摸屏幕的時候官还,是否愿意成為響應(yīng)者;
onStartShouldSetPanResponder: () => true,
//用戶開始觸摸屏幕的時候毒坛,是否愿意成為響應(yīng)者望伦;
onMoveShouldSetPanResponder: () => true,
// 開始按下的時候 調(diào)用的方法.
onPanResponderGrant: (evt, gestureState) => this.onPanResponderGrant(evt),
// 手指移動的時候調(diào)用的方法
onPanResponderMove: (evt, gestureState) => this.onPanResponderMove(evt, gestureState),
// 手指放開的調(diào)用的方法.
onPanResponderRelease: (evt, gestureState) => this.onPanResponderEnd(),
onPanResponderTerminate: (evt, gestureState) => this.onPanResponderEnd()
});
}
render() {
const views1 = [];
const views2 = [];
this.names.forEach((value, index) => {
let bg = this.color[index];
if (index >= 5) {
views2.push(
<View
// 加了這句 View 才能響應(yīng)手勢
{...this._panResponder.panHandlers}
key={value}
style={
[styles.circle, {
left: (index - 5) * CIRCLE_SIZE,
backgroundColor: bg,
top: CIRCLE_SIZE,
}]}
ref={ref => this.names[index] = ref}
/>)
} else {
views1.push(
<View
// 加了這句 View 才能響應(yīng)手勢
{...this._panResponder.panHandlers}
key={value}
style={
[styles.circle,
{left: index * CIRCLE_SIZE},
{backgroundColor: bg},
]}
ref={ref => this.names[index] = ref}
/>)
}
});
return (
<View
style={styles.container}>
{views1}
{views2}
</View>
);
}
2 需要根據(jù)按下的位置來判斷 要移動哪個view
// 根據(jù)按下的坐標(biāo),判斷索引
getIdByPosition(pageX, pageY) {
let index = -1;
if (pageX > CIRCLE_SIZE * this.names.length) {
index = this.names.length - 1;
} else {
index = Math.floor((pageX) / CIRCLE_SIZE)
}
// 如果觸摸的高度(需要減去導(dǎo)航欄的高度)大于圓的半徑,說明是第二行
if (pageY - NAV_BAR_HEIGHT > CIRCLE_SIZE) {
index += 5;
}
return index;
}
static getLeftValueYById(id) {
return id >= 5 ? (id - 5) * CIRCLE_SIZE : id * CIRCLE_SIZE;
}
static getTopValueById(id) {
return id >= 5 ? CIRCLE_SIZE : 0;
}
3. 開始按下的操作
// 開始按下手勢操作。加點偏移量給用戶一些視覺反饋煎殷,讓他們知道發(fā)生了什么事情屯伞!
onPanResponderGrant(evt) {
const {pageX, locationX, pageY, locationY} = evt.nativeEvent;
this.index = this.getIdByPosition(pageX, pageY);
console.log("this.index" + this.index);
this.preX = pageX - locationX;
this.preY = pageY - locationY;
// 給退拽的item加個陰影
let item = this.names[this.index];
item.setNativeProps({
style: {
shadowColor: "red",
shadowOpacity: 0.5,
shadowRadius: 5,
shadowOffset: {height: 10, width: 10},
elevation: 5,
top: -2,
}
});
}
3. 移動的操作, 移動的時候 需要根據(jù)手指移動的位置,來判斷哪兩個小球交換.
// 開始移動
onPanResponderMove(evt, gestureState) {
// 按下的小球
let index = this.index;
// 按下的小球需要移動到哪
let left = this.preX + gestureState.dx;
let top = this.preY + gestureState.dy;
// 根據(jù)ref 來獲取索引對應(yīng)的view,setNativeProps來設(shè)置小球的位置
let item = this.names[this.index];
item.setNativeProps({
style: {
left: left,
top: top,
}
});
// 手指移動的某個點對應(yīng)的索引,如果手指移動到某個點對應(yīng)的索引 與按下那個點對應(yīng)的索引不同,就交換兩個小球的位置.
let collideIndex = this.getIdByPosition(evt.nativeEvent.pageX, evt.nativeEvent.pageY);
if (collideIndex !== this.index && collideIndex !== -1) {
let collideItem = this.names[collideIndex];
collideItem.setNativeProps({
style: {
left: ListViewPage.getLeftValueYById(this.index),
top: ListViewPage.getTopValueById(this.index)
}
});
//交換兩個值
[this.names[this.index], this.names[collideIndex]] = [this.names[collideIndex], this.names[this.index]];
[this.order[this.index], this.order[collideIndex]] = [this.order[collideIndex], this.order[this.index]];
this.index = collideIndex;
}
}
4. 手指釋放的操作
onPanResponderEnd() {
// 復(fù)位
const shadowStyle = {
shadowColor: "#000",
shadowOpacity: 0,
shadowRadius: 0,
shadowOffset: {height: 0, width: 0,},
elevation: 0
};
// 這個索引是已經(jīng)交換過的索引.
let item = this.names[this.index];
let index = this.index;
let leftValue = ListViewPage.getLeftValueYById(this.index);
let topValue = ListViewPage.getTopValueById(this.index);
item.setNativeProps({
style: {
...shadowStyle,
left: leftValue,
top: topValue,
}
});
}
5.全部的代碼
import React, {Component} from 'react';
import {
StyleSheet,
View,
PanResponder,
Animated,
LayoutAnimation,
UIManager,
} from 'react-native';
/**
* 自定義拖拽圖片排序
**/
const CIRCLE_SIZE = 70;
const NAV_BAR_HEIGHT = 0;
export default class DragSort extends Component {
constructor(props) {
super(props);
this.names = ['Android', 'iOS', 'js', 'jq', '00', '11', '22', '33', '44', '55'];
this.order = ['Android', 'iOS', 'js', 'jq', '00', '11', '22', '33', '44', '55'];
this.color = ['red', 'blue', 'black', 'pink', 'gray', '#03ef94', '#009688', '#607d8b', '#3f51b5', '#00796b'];
}
// 開始按下手勢操作。加點偏移量給用戶一些視覺反饋豪直,讓他們知道發(fā)生了什么事情劣摇!
onPanResponderGrant(evt) {
const {pageX, locationX, pageY, locationY} = evt.nativeEvent;
this.index = this.getIdByPosition(pageX, pageY);
console.log("this.index" + this.index);
this.preX = pageX - locationX;
this.preY = pageY - locationY;
// 給退拽的item加個陰影
let item = this.names[this.index];
item.setNativeProps({
style: {
shadowColor: "red",
shadowOpacity: 0.5,
shadowRadius: 5,
shadowOffset: {height: 10, width: 10},
elevation: 5,
top: -2,
}
});
}
// 最近一次的移動距離為gestureState.move{X,Y}
onPanResponderMove(evt, gestureState) {
let index = this.index;
let left = this.preX + gestureState.dx;
let top = this.preY + gestureState.dy;
let item = this.names[this.index];
item.setNativeProps({
style: {
left: left,
top: top,
}
});
let collideIndex = this.getIdByPosition(evt.nativeEvent.pageX, evt.nativeEvent.pageY);
if (collideIndex !== this.index && collideIndex !== -1) {
let collideItem = this.names[collideIndex];
collideItem.setNativeProps({
style: {
left: ListViewPage.getLeftValueYById(this.index),
top: ListViewPage.getTopValueById(this.index)
}
});
//交換兩個值
[this.names[this.index], this.names[collideIndex]] = [this.names[collideIndex], this.names[this.index]];
[this.order[this.index], this.order[collideIndex]] = [this.order[collideIndex], this.order[this.index]];
this.index = collideIndex;
}
}
// 用戶放開了所有的觸摸點,且此時視圖已經(jīng)成為了響應(yīng)者弓乙。
// 一般來說這意味著一個手勢操作已經(jīng)成功完成末融。
onPanResponderEnd() {
// 復(fù)位
const shadowStyle = {
shadowColor: "#000",
shadowOpacity: 0,
shadowRadius: 0,
shadowOffset: {height: 0, width: 0,},
elevation: 0
};
let item = this.names[this.index];
let index = this.index;
let leftValue = ListViewPage.getLeftValueYById(this.index);
let topValue = ListViewPage.getTopValueById(this.index);
item.setNativeProps({
style: {
...shadowStyle,
left: leftValue,
top: topValue,
}
});
console.log(this.order);
}
// 根據(jù)按下的坐標(biāo),判斷索引
getIdByPosition(pageX, pageY) {
let index = -1;
if (pageX > CIRCLE_SIZE * this.names.length) {
index = this.names.length - 1;
} else {
index = Math.floor((pageX) / CIRCLE_SIZE)
}
// 如果觸摸的高度(需要減去導(dǎo)航欄的高度)大于圓的半徑,說明是第二行
if (pageY - NAV_BAR_HEIGHT > CIRCLE_SIZE) {
index += 5;
}
return index;
}
static getLeftValueYById(id) {
return id >= 5 ? (id - 5) * CIRCLE_SIZE : id * CIRCLE_SIZE;
}
static getTopValueById(id) {
return id >= 5 ? CIRCLE_SIZE : 0;
}
componentWillMount() {
this._panResponder = PanResponder.create({
//用戶開始觸摸屏幕的時候,是否愿意成為響應(yīng)者暇韧;
onStartShouldSetPanResponder: () => true,
//用戶開始觸摸屏幕的時候勾习,是否愿意成為響應(yīng)者;
onMoveShouldSetPanResponder: () => true,
// 開始觸摸的時候 調(diào)用的方法.
onPanResponderGrant: (evt, gestureState) => this.onPanResponderGrant(evt),
// 手指移動的時候調(diào)用的方法
onPanResponderMove: (evt, gestureState) => this.onPanResponderMove(evt, gestureState),
// 手指放開的調(diào)用的方法.
onPanResponderRelease: (evt, gestureState) => this.onPanResponderEnd(),
onPanResponderTerminate: (evt, gestureState) => this.onPanResponderEnd()
});
}
render() {
const views1 = [];
const views2 = [];
this.names.forEach((value, index) => {
let bg = this.color[index];
if (index >= 5) {
views2.push(
<View
{...this._panResponder.panHandlers}
key={value}
style={
[styles.circle, {
left: (index - 5) * CIRCLE_SIZE,
backgroundColor: bg,
top: CIRCLE_SIZE,
}]}
ref={ref => this.names[index] = ref}
/>)
} else {
views1.push(
<View
{...this._panResponder.panHandlers}
key={value}
style={
[styles.circle,
{left: index * CIRCLE_SIZE},
{backgroundColor: bg},
]}
ref={ref => this.names[index] = ref}
/>)
}
});
return (
<View
style={styles.container}>
{views1}
{views2}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
circle: {
width: CIRCLE_SIZE,
height: CIRCLE_SIZE,
borderRadius: CIRCLE_SIZE / 2,
backgroundColor: 'blue',
position: 'absolute',
}
});