私はドラッグアンドドロップの部分を持っているようだが、スワップ場所を行う方法を知らない。また、Zインデックスの問題を修正する方法もわかりません(Animated.Viewで怪しいものがあるようです)。アイテムのアニメーションをドラッグ、ドロップ、スワップしますか?
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Image,
PanResponder,
Animated,
Alert,
} from 'react-native';
class Draggable extends Component {
constructor(props) {
super(props);
this.state = {
pan: new Animated.ValueXY(),
scale: new Animated.Value(1),
};
}
componentWillMount() {
this._panResponder = PanResponder.create({
onMoveShouldSetResponderCapture:() => true,
onMoveShouldSetPanResponderCapture:() => true,
onPanResponderGrant: (e, gestureState) => {
this.state.pan.setOffset({x: this.state.pan.x._value, y: this.state.pan.y._value});
this.state.pan.setValue({x: 0, y: 0});
Animated.spring(
this.state.scale,
{ toValue: 1.1, friction: 3 }
).start();
},
onPanResponderMove: Animated.event([
null, {dx: this.state.pan.x, dy: this.state.pan.y},
]),
onPanResponderRelease: (e, gesture) => {
this.state.pan.flattenOffset();
Animated.spring(
this.state.scale,
{ toValue: 1, friction: 3 }
).start();
let dropzone = this.inDropZone(gesture);
if (dropzone) {
console.log(dropzone.y-this.layout.y, this.state.pan.y._value, dropzone.y);
Animated.spring(
this.state.pan,
{toValue:{
x: 0,
y: dropzone.y-this.layout.y,
}}
).start();
} else {
Animated.spring(
this.state.pan,
{toValue:{x:0,y:0}}
).start();
}
},
});
}
inDropZone(gesture) {
var isDropZone = false;
for (dropzone of this.props.dropZoneValues) {
if (gesture.moveY > dropzone.y && gesture.moveY < dropzone.y + dropzone.height && gesture.moveX > dropzone.x && gesture.moveX < dropzone.x + dropzone.width) {
isDropZone = dropzone;
}
}
return isDropZone;
}
setDropZoneValues(event) {
this.props.setDropZoneValues(event.nativeEvent.layout);
this.layout = event.nativeEvent.layout;
}
render() {
let { pan, scale } = this.state;
let [translateX, translateY] = [pan.x, pan.y];
let rotate = '0deg';
let imageStyle = {transform: [{translateX}, {translateY}, {rotate}, {scale}]};
return (
<View
style={styles.dropzone}
onLayout={this.setDropZoneValues.bind(this)}
>
<Animated.View
style={[imageStyle, styles.draggable]}
{...this._panResponder.panHandlers}>
<Image style={styles.image} resizeMode="contain" source={{ uri: this.props.uri }} />
</Animated.View>
</View>
);
}
}
class Playground extends Component {
constructor(props) {
super(props);
this.state = {
dropZoneValues: [],
};
}
setDropZoneValues(layout) {
this.setState({
dropZoneValues: this.state.dropZoneValues.concat(layout),
});
}
render() {
return (
<View style={styles.container}>
<Draggable
dropZoneValues={this.state.dropZoneValues}
setDropZoneValues={this.setDropZoneValues.bind(this)}
uri="https://pbs.twimg.com/profile_images/378800000822867536/3f5a00acf72df93528b6bb7cd0a4fd0c.jpeg"
/>
<Draggable
dropZoneValues={this.state.dropZoneValues}
setDropZoneValues={this.setDropZoneValues.bind(this)}
uri="https://pbs.twimg.com/profile_images/446566229210181632/2IeTff-V.jpeg"
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'orange',
justifyContent: 'center',
alignItems: 'center',
},
dropzone: {
zIndex: 0,
margin: 5,
width: 106,
height: 106,
borderColor: 'green',
borderWidth: 3
},
draggable: {
zIndex: 0,
backgroundColor: 'white',
justifyContent: 'center',
alignItems: 'center',
width: 100,
height: 100,
borderWidth: 1,
borderColor: 'black'
},
image: {
width: 75,
height: 75
}
});
export default Playground;
EDIT:私はスワップの試みをしたが、たったの約半分の時間を動作するようです。また、zIndexは依然として私をナットにしています。私は{color} {zIndex}
のような状態を印刷していますので、100に更新されているのを見ることができますが、それは有効に見えません。青に色を変えることはうまくいくようだ...私は混乱している。
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Image,
PanResponder,
Animated,
Alert,
} from 'react-native';
class Draggable extends Component {
constructor(props) {
super(props);
this.state = {
pan: new Animated.ValueXY(),
scale: new Animated.Value(1),
zIndex: 0,
color: 'white',
};
}
componentWillMount() {
this._panResponder = PanResponder.create({
onMoveShouldSetResponderCapture:() => true,
onMoveShouldSetPanResponderCapture:() => true,
onPanResponderGrant: (e, gestureState) => {
console.log('moving', this.props.index);
this.state.pan.setOffset({x: this.state.pan.x._value, y: this.state.pan.y._value});
this.state.pan.setValue({x: 0, y: 0});
Animated.spring(
this.state.scale,
{ toValue: 1.1, friction: 3 }
).start();
this.setState({ color: 'blue', zIndex: 100 });
},
onPanResponderMove: Animated.event([null,
{ dx: this.state.pan.x, dy: this.state.pan.y },
]),
onPanResponderRelease: (e, gesture) => {
this.state.pan.flattenOffset();
// de-scale
Animated.spring(
this.state.scale,
{ toValue: 1, friction: 3 }
).start();
this.setState({ color: 'white', zIndex: 0 });
let dropzone = this.inDropZone(gesture);
if (dropzone) { // plop into dropzone
// console.log(dropzone.y-this.layout.y, this.state.pan.y._value, dropzone.y);
console.log('grabbed', this.props.index, ' => dropped', dropzone.index);
Animated.spring(
this.state.pan,
{toValue:{
x: 0,
y: dropzone.y-this.layout.y,
}}
).start();
if (this.props.index !== dropzone.index) {
this.props.swapItems(this.props.index, dropzone.index, dropzone.y-this.layout.y);
}
} else {
// spring back to start
Animated.spring(
this.state.pan,
{toValue:{x:0,y:0}}
).start();
}
},
});
}
inDropZone(gesture) {
var isDropZone = false;
for (var dropzone of this.props.dropZoneValues) {
if (gesture.moveY > dropzone.y && gesture.moveY < dropzone.y + dropzone.height) {
isDropZone = dropzone;
}
}
return isDropZone;
}
setDropZoneValues(event) {
this.props.setDropZoneValues(event.nativeEvent.layout, this.props.index, this);
this.layout = event.nativeEvent.layout;
this.layout.index = this.props.index;
}
render() {
let { pan, scale, zIndex, color } = this.state;
let [translateX, translateY] = [pan.x, pan.y];
let rotate = '0deg';
let imageStyle = {
transform: [{translateX}, {translateY}, {rotate}, {scale}]
};
return (
<View
style={[styles.dropzone]}
onLayout={this.setDropZoneValues.bind(this)}
>
<Animated.View
{...this._panResponder.panHandlers}
style={[imageStyle, styles.draggable, { backgroundColor: color, zIndex }]}
>
<Text>{this.props.index}</Text>
<Text>{this.props.char}</Text>
<Text>{this.state.color} {this.state.zIndex}</Text>
</Animated.View>
</View>
);
}
}
Array.prototype.swap = function (x,y) {
var b = this[x];
this[x] = this[y];
this[y] = b;
return this;
}
Array.prototype.clone = function() {
return this.slice(0);
};
const items = [
'shiba inu',
'labrador',
];
class Playground extends Component {
constructor(props) {
super(props);
this.state = {
items,
dropZoneValues: [],
dropzones: [],
};
}
setDropZoneValues(layout, index, dropzone) {
layout.index = index;
this.setState({
dropZoneValues: this.state.dropZoneValues.concat(layout),
});
this.setState({
dropzones: this.state.dropzones.concat(dropzone),
});
}
swapItems(i1, i2, y) {
console.log('swapping', i1, i2);
var height = y < 0 ? this.state.dropzones[i1].layout.height : -this.state.dropzones[i1].layout.height;
Animated.spring(
this.state.dropzones[i2].state.pan,
{toValue:{
x: 0,
y: -y-height
}}
).start();
var clone = this.state.items.clone();
console.log(clone);
clone.swap(i1, i2);
console.log(clone);
this.setState({
items: clone
});
}
render() {
console.log('state', this.state);
return (
<View style={styles.container}>
{this.state.items.map((i, index) =>
<Draggable key={index}
dropZoneValues={this.state.dropZoneValues}
setDropZoneValues={this.setDropZoneValues.bind(this)}
char={i}
index={index}
swapItems={this.swapItems.bind(this)}
/>
)}
<View style={{ zIndex: 100, backgroundColor: 'red' }}><Text>foo</Text></View>
<View style={{ zIndex: -100, top: -10, backgroundColor: 'blue' }}><Text>bar</Text></View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'orange',
justifyContent: 'center',
alignItems: 'center',
},
dropzone: {
// margin: 5,
zIndex: -100,
width: 106,
height: 106,
borderColor: 'green',
borderWidth: 3,
backgroundColor: 'lightgreen',
},
draggable: {
backgroundColor: 'white',
justifyContent: 'center',
alignItems: 'center',
width: 100,
height: 100,
borderWidth: 1,
borderColor: 'black'
},
image: {
width: 75,
height: 75
}
});
export default Playground;
EDIT2: zIndex
は唯一の子の兄弟に影響を与え、私が代わりにAnimated.View
の親(緑箱)の上に置く必要がありました。
スワップが半分しか機能しなかったのは、addDropzone
にレイアウトを追加していたため、inDropzone
で使用できないことがありました。私がレイアウトを並べ替えると、inDropzone
が私の予想通りに動作します。
全体として、この全体のことはまだGIANT HACKのように感じられます。実際に自分が行っていることを知っている誰かが私の実装で欠陥を見て改善することができれば、それは本当に素晴らしいでしょう。また、プレビューをするのもいいでしょう。そのため、ドロップゾーンをドラッグすると、変更しようとしているものの一時的なスワップや、考えられるその他の有用なビジュアルインジケータが表示されます。ドラッグ、ドロップ、およびスワップは、モバイルアプリにとっては非常に一般的な機能であり、その中の唯一のライブラリは垂直リストでのみ機能します。これを写真のグリッドにする必要があったので、これをゼロから実装する必要がありました。
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Image,
PanResponder,
Animated,
Alert,
} from 'react-native';
import _ from 'lodash';
class Draggable extends Component {
constructor(props) {
super(props);
this.state = {
pan: new Animated.ValueXY(),
scale: new Animated.Value(1),
zIndex: 0,
backgroundColor: 'white',
};
}
handleOnLayout(event) {
const { addDropzone } = this.props;
const { layout } = event.nativeEvent;
this.layout = layout;
addDropzone(this, layout);
}
componentWillMount() {
const { inDropzone, swapItems, index } = this.props;
this._panResponder = PanResponder.create({
onMoveShouldSetResponderCapture:() => true,
onMoveShouldSetPanResponderCapture:() => true,
onPanResponderGrant: (e, gestureState) => {
console.log('moving', index);
this.state.pan.setOffset({ x: this.state.pan.x._value, y: this.state.pan.y._value });
this.state.pan.setValue({ x: 0, y: 0 });
Animated.spring(this.state.scale, { toValue: 0.75, friction: 3 }).start();
this.setState({ backgroundColor: 'deepskyblue', zIndex: 1 });
},
onPanResponderMove: Animated.event([null, { dx: this.state.pan.x, dy: this.state.pan.y }]),
onPanResponderRelease: (e, gesture) => {
this.state.pan.flattenOffset();
Animated.spring(this.state.scale, { toValue: 1 }).start();
this.setState({ backgroundColor: 'white', zIndex: 0 });
let dropzone = inDropzone(gesture);
if (dropzone) {
console.log('in dropzone', dropzone.index);
// adjust into place
Animated.spring(this.state.pan, { toValue: {
x: dropzone.x - this.layout.x,
y: dropzone.y - this.layout.y,
} }).start();
if (index !== dropzone.index) {
swapItems(index, dropzone.index);
}
}
Animated.spring(this.state.pan, { toValue: { x: 0, y: 0 } }).start();
}
});
}
render() {
const { pan, scale, zIndex, backgroundColor } = this.state;
const [translateX, translateY] = [pan.x, pan.y];
const rotate = '0deg';
const imageStyle = {
transform: [{ translateX }, { translateY }, { rotate }, { scale }],
};
return (
<View
style={[styles.dropzone, { zIndex }]}
onLayout={event => this.handleOnLayout(event)}
>
<Animated.View
{...this._panResponder.panHandlers}
style={[imageStyle, styles.draggable, { backgroundColor }]}
>
<Image style={styles.image} source={{ uri: this.props.item }} />
</Animated.View>
</View>
);
}
}
const swap = (array, fromIndex, toIndex) => {
const newArray = array.slice(0);
newArray[fromIndex] = array[toIndex];
newArray[toIndex] = array[fromIndex];
return newArray;
}
class Playground extends Component {
constructor(props) {
super(props);
this.state = {
items: [
'https://files.graphiq.com/465/media/images/t2/Shiba_Inu_5187048.jpg',
'https://i.ytimg.com/vi/To8oesttqc4/hqdefault.jpg',
'https://vitaminsforpitbulls.com/wp-content/uploads/2013/06/english-bulldog-puppy-for-sale-909x1024.jpg',
'https://s-media-cache-ak0.pinimg.com/236x/20/16/e6/2016e61e8642c8aab60c71f6e3bcd004.jpg',
'https://pbs.twimg.com/profile_images/446566229210181632/2IeTff-V.jpeg',
'https://s-media-cache-ak0.pinimg.com/236x/fa/7b/18/fa7b185924d9d4d14a0623bc567f4e87.jpg',
],
dropzones: [],
dropzoneLayouts: [],
};
}
addDropzone(dropzone, dropzoneLayout) {
const { items, dropzones, dropzoneLayouts } = this.state;
// HACK: to make sure setting state does not re-add dropzones
if (items.length !== dropzones.length) {
this.setState({
dropzones: [...dropzones, dropzone],
dropzoneLayouts: [...dropzoneLayouts, dropzoneLayout],
});
}
}
inDropzone(gesture) {
const { dropzoneLayouts } = this.state;
// HACK: with the way they are added, sometimes the layouts end up out of order, so we need to sort by y,x (x,y doesn't work)
const sortedDropzoneLayouts = _.sortBy(dropzoneLayouts, ['y', 'x']);
let inDropzone = false;
sortedDropzoneLayouts.forEach((dropzone, index) => {
const inX = gesture.moveX > dropzone.x && gesture.moveX < dropzone.x + dropzone.width;
const inY = gesture.moveY > dropzone.y && gesture.moveY < dropzone.y + dropzone.height;
if (inX && inY) {
inDropzone = dropzone;
inDropzone.index = index;
}
});
return inDropzone;
}
swapItems(fromIndex, toIndex) {
console.log('swapping', fromIndex, '<->', toIndex);
const { items, dropzones } = this.state;
this.setState({
items: swap(items, fromIndex, toIndex),
dropzones: swap(dropzones, fromIndex, toIndex),
});
}
render() {
console.log(this.state);
return (
<View style={styles.container}>
{this.state.items.map((item, index) =>
<Draggable key={index}
item={item}
index={index}
addDropzone={this.addDropzone.bind(this)}
inDropzone={this.inDropzone.bind(this)}
swapItems={this.swapItems.bind(this)}
/>
)}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 60,
backgroundColor: 'orange',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
flexWrap: 'wrap',
},
dropzone: {
// margin: 5,
zIndex: -1,
width: 106,
height: 106,
borderColor: 'green',
borderWidth: 3,
backgroundColor: 'lightgreen',
},
draggable: {
backgroundColor: 'white',
justifyContent: 'center',
alignItems: 'center',
width: 100,
height: 100,
borderWidth: 1,
borderColor: 'black'
},
image: {
width: 75,
height: 75
}
});
export default Playground;
EDIT3:したがって、上記のは、シミュレータで素晴らしい作品が、実際のiPhone上で非常に遅いです。開始時に読み込むには時間がかかりすぎて何か(〜3秒)をドラッグして項目を入れ替える(約1秒)ことができます。理由を理解しようとしています(おそらく、配列の並べ替え/ループの回数が多すぎるかもしれませんが、それ以外の方法はわかりません)。私はそれが実際の電話でどのくらい大幅に遅いと信じられませんでした。
LATEST:私はちょうど私が間違ったことを見つけるためにこれらの実装https://github.com/ollija/react-native-sortable-grid,https://github.com/fangwei716/30-days-of-react-native#day-18を研究/使用するつもりです。彼らは見つけることが非常に困難でした(そうでなければ、これを最初からやっていないでしょうし、この質問を投稿しました)ので、誰かが自分のアプリで同じことをしようとしている人を助けてくれることを願っています!
Wow https://github.com/ollija/react-native-sortable-grid! – kayla
私は暗闇の中でちょうどくすんでいるのではなく、以前これについて知りたがっています。https://github.com/fangwei716/30-days-of-react-native#day-18 とにかく、このリンクと上記のリンクは、アプリでこれをしようとしている人を助けるはずです。 私は自分の実装を勉強して間違ったことを見つけます。 – kayla