ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React Native] 사진개수에 따른 레이아웃 변경 (페이스북 게시물 형태)
    ► React Native/개발일기 2023. 6. 23. 22:24
    반응형

    페이스북에서 사진이 게시될 때의 레이아웃을 그대로 구현하고 싶었다.
    페이스북처럼 1~6개이상일경우에 따라, 각각 다른 사진 레이아웃을 적용하고 싶었다.
     
    그래서 라이브러리를 찾아보았는데,
    마땅한 라이브러리를 찾지 못해 직접 구현했다.
    (혹시 라이브러리 아시는 분들은…. 알려주세여 ㅜㅜㅜ)


    📍 구현코드

    나중에 다시 쓸 수 있게 참고하기 위해 코드를 적어놨다…
    나만 알아볼 수 있게찌 ….?;;;
     
    PictureViewer.js 파일에 ‘사진 레이아웃’을 구현하였다.
    ImageViewer 라이브러리를 활용하여, 클릭한 이미지를 전체화면으로 띄웠다.
    PicsLayout.js 파일을 만들어, 1~6개이상의 사진갯수마다의 레이아웃을 각각의 컴포넌트를 만들어 모아놨다.
     
    ▼ PictureViewer.js

    import React, { useState } from 'react';
    import { StyleSheet } from 'react-native';
    import { Layout, Icon, Button } from '@ui-kitten/components';
    import Modal from 'react-native-modal';
    import {
      heightPercentageToDP as hp,
      widthPercentageToDP as wp,
    } from '../../utils/ResponsiveScreen';
    import ImageViewer from 'react-native-image-zoom-viewer';
    
    const PictureViewer = () => {
    
      // [사진모음 배열] 구조
      const picsData = [
        {
          Created_Date: '2023-04-06 09:56:18',
          Public_Link:
            'https://images.pexels.com/photos/786357/pexels-photo-786357.jpeg?auto=compress&cs=tinysrgb&w=800',
          Title: '다운로드',
        },
        {
          Created_Date: '2023-04-06 09:56:18',
          Public_Link:
            'https://images.pexels.com/photos/786357/pexels-photo-786357.jpeg?auto=compress&cs=tinysrgb&w=800',
          Title: '다운로드',
        },
        {
          Created_Date: '2023-04-06 09:56:18',
          Public_Link:
            'https://images.pexels.com/photos/786357/pexels-photo-786357.jpeg?auto=compress&cs=tinysrgb&w=800',
          Title: '다운로드',
        },
      ];
    
      // 사진뷰어 기본값
      const [pictureViewer, setPictureViewer] = useState({
        visible: false, //  불린값
        item: {}, //  선택사진 객체
        itemList: [], //  사진모음 배열
      });
    
      // 사진뷰어 창닫기 기능
      const onViewerClose = () => {
        setPictureViewer({ viewerVisible: false, item: {}, itemList: [] });
      };
    
      // 사진 이미지 뷰어
      const handlePictureView = (item, itemList) => {
        setPictureViewer({
          viewerVisible: true,
          item: {선택사진 객체},
          itemList: [사진모음 배열],
        });
      };
    
      const images = pictureViewer.itemList.map((v, i, arr) => {
        return {
          url: v.Public_Link,
          props: {
            index: i,
          },
        };
      });
      return (
        <>
          <PicFBPackage
            data={[사진모음 배열]}  // 사진배열
            setPictureView={handlePictureView}  // 선택한 사진의 뷰어를 여는 기능
          />
          <Modal
            style={}
            isVisible={불린값}
            animationIn={'fadeIn'}
            animationOut={'fadeOut'}>
            <ImageViewer
              imageUrls={images}
              index={[사진모음 배열].indexOf({선택사진 객체})}
            />
            <Button onPress={() => onViewerClose()}>닫기</Button>
          </Modal>
        </>
      );
    };
    
    export { PictureViewer };

    ▼ PicsLayout.js

    import React, { useEffect, useState } from 'react';
    import { default as _color } from '../../theme/colorTheme.json';
    import { Text, Image, StyleSheet, TouchableOpacity } from 'react-native';
    import { Layout } from '@ui-kitten/components';
    import {
      heightPercentageToDP as hp,
      widthPercentageToDP as wp,
    } from '../../utils/ResponsiveScreen';
    
    const PicFB1 = ({
      data,
      handlePictureView,
      stylesTouchablePics,
      stylesImage,
    }) => {
      const styles = StyleSheet.create({
        touchablePics: {},
        image: {
          flexDirection: 'row',
          flex: 1,
          borderWidth: 1.5,
          borderColor: _color['color-background-100'],
          height: wp('100%'),
          resizeMode: 'cover',
        },
      });
      const renderItem = data.map((value, idx) => (
        <TouchableOpacity
          onPress={() => handlePictureView(value, data)}
          style={{
            ...styles.touchablePics,
            stylesTouchablePics,
          }}>
          <Image
            source={{
              uri: value.Public_Link ? value.Public_Link : '',
            }}
            style={{
              ...styles.image,
              ...stylesImage,
            }}
          />
        </TouchableOpacity>
      ));
      return <>{renderItem}</>;
    };
    
    const PicFB2 = ({
      data,
      handlePictureView,
      stylesPicsContainer,
      stylesTouchablePics,
      stylesImage,
    }) => {
      const styles = StyleSheet.create({
        picsContainer: {
          flexDirection: 'row',
          justifyContent: 'space-between',
        },
        touchablePics: {},
        image: {
          borderWidth: 1.5,
          borderColor: _color['color-background-100'],
          height: wp('44.5%'),
          width: wp('44.5%'),
          resizeMode: 'cover',
        },
      });
      const renderItem = data.map((value, idx) => (
        <TouchableOpacity
          onPress={() => handlePictureView(value, data)}
          style={{
            ...styles.touchablePics,
            stylesTouchablePics,
          }}>
          <Image
            source={{
              uri: value.Public_Link ? value.Public_Link : '',
            }}
            style={{
              ...styles.image,
              ...stylesImage,
            }}
          />
        </TouchableOpacity>
      ));
      return (
        <>
          <Layout
            style={{
              ...styles.picsContainer,
              stylesPicsContainer,
            }}>
            {renderItem}
          </Layout>
        </>
      );
    };
    
    const PicFB3 = ({
      data,
      stylesPicsContainer,
      stylesPicsContainerL,
      stylesPicsContainerR,
      stylesImageL,
      stylesImageR,
    
      handlePictureView,
    }) => {
      const [arrPicsR, setArrPicsR] = useState([]);
    
      useEffect(() => {
        let makeArrPicsR = data.slice(1, 3);
        setArrPicsR(makeArrPicsR);
      }, [data]);
    
      const styles = StyleSheet.create({
        picsContainer: {
          flexDirection: 'row',
          justifyContent: 'space-between',
        },
        picsContainerL: {},
        picsContainerR: { justifyContent: 'space-between' },
        imageL: {
          borderWidth: 1.5,
          borderColor: _color['color-background-100'],
          height: wp('89%'),
          width: wp('44.5%'),
          resizeMode: 'cover',
        },
        imageR: {
          borderWidth: 1.5,
          borderColor: _color['color-background-100'],
          height: wp('44%'),
          width: wp('44.5%'),
          resizeMode: 'cover',
        },
      });
      const picL = (
        <TouchableOpacity
          onPress={() => handlePictureView(data[0], data)}
          style={{}}>
          <Image
            source={{
              uri: data[0].Public_Link ? data[0].Public_Link : '',
            }}
            style={{
              ...styles.imageL,
              ...stylesImageL,
            }}
          />
        </TouchableOpacity>
      );
    
      const picR = arrPicsR.map((value, idx) => (
        <TouchableOpacity onPress={() => handlePictureView(value, data)} style={{}}>
          <Image
            source={{
              uri: value.Public_Link ? value.Public_Link : '',
            }}
            style={{
              ...styles.imageR,
              ...stylesImageR,
            }}
          />
        </TouchableOpacity>
      ));
      return (
        <>
          <Layout
            style={{
              ...styles.picsContainer,
              stylesPicsContainer,
            }}>
            <Layout
              style={{
                ...styles.picsContainerL,
                stylesPicsContainerL,
              }}>
              {picL}
            </Layout>
            <Layout
              style={{
                ...styles.picsContainerR,
                stylesPicsContainerR,
              }}>
              {picR}
            </Layout>
          </Layout>
        </>
      );
    };
    
    const PicFB4 = ({
      data,
      handlePictureView,
      stylesPicsContainer,
      stylesTouchablePics,
      stylesImage,
    }) => {
      const styles = StyleSheet.create({
        picsContainer: {
          flexDirection: 'row',
          flexWrap: 'wrap',
          justifyContent: 'space-between',
        },
        touchablePics: {},
        image: {
          borderWidth: 1.5,
          borderColor: _color['color-background-100'],
          height: wp('44.5%'),
          width: wp('44.5%'),
          resizeMode: 'cover',
        },
      });
      const renderItem = data.map((value, idx) => (
        <TouchableOpacity
          onPress={() => handlePictureView(value, data)}
          style={{
            ...styles.touchablePics,
            stylesTouchablePics,
          }}>
          <Image
            source={{
              uri: value.Public_Link ? value.Public_Link : '',
            }}
            style={{
              ...styles.image,
              ...stylesImage,
            }}
          />
        </TouchableOpacity>
      ));
      return (
        <>
          <Layout
            style={{
              ...styles.picsContainer,
              stylesPicsContainer,
            }}>
            {renderItem}
          </Layout>
        </>
      );
    };
    
    const PicFB5 = ({
      data,
      stylesPicsContainer,
      stylesPicsGroup,
      stylesImage1,
      stylesImage2,
    
      handlePictureView,
    }) => {
      const [arrPics1, setArrPics1] = useState([]);
      const [arrPics2, setArrPics2] = useState([]);
    
      useEffect(() => {
        let makeArrPics1 = data.slice(0, 2);
        setArrPics1(makeArrPics1);
      }, [data]);
      useEffect(() => {
        let makeArrPics2 = data.slice(2, 5);
        setArrPics2(makeArrPics2);
      }, [data]);
    
      const styles = StyleSheet.create({
        picsContainer: {
          height: wp('75%'),
          justifyContent: 'space-between',
        },
        picsGroup: {
          flexDirection: 'row',
          justifyContent: 'space-between',
        },
        image1: {
          borderWidth: 1.5,
          width: wp('44.5%'),
          height: wp('44.5%'),
          resizeMode: 'cover',
          borderColor: _color['color-background-100'],
        },
        image2: {
          borderWidth: 1.5,
          width: wp('29.5%'),
          resizeMode: 'cover',
          height: wp('29.5%'),
          borderColor: _color['color-background-100'],
        },
      });
      const renderPic1 = arrPics1.map((value, idx) => (
        <TouchableOpacity onPress={() => handlePictureView(value, data)} style={{}}>
          <Image
            source={{
              uri: value.Public_Link ? value.Public_Link : '',
            }}
            style={{
              ...styles.image1,
              ...stylesImage1,
            }}
          />
        </TouchableOpacity>
      ));
      const renderPic2 = arrPics2.map((value, idx) => (
        <TouchableOpacity onPress={() => handlePictureView(value, data)} style={{}}>
          <Image
            source={{
              uri: value.Public_Link ? value.Public_Link : '',
            }}
            style={{
              ...styles.image2,
              ...stylesImage2,
            }}
          />
        </TouchableOpacity>
      ));
    
      return (
        <>
          <Layout
            style={{
              ...styles.picsContainer,
              stylesPicsContainer,
            }}>
            <Layout
              style={{
                ...styles.picsGroup,
                stylesPicsGroup,
              }}>
              {renderPic1}
            </Layout>
            <Layout
              style={{
                ...styles.picsGroup,
                stylesPicsGroup,
              }}>
              {renderPic2}
            </Layout>
          </Layout>
        </>
      );
    };
    
    const PicFBOver6 = ({
      data,
      handlePictureView,
      stylesPicsContainer,
      stylesTouchablePics,
      stylesImage,
    }) => {
      const styles = StyleSheet.create({
        picsContainer: {
          flexDirection: 'row',
          flexWrap: 'wrap',
        },
        touchablePics: { marginRight: wp('0.5%') },
        image: {
          borderWidth: 1.5,
          borderColor: _color['color-background-100'],
          height: wp('29.5%'),
          width: wp('29.5%'),
          resizeMode: 'cover',
        },
      });
      const renderItem = data.map((value, idx) => (
        <TouchableOpacity
          onPress={() => handlePictureView(value, data)}
          style={{
            ...styles.touchablePics,
            stylesTouchablePics,
          }}>
          <Image
            source={{
              uri: value.Public_Link ? value.Public_Link : '',
            }}
            style={{
              ...styles.image,
              ...stylesImage,
            }}
          />
        </TouchableOpacity>
      ));
      return (
        <>
          <Layout
            style={{
              ...styles.picsContainer,
              stylesPicsContainer,
            }}>
            {renderItem}
          </Layout>
        </>
      );
    };
    
    const PicFBPackage = ({ data, setPictureView }) => {
      return (
        <>
          
    {/* 조건식에서,
    [ ] 빈 배열도 True를 리턴하기 때문에 (값이 존재하다고 인식함), data.length 로 수정하였다.
    data.length는 [ ] 빈 배열을 받으면, False로 리턴한다.
    그 다음에는 null일 경우, 에러가 발생하여 (옵셔널체이닝)data?.length으로 변경해 주엇다 */}
    
          {data?.length ? (
            (() => {
              switch (data.length) {
                case 1:
                  return <PicFB1 data={data} handlePictureView={setPictureView} />;
                case 2:
                  return <PicFB2 data={data} handlePictureView={setPictureView} />;
                case 3:
                  return <PicFB3 data={data} handlePictureView={setPictureView} />;
                case 4:
                  return <PicFB4 data={data} handlePictureView={setPictureView} />;
                case 5:
                  return <PicFB5 data={data} handlePictureView={setPictureView} />;
                default:
                  return (
                    <PicFBOver6 data={data} handlePictureView={setPictureView} />
                  );
              }
            })()
          ) : (
            <Text>사진이 존재하지 않습니다.</Text>
          )}
        </>
      );
    };
    
    export {
      PicFB1,
      PicFB2,
      PicFB3,
      PicFB4,
      PicFB5,
      PicFBOver6,
      PicFBPackage, // 사진장수에 따라, picFB1~PicFBOver6 실행됨
    };

    📍 결과물

    ▼ 사진1개, 2개, 3개일 때의 레이아웃

    ▼ 사진4개, 5개, 6개이상일 때의 레이아웃

     


    개인적으로 개발시행착오를 겪으면서, 그런 경험들을 기록하기도하고, 모은정보들을 메모하며, 개인공부내용을 공유하는 게시물입니다. 친절한 조언과 다양한 의견 남겨주시고, 소통해주시는분들은 언제든지 환영합니다 :D
     

     
     
     
     
     
     
     
     
     
     
     
     

    반응형
Designed by Tistory.