2016-10-30 10 views
9

私はドラッグアンドドロップの部分を持っているようだが、スワップ場所を行う方法を知らない。また、Zインデックスの問題を修正する方法もわかりません(Animated.Viewで怪しいものがあるようです)。アイテムのアニメーションをドラッグ、ドロップ、スワップしますか?

enter image description here

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に更新されているのを見ることができますが、それは有効に見えません。青に色を変えることはうまくいくようだ...私は混乱している。

enter image description here

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のように感じられます。実際に自分が行っていることを知っている誰かが私の実装で欠陥を見て改善することができれば、それは本当に素晴らしいでしょう。また、プレビューをするのもいいでしょう。そのため、ドロップゾーンをドラッグすると、変更しようとしているものの一時的なスワップや、考えられるその他の有用なビジュアルインジケータが表示されます。ドラッグ、ドロップ、およびスワップは、モバイルアプリにとっては非常に一般的な機能であり、その中の唯一のライブラリは垂直リストでのみ機能します。これを写真のグリッドにする必要があったので、これをゼロから実装する必要がありました。

enter image description here

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を研究/使用するつもりです。彼らは見つけることが非常に困難でした(そうでなければ、これを最初からやっていないでしょうし、この質問を投稿しました)ので、誰かが自分のアプリで同じことをしようとしている人を助けてくれることを願っています!

+1

Wow https://github.com/ollija/react-native-sortable-grid! – kayla

+0

私は暗闇の中でちょうどくすんでいるのではなく、以前これについて知りたがっています。https://github.com/fangwei716/30-days-of-react-native#day-18 とにかく、このリンクと上記のリンクは、アプリでこれをしようとしている人を助けるはずです。 私は自分の実装を勉強して間違ったことを見つけます。 – kayla

答えて

0

パフォーマンスの問題では、まずDirect Manipulationを使用することをお勧めします。あなたの画像を変換したいときは、setNativePropsでそれを実行する必要があります。

this.refs['YOUR_IMAGE'].setNativeProps({style: { 
    transform: [{ translateX }, { translateY }, { rotate }, { scale }], 
}}); 

では反応し、ネイティブ、我々は2つのレルム、JavaScriptとネイティブ側を持っている、と私たちはそれらの間のブリッジを持っています。

ここで、React Nativeのパフォーマンスを理解するための主要なキーの1つがあります。各領域はそれだけで高速です。パフォーマンスのボトルネックは、あるレルムから別のレルムに移動するときによく発生します。 Reactネイティブアプリのパフォーマンスを設計するには、ブリッジを最小限に抑える必要があります。

hereで詳細を読むことができます。

第2に、パフォーマンスモニタ(デバイスを振る、またはCommand-Dを表示し、パフォーマンスモニタを表示する)を参照してください。重要なのはビューです。上の数字は画面のビュー数とボトム数は通常は大きくなりますが、通常は改善/リファクタリングできるものがあることを示しています。

関連する問題