Как получить размерность нескольких представлений с помощью onLayout только с помощью одной функции в React Native?

#react-native

#react-native

Вопрос:

У меня есть сцена сетки, которая содержит 4 * 4 поля просмотра. Я хочу получить размерность каждого из них, чтобы видеть, перемещает ли пользователь по ним свой палец или нет. Я преуспел в этом процессе, написав 16 отдельных функций ‘onLayout’ для своих 16 блоков, а затем попытался рассчитать движения пользователя при касании. Кроме того, для этого я пишу пользовательский интерфейс 16 view вместо чистого цикла for.

В итоге я получил несколько грязных кодов, которые нельзя использовать динамически и которые невозможно реализовать. кто-нибудь может мне помочь очистить этот код с помощью функции и цикла?

Это то, что у меня есть:

 render() {
return (
  <View style={{justifyContent: 'center', alignItems: 'center', flex: 1}}>
      <View style={{flex : 1}}><Text>{this.state.wordObj['A']}</Text></View>
      <View onLayout={this.getExactPos} style={{flex : 4, backgroundColor: 'red', marginLeft:20,marginRight:20}} {...this._panResponder.panHandlers}  >
        <View style={{flexDirection: 'row', flex: 1 , backgroundColor: 'red', width: '100%'}}>
            <View onLayout={this.getExactPosA} style={{flex : 1, backgroundColor: 'yellow', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box1}</Text></View>
            <View onLayout={this.getExactPosB} style={{flex : 1, backgroundColor: 'orange', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box2}</Text></View>
            <View onLayout={this.getExactPosC} style={{flex : 1, backgroundColor: 'gray', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box3}</Text></View>
        </View>
        <View onLayout={this.getExactPosRow2} style={{flexDirection: 'row', flex: 1 , backgroundColor: 'blue' , width: '100%'}}>
            <View onLayout={this.getExactPosD} style={{flex : 1, backgroundColor: 'green', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box4}</Text></View>
            <View onLayout={this.getExactPosE} style={{flex : 1, backgroundColor: 'red', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box5}</Text></View>
            <View onLayout={this.getExactPosF} style={{flex : 1, backgroundColor: 'blue', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box6}</Text></View>
        </View>
        <View onLayout={this.getExactPosRow3} style={{flexDirection: 'row', flex: 1 , backgroundColor: 'green', width: '100%'}}>
            <View onLayout={this.getExactPosG} style={{flex : 1, backgroundColor: 'purple', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box7}</Text></View>
            <View onLayout={this.getExactPosH} style={{flex : 1, backgroundColor: 'skyblue', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box8}</Text></View>
            <View onLayout={this.getExactPosI} style={{flex : 1, backgroundColor: '#124567', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box9}</Text></View>
        </View>
      </View>
      <View style={{flex : 1}}><Text>3</Text></View>
    <Text>Hello world! - Home</Text>
    <TouchableOpacity style={{backgroundColor : 'red'}} onPress={this._signOutAsync}>
        <Text>Log Out</Text>
    </TouchableOpacity> 
  </View>
);  }
  

и эта часть — функции onLayout view:

 getExactPos = e => {
    const { width, height, x, y } = e.nativeEvent.layout;   
    this.setState({
      gridXstart : x,
      gridXend : x   width,
      gridYstart : y,
      gridYend : y   height,
    })
    // alert(x);
  };

  getExactPosRow2 = e => {
    const { width, height, x, y } = e.nativeEvent.layout;   
    this.setState({         
      row2Y : y,        
    })       
  };

  getExactPosRow3 = e => {
    const { width, height, x, y } = e.nativeEvent.layout;   
    this.setState({         
      row3Y : y,        
    })       
  };

  getExactPosA = e => {
    const { width, height, x, y } = e.nativeEvent.layout; 
    let newArray = this.state.a;
      newArray.gridXstart = x;
      newArray.gridXend = x   width;
      newArray.gridYstart = y;
      newArray.gridYend = y   height;
    this.setState({a: newArray});
  }; 

  getExactPosB = e => {
    const { width, height, x, y } = e.nativeEvent.layout;   
    let newArray =this.state.b;
      newArray.gridXstart = x;
      newArray.gridXend = x   width;
      newArray.gridYstart = y;
      newArray.gridYend = y   height;
    this.setState({b: newArray});
  };

  getExactPosC = e => {
    const { width, height, x, y } = e.nativeEvent.layout;   
    let newArray = this.state.c;
      newArray.gridXstart = x;
      newArray.gridXend = x   width;
      newArray.gridYstart = y;
      newArray.gridYend = y   height;
    this.setState({c: newArray});
  };

  getExactPosD = e => {
    const { width, height, x, y } = e.nativeEvent.layout;   
    let newArray = this.state.d;
      newArray.gridXstart = x;
      newArray.gridXend = x   width;
      newArray.gridYstart = y;
      newArray.gridYend = y   height;
    this.setState({d: newArray});
  };

  getExactPosE = e => {
    const { width, height, x, y } = e.nativeEvent.layout;   
    let newArray = this.state.e;
      newArray.gridXstart = x;
      newArray.gridXend = x   width;
      newArray.gridYstart = y;
      newArray.gridYend = y   height;
    this.setState({e: newArray});
  };

  getExactPosF = e => {
    const { width, height, x, y } = e.nativeEvent.layout;   
    let newArray = this.state.f;
      newArray.gridXstart = x;
      newArray.gridXend = x   width;
      newArray.gridYstart = y;
      newArray.gridYend = y   height;
    this.setState({f: newArray});
  };

  getExactPosG = e => {
    const { width, height, x, y } = e.nativeEvent.layout;   
    let newArray = this.state.g;
      newArray.gridXstart = x;
      newArray.gridXend = x   width;
      newArray.gridYstart = y;
      newArray.gridYend = y   height;
    this.setState({g: newArray});
  };

  getExactPosH = e => {
    const { width, height, x, y } = e.nativeEvent.layout;   
    let newArray = this.state.h;
      newArray.gridXstart = x;
      newArray.gridXend = x   width;
      newArray.gridYstart = y;
      newArray.gridYend = y   height;
    this.setState({h: newArray});
  };

  getExactPosI = e => {
    const { width, height, x, y } = e.nativeEvent.layout;   
    let newArray = this.state.i;
      newArray.gridXstart = x;
      newArray.gridXend = x   width;
      newArray.gridYstart = y;
      newArray.gridYend = y   height;
    this.setState({i: newArray});
    //alert('asdasdas')
  };
  

Я хочу использовать этот цикл для динамического создания моей сетки, а также одну функцию onLayout для обработки всех. :

 let rows = [];        
    let k = 0;
    for(let i = 0; i<4 ; i  ){
        let row= [];
        for(let j = 0; j<4; j  ){
           k  ;
           row.push(
            <View onLayout={this.getExactPos2} style={{flex : 1, backgroundColor: 'yellow', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box1}</Text></View>
           )     
        }
        rows.push(
          <View style={{flexDirection: 'row', flex: 1 , backgroundColor: 'red', width: '100%'}}>{row}</View>
        )
    }
  

And for detecting user touch movement on boxes(like piano) I tried to get user’s finger location on screen and compare with each boxes location. that is because I couldn’t find any solution that each box itself always listen for touch movement.

here is my code for gesture handling: I use panResponder for parent view.

 <View onLayout={this.getExactPos} style={{flex : 4, backgroundColor: 'red', marginLeft:20,marginRight:20}} {...this._panResponder.panHandlers}  >
        <View style={{flexDirection: 'row', flex: 1 , backgroundColor: 'red', width: '100%'}}>
            <View onLayout={this.getExactPosA} style={{flex : 1, backgroundColor: 'yellow', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box1}</Text></View>
            <View onLayout={this.getExactPosB} style={{flex : 1, backgroundColor: 'orange', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box2}</Text></View>
            <View onLayout={this.getExactPosC} style={{flex : 1, backgroundColor: 'gray', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box3}</Text></View>
        </View>
        <View onLayout={this.getExactPosRow2} style={{flexDirection: 'row', flex: 1 , backgroundColor: 'blue' , width: '100%'}}>
            <View onLayout={this.getExactPosD} style={{flex : 1, backgroundColor: 'green', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box4}</Text></View>
            <View onLayout={this.getExactPosE} style={{flex : 1, backgroundColor: 'red', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box5}</Text></View>
            <View onLayout={this.getExactPosF} style={{flex : 1, backgroundColor: 'blue', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box6}</Text></View>
        </View>
        <View onLayout={this.getExactPosRow3} style={{flexDirection: 'row', flex: 1 , backgroundColor: 'green', width: '100%'}}>
            <View onLayout={this.getExactPosG} style={{flex : 1, backgroundColor: 'purple', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box7}</Text></View>
            <View onLayout={this.getExactPosH} style={{flex : 1, backgroundColor: 'skyblue', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box8}</Text></View>
            <View onLayout={this.getExactPosI} style={{flex : 1, backgroundColor: '#124567', justifyContent: 'center', alignItems: 'center'}}><Text>{this.state.Box9}</Text></View>
        </View>
      </View>
  

here is my panResponder function:

 componentWillMount() {
    this._panResponder = PanResponder.create({
      onMoveShouldSetPanResponder: (evt, gestureState) => true,
      onPanResponderGrant: (evt) => {
        this.setState({
          zone: '',
        });
      },
      onPanResponderMove: (evt, gestureState) => {
        const drag = this.getDirectionAndColor(gestureState);
        this.setState({
          zone: drag ,
        });                      
      },
      onPanResponderTerminationRequest: (evt, gestureState) => true,
      onPanResponderRelease: (evt, gestureState) => {
        this.setState({
          zone: 'Finished',
        });            
      },
    });
  }
  

and here is my calculation function:

  getDirectionAndColor = ({ moveX, moveY, dx, dy, x0, y0, stateID }) => {  

          if(
            (this.state.a.gridXstart this.state.gridXstart < moveX)
            amp;amp;          
            (moveX < this.state.a.gridXend   this.state.gridXstart)
            amp;amp;
            (this.state.a.gridYstart this.state.gridYstart < moveY)
            amp;amp;
            (moveY < this.state.a.gridYend   this.state.gridYstart)     
            ){           
             return this.state.Box1;
           }else if(
             (this.state.b.gridXstart this.state.gridXstart < moveX)
             amp;amp;          
             (moveX < this.state.b.gridXend   this.state.gridXstart)
             amp;amp;
             (this.state.b.gridYstart this.state.gridYstart < moveY)
             amp;amp;
             (moveY < this.state.b.gridYend   this.state.gridYstart)
           ){
             return this.state.Box2;
           }else if(
             (this.state.c.gridXstart this.state.gridXstart < moveX)
             amp;amp;          
             (moveX < this.state.c.gridXend   this.state.gridXstart)
             amp;amp;
             (this.state.c.gridYstart this.state.gridYstart < moveY)
             amp;amp;
             (moveY < this.state.c.gridYend   this.state.gridYstart)
           ){
             return this.state.Box3;
           }else if(
             (this.state.d.gridXstart this.state.gridXstart < moveX)
             amp;amp;          
             (moveX < this.state.d.gridXend   this.state.gridXstart)
             amp;amp;
             (this.state.d.gridYstart this.state.gridYstart this.state.row2Y < moveY)
             amp;amp;
             (moveY < this.state.d.gridYend   this.state.gridYstart   this.state.row2Y)
           ){
             return this.state.Box4;
           }else if(
             (this.state.e.gridXstart this.state.gridXstart < moveX)
             amp;amp;          
             (moveX < this.state.e.gridXend   this.state.gridXstart)
             amp;amp;
             (this.state.e.gridYstart this.state.gridYstart this.state.row2Y < moveY)
             amp;amp;
             (moveY < this.state.e.gridYend   this.state.gridYstart   this.state.row2Y)
           ){
             return this.state.Box5;
           }else if(
             (this.state.f.gridXstart this.state.gridXstart < moveX)
             amp;amp;          
             (moveX < this.state.f.gridXend   this.state.gridXstart)
             amp;amp;
             (this.state.f.gridYstart this.state.gridYstart this.state.row2Y < moveY)
             amp;amp;
             (moveY < this.state.f.gridYend   this.state.gridYstart   this.state.row2Y)
           ){ 
             return this.state.Box6;
           }else if(
             (this.state.g.gridXstart this.state.gridXstart < moveX)
             amp;amp;          
             (moveX < this.state.g.gridXend   this.state.gridXstart)
             amp;amp;
             (this.state.g.gridYstart this.state.gridYstart this.state.row2Y  this.state.row2Y < moveY)
             amp;amp;
             (moveY < this.state.g.gridYend   this.state.gridYstart   this.state.row2Y  this.state.row2Y)
           ){
             return this.state.Box7;
           }else if(
             (this.state.h.gridXstart this.state.gridXstart < moveX)
             amp;amp;          
             (moveX < this.state.h.gridXend   this.state.gridXstart)
             amp;amp;
             (this.state.h.gridYstart this.state.gridYstart this.state.row2Y  this.state.row2Y < moveY)
             amp;amp;
             (moveY < this.state.h.gridYend   this.state.gridYstart   this.state.row2Y  this.state.row2Y)
           ){
             return this.state.Box8;
           }else if(
             (this.state.i.gridXstart this.state.gridXstart < moveX)
             amp;amp;          
             (moveX < this.state.i.gridXend   this.state.gridXstart)
             amp;amp;
             (this.state.i.gridYstart this.state.gridYstart this.state.row2Y  this.state.row2Y < moveY)
             amp;amp;
             (moveY < this.state.i.gridYend   this.state.gridYstart   this.state.row2Y  this.state.row2Y)
           ){
             return this.state.Box9;
           }else{
             return 'No'
           }        
  };
  

вот мое состояние:

 state = { 
    zone: "Still Touchable",      
    gridXstart : 0,
    gridXend : 0,
    gridYstart : 0,
    gridYend : 0,
    row2Y: 0,
    row3Y: 0,
    Box1: 'A',Box2: 'B', Box3: 'C', Box4: 'D', Box5: 'E',Box6: 'F',Box7: 'G', Box8: 'H', Box9: 'I',
    wordObj : {},
    a : {
      gridXstart : 0,
      gridXend : 0,
      gridYstart : 0,
      gridYend : 0,
    },
    b : {
      gridXstart : 0,
      gridXend : 0,
      gridYstart : 0,
      gridYend : 0,
    },
    c : {
      gridXstart : 0,
      gridXend : 0,
      gridYstart : 0,
      gridYend : 0,
    },
    d : {
      gridXstart : 0,
      gridXend : 0,
      gridYstart : 0,
      gridYend : 0,
    },
    e : {
      gridXstart : 0,
      gridXend : 0,
      gridYstart : 0,
      gridYend : 0,
    },
    f : {
      gridXstart : 0,
      gridXend : 0,
      gridYstart : 0,
      gridYend : 0,
    },
    g : {
      gridXstart : 0,
      gridXend : 0,
      gridYstart : 0,
      gridYend : 0,
    },
    h : {
      gridXstart : 0,
      gridXend : 0,
      gridYstart : 0,
      gridYend : 0,
    },
    i : {
      gridXstart : 0,
      gridXend : 0,
      gridYstart : 0,
      gridYend : 0,
    },         
  };
  

Пожалуйста, помогите мне несколькими советами.
Спасибо.

Ответ №1:

Вы не можете сделать это с помощью одной onLayout функции, поскольку в вашей текущей настройке вы используете две разные функции. Таким образом, это можно сделать с помощью двух. Вам просто нужно немного абстрагировать код, и тогда можно заставить его работать.

Если мы представим себе сетку, которую вы строите таким образом, мы можем легко найти каждую ячейку в сетке, если будем следовать прямому соглашению об именовании, XY где X — строка, а Y — столбец.

  ---- ---- ---- 
| 11 | 12 | 13 |
 ---- ---- ---- 
| 21 | 22 | 23 |
 ---- ---- ---- 
| 31 | 32 | 33 |
 ---- ---- ---- 
  

Используя эту идею, мы можем модифицировать ваши две onLayout функции примерно так:

 getExactPos = (e, key) => { // pass a key as well now
  const { width, height, x, y } = e.nativeEvent.layout;
  let position = {};
  position.gridXstart = x;
  position.gridXend = x   width;
  position.gridYstart = y;
  position.gridYend = y   height;
  this.setState({ [key]: position }); // notice that we use the key to store it in state
}

getExactPosRow = (e, key) => {  // pass a key as well now
  const { y } = e.nativeEvent.layout;
  this.setState({ [key]: y });  // notice that we use the key to store it in state
};
  

Мы установим ключи, используемые в этих функциях, в constructViews функции ниже. Теперь с их помощью мы можем создать функцию, которая, в свою очередь, построит сетку:

 constructViews = () => {
  let rows = [];
  for (let i = 1; i < 4; i  ) {
    let row = [];
    for (let j = 1; j < 4; j  ) {
      let stateKey = `${i}${j}`;
      let styleKey = `box${stateKey}`;
      row.push(
        <View onLayout={ (e) => this.getExactPos(e, stateKey)} style={styles[styleKey]} key={stateKey}><Text>{this.state[styleKey]}</Text></View>
      );
    }
    rows.push(
      <View onLayout={e => this.getExactPosRow(e, `${i}`)} style={styles[`row${i}`]} key={i}>{row}</View>
    );
  }
  return rows;
}
  

В этой функции у нас есть вложенная for-loop , которая создает сетку. Обратите внимание, что мы создаем и передаем ключи к двум onLayout функциям, которые мы создали. Мы можем расширить идею использования ключей дальше, динамически получая правильные стили и правильный текст.

Вот это в рабочем POC. Этого должно хватить для реализации того, что вы хотите. Я не реализовал никаких средств реагирования на жесты и оставляю это на ваше усмотрение.

 import React from 'react';
import { Text, View, StyleSheet, Button } from 'react-native';

export default class App extends React.Component {
  state = {
    box11: 'Box 11',
    box12: 'Box 12',
    box13: 'Box 13',
    box21: 'Box 21',
    box22: 'Box 22',
    box23: 'Box 23',
    box31: 'Box 31',
    box32: 'Box 32',
    box33: 'Box 33'
  }

    getExactPos = (e, key) => {
      const { width, height, x, y } = e.nativeEvent.layout;
      let position = {};
      position.gridXstart = x;
      position.gridXend = x   width;
      position.gridYstart = y;
      position.gridYend = y   height;
      this.setState({ [key]: position });
    }

  getExactPosRow = (e, key) => {
    const { y } = e.nativeEvent.layout;
    this.setState({
      [key]: y
    });
  };

  constructViews = () => {
    let rows = [];
    for (let i = 1; i < 4; i  ) {
      let row = [];
      for (let j = 1; j < 4; j  ) {
        let stateKey = `${i}${j}`;
        let styleKey = `box${stateKey}`;
        row.push(
          <View onLayout={ (e) => this.getExactPos(e, stateKey)} style={styles[styleKey]} key={stateKey}><Text>{this.state[styleKey]}</Text></View>
        );
      }
      rows.push(
        <View onLayout={e => this.getExactPosRow(e, `${i}`)} style={styles[`row${i}`]} key={i}>{row}</View>
      );
    }
    return rows;
  }

  render () {
    return (
      <View style={{ justifyContent: 'center', alignItems: 'center', flex: 1 }}>
        <View style={{ flex: 1 }}></View>
        <Button onPress={() => console.log(this.state)} title={'console log state'} />
        <View style={{ flex: 4, backgroundColor: 'red', marginLeft: 20, marginRight: 20 }} >
          {this.constructViews()}
        </View>
        <View style={{ flex: 1 }}></View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  row1: { flexDirection: 'row', flex: 1, backgroundColor: 'red', width: '100%' },
  row2: { flexDirection: 'row', flex: 1, backgroundColor: 'blue', width: '100%' },
  row3: { flexDirection: 'row', flex: 1, backgroundColor: 'green', width: '100%' },
  box11: { flex: 1, backgroundColor: 'yellow', justifyContent: 'center', alignItems: 'center' },
  box12: { flex: 1, backgroundColor: 'orange', justifyContent: 'center', alignItems: 'center' },
  box13: { flex: 1, backgroundColor: 'gray', justifyContent: 'center', alignItems: 'center' },
  box21: { flex: 1, backgroundColor: 'green', justifyContent: 'center', alignItems: 'center' },
  box22: { flex: 1, backgroundColor: 'red', justifyContent: 'center', alignItems: 'center' },
  box23: { flex: 1, backgroundColor: 'blue', justifyContent: 'center', alignItems: 'center' },
  box31: { flex: 1, backgroundColor: 'purple', justifyContent: 'center', alignItems: 'center' },
  box32: { flex: 1, backgroundColor: 'skyblue', justifyContent: 'center', alignItems: 'center' },
  box33: { flex: 1, backgroundColor: '#124567', justifyContent: 'center', alignItems: 'center' }
});
  

Здесь вы можете увидеть, как это работает в этой закуске https://snack.expo.io/@andypandy/dynamic-grid-using-onlayout

Комментарии:

1. Это работает как шарм, я также пытался определять движение пальца пользователя по блокам, и поскольку я не мог найти никакого решения, которое отслеживало бы движение каждого блока, поэтому я использовал их местоположение и местоположение движения пальца пользователя, чтобы узнать, где это находится. Я хочу иметь опыт работы с пианино. итак, я помещу свой код здесь, если вы могли бы, пожалуйста, помочь мне улучшить его или использовать другое решение.

2. Я добавляю свой код обработчика жестов к вопросу. У меня есть две проблемы для реализации этого с вашим решением: 1- как использовать другой цикл for, чтобы проверить, где находится палец пользователя. 2- Мне нужно добавить состояние блоков после того, как я получу размер сетки. может быть, 4 * 4, может быть, 10 * 10, … поэтому я не могу определить состояние полей в своем коде. также мне нужно определить это в этом цикле.

3. 1) Я не уверен, как вы хотите реализовать обработчик жестов, поэтому я не могу ответить на этот вопрос. 2) приведенный выше код можно дополнительно абстрагировать для обработки сетки любого размера. Вам просто нужно установить значения перед вызовом цикла, поэтому это должно быть сделано в конструкторе или componentDidMount. Я думаю, у вас есть возможность выяснить, как выполнить следующую абстракцию. Просто подумайте, с какой основной проблемой вы сталкиваетесь, если меняете размеры сетки. Нужно решить только две проблемы. Спросите себя, каковы начальный текст и стили для каждого поля?