2016-10-30 11 views
9

Wydaje mi się, że część "przeciągnij i upuść" działa, ale nie wiem, jak wykonywać zamienniki. Również nie wiesz, jak rozwiązać problem z-index (wydaje się, że robi coś podejrzanego z Animated.View).Przeciągnij, upuść i zamień animację elementów?

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: zrobiłem próbę na zamiany, ale wydaje się tylko do pracy o pół godziny. Ponadto zIndex wciąż doprowadza mnie do szaleństwa. Drukuję stan taki jak {color} {zIndex}, więc możesz go zaktualizować do 100, ale wydaje się, że nie działa. Zmiana koloru na niebieski wydaje się jednak działać ... jestem zdezorientowany.

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 wpływa tylko rodzeństwo dzieci, więc musiałem umieścić go na rodzica (zielona ramka) zamiast Animated.View.

Powód, dla którego zamiana działała tylko przez połowę czasu, polegał na tym, że ze względu na sposób, w jaki dodawałem układy w addDropzone, czasami kończyły się one nieprzydatnością do użycia w inDropzone. Kiedy sortuję układy, inDropzone działa tak, jak bym się spodziewał.

Ogólnie rzecz biorąc, cała ta kwestia nadal wygląda jak GIANT HACK, więc jeśli ktoś, kto rzeczywiście wie, co robi, widzi błędy w mojej implementacji i może ją poprawić, to byłoby naprawdę świetnie. Przydałby się również podgląd, więc po przeciągnięciu nad strefą zrzutu pokazuje tymczasową wymianę tego, co ma się zmienić, lub inne użyteczne wskaźniki wizualne, które można wymyślić. Przeciąganie, upuszczanie i zamiana to bardzo powszechna funkcja aplikacji mobilnej, a jedyna dostępna tam biblioteka działa tylko na liście pionowej. Musiałem to zaimplementować od zera, ponieważ chciałem zrobić z tego sieć zdjęć.

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: Więc powyższe działa świetnie w symulatorze, ale jest bardzo powolny na rzeczywiste iPhone. Ładowanie zajmuje zbyt wiele czasu, zanim możesz przeciągnąć coś (~ 3 sekundy) i zamarza podczas zamiany przedmiotów (~ 1 sekunda). Próbuję dowiedzieć się dlaczego (prawdopodobnie moja straszna implementacja sortowania/pętli nad tablicami zbyt wiele razy, ale nie wiem jak inaczej to zrobić). Nie mogłem uwierzyć, jak znacznie wolniej jest na prawdziwym telefonie.

NAJNOWSZE: Idę na studia/wykorzystać te implementacje https://github.com/ollija/react-native-sortable-grid, https://github.com/fangwei716/30-days-of-react-native#day-18 aby dowiedzieć się, co zrobiłem źle. Bardzo trudno było je znaleźć (inaczej nie zrobiłbym tego od zera i nie napisałem tego pytania), więc mam nadzieję, że pomoże to komuś, kto próbuje zrobić to samo dla swojej aplikacji!

+1

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

+0

Naprawdę chciałbym wiedzieć o tym wcześniej, zamiast po prostu grzebać w ciemności: https://github.com/fangwei716/30-days-of-react-native#day-18 W każdym razie, zapomnij o moim rozwiązaniu, ale ten link i powyższy link powinny pomóc osobom, które próbują to zrobić za pomocą swoich aplikacji. Dowiesz się, co zrobiłem źle, analizując ich implementacje. – kayla

Odpowiedz

0

Najpierw w celu zwiększenia wydajności, sugeruję użycie Direct Manipulation. Gdy chcesz, aby przekształcić swoje obrazy, trzeba to zrobić z setNativeProps:

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

W reagować-rodzimy mamy dwa światy, JavaScript oraz Native niepożądane, a mamy pomost pomiędzy nimi.

Tu leży jeden z głównych kluczy do zrozumienia działania Native Native. Każde królestwo samo w sobie jest szybkie. Wąskie gardło wydajności występuje często, gdy przechodzimy z jednej sfery do drugiej. Aby zaprojektować wydajność React Native aplikacje, musimy przejść przez most do minimum.

Możesz przeczytać więcej na przykładach here.

Po drugie, zobacz monitor wydajności (potrząśnij urządzeniem lub Command-D i wybierz opcję Pokaż monitor monitora). Ważną częścią są widoki, górna liczba to liczba wyświetleń na ekranie, a liczba dolna jest zwykle większa, ale zwykle oznacza, że ​​masz coś, co można poprawić/udoskonalić.

Powiązane problemy