[SKKU DT] 46일차 -유니티 공공API 데이터 활용으로 Scroll View UI 만들기 -더보기 버튼, 디테일 패널(Detail Panel)

2024. 1. 5. 15:06SKKU DT

728x90
반응형

JSON의 Key 값에 띄어쓰기가 있거나 괄호가 있는 경우, 다른 JSON Parser를 이용하면 된다. 다음 주에 예제와 같이 알아볼 예정.

 

이전에는 한 페이지의 내용만 불러오도록 했는데, 다른 페이지의 내용까지 불러오도록 적용해본다. 더보기 버튼 눌러서 10개를 추가로 목록 아래에 붙여넣는다.

 

 

기존에 썼던 LegacyCell을 복사하고 이름을 바꾼다.

 

 

Image 컴포넌트 아래의 불필요한 컴포넌트들을 다 지우고(Vertical Layout 등) Hierarchy상에 텍스트도 지우고 버튼을 하나 추가한다. 버튼을 stretch 시켜서 셀을 다 덮도록 한다.

 

 

더보기 버튼의 디자인을 수정하고 MoreButtonCellController 스크립트를 만든다.

 

Content 아래에 여러 셀을 생성해서 간격과 크기를 본다.

 

 

LegacyDataManager 스크립트에 MoreButtonCellController 관련 내용 추가

SerializeField] private MoreButtonCellController moreButtonPrefab;

 

전체코드

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Networking;

public class LegacyDataManager : MonoBehaviour
{
    [SerializeField] private string serviceKey; //서비스 키는 유출되지 않게 하기 위해서 Scene에서 관리할 예정이다.
    [SerializeField] private LegacyCellController legacyCellPrefab;
    [SerializeField] private MoreButtonCellController moreButtonPrefab;
    [SerializeField] private Transform content;

    private void Start()
    {
        StartCoroutine(LoadData(1));
    }
    IEnumerator LoadData(int page) //몇 번째 page를 로드할 것인지
    {
        //서버 URL 설정
        string url = string.Format("{0}?page={1}&perPage={2}&serviceKey={3}", Constants.url, page, Constants.perPage, serviceKey);//여러 개의 문자열을 조합할 수 있다.

        UnityWebRequest request = new UnityWebRequest();
        using (request = UnityWebRequest.Get(url))
        {
            yield return request.SendWebRequest();

            if(request.result != UnityWebRequest.Result.Success)
            {
                Debug.Log(request.error);
            }
            else
            {
                string result = request.downloadHandler.text; //텍스트 형식으로 받아오기. 많이 불러왔었던 빼곡한 글들로 불러온다.

                LegacyData legacyData = JsonUtility.FromJson<LegacyData>(result); //파싱에서 가져온 이름 LegacyData
                Legacy[] legacyArray = legacyData.data;

                for(int i = 0; i < legacyArray.Length; i++)
                {
                    LegacyCellController legacyCellController = Instantiate(legacyCellPrefab, content);
                    legacyCellController.SetData(legacyArray[i].명칭, legacyArray[i].종목, legacyArray[i].지정일);
                }
                //for 루프가 끝나는 지점에 더보기 버튼 추가
                MoreButtonCellController moreButtonCellController = Instantiate(moreButtonPrefab, content);
            }
        }
    }
}

 

 

컴포넌트에서도 드래그 앤 드랍하면 실행했을 때 더보기 버튼이 나온다.

 

 


 

 

LegacyDataManager 스크립트 수정(더보기 버튼 생성 부분)

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Networking;

public class LegacyDataManager : MonoBehaviour
{
    [SerializeField] private string serviceKey; //서비스 키는 유출되지 않게 하기 위해서 Scene에서 관리할 예정이다.
    [SerializeField] private LegacyCellController legacyCellPrefab;
    [SerializeField] private MoreButtonCellController moreButtonPrefab;
    [SerializeField] private Transform content;

    private void Start()
    {
        StartCoroutine(LoadData(1));
    }
    IEnumerator LoadData(int page) //몇 번째 page를 로드할 것인지
    {
        //서버 URL 설정
        string url = string.Format("{0}?page={1}&perPage={2}&serviceKey={3}", Constants.url, page, Constants.perPage, serviceKey);//여러 개의 문자열을 조합할 수 있다.

        UnityWebRequest request = new UnityWebRequest();
        using (request = UnityWebRequest.Get(url))
        {
            yield return request.SendWebRequest();

            if(request.result != UnityWebRequest.Result.Success)
            {
                Debug.Log(request.error);
            }
            else
            {
                string result = request.downloadHandler.text; //텍스트 형식으로 받아오기. 많이 불러왔었던 빼곡한 글들로 불러온다.

                LegacyData legacyData = JsonUtility.FromJson<LegacyData>(result); //파싱에서 가져온 이름 LegacyData
                Legacy[] legacyArray = legacyData.data;

                for(int i = 0; i < legacyArray.Length; i++)
                {
                    LegacyCellController legacyCellController = Instantiate(legacyCellPrefab, content);
                    legacyCellController.SetData(legacyArray[i].명칭, legacyArray[i].종목, legacyArray[i].지정일);
                }

                int currentTotalCount = legacyData.perPage * (legacyData.page - 1) + legacyData.currentCount; //전체 불러온 데이터 카운트
                if(currentTotalCount < legacyData.totalCount) //아직 불러올 데이터가 있다면
                {
                    MoreButtonCellController moreButtonCellController = Instantiate(moreButtonPrefab, content); //더보기 버튼 생성
                }
            }
        }
    }
}

 

 

이제 MoreButtonCellController 스크립트를 작성한다.

데이터를 불러오는건 LegacyDataManager 스크립트이고 MoreButtonCellController는 불러와진 정보를 적절하게 보여주는 역할만 한다. 여기서 대리자(Delegate)를 사용한다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public delegate void LoadMoreData();

public class MoreButtonCellController : MonoBehaviour
{
    public LoadMoreData loadMoreData;
    public void OnClickMoreButton()
    {
        if(loadMoreData != null) loadMoreData.Invoke(); //null 값이 아니면 실행
    }
}

데이터는 잘 나오지만 더보기 버튼은 따로 없어지진 않는다.

 

 


 

 

MoreButtonCellController 스크립트 수정

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public delegate void LoadMoreData();

public class MoreButtonCellController : MonoBehaviour
{
    public Button button;
    public LoadMoreData loadMoreData;

    private void Start()
    {
        button = GetComponentInChildren<Button>(); //내 자식 중에서 Button이 있는지를 찾아서 있으면 할당 없으면 말고
    }
    public void OnClickMoreButton()
    {
        button.interactable = false; //버튼이 한 번 눌러지면 못누르게 false 설정
        if(loadMoreData != null) loadMoreData.Invoke(); //null 값이 아니면 실행
    }
}

 

 

LegacyDataManager 스크립트에서 마지막 부분에 Destroy 함수 추가

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Networking;

public class LegacyDataManager : MonoBehaviour
{
    [SerializeField] private string serviceKey; //서비스 키는 유출되지 않게 하기 위해서 Scene에서 관리할 예정이다.
    [SerializeField] private LegacyCellController legacyCellPrefab;
    [SerializeField] private MoreButtonCellController moreButtonPrefab;
    [SerializeField] private Transform content;

    private void Start()
    {
        StartCoroutine(LoadData(1));
    }
    IEnumerator LoadData(int page) //몇 번째 page를 로드할 것인지
    {
        //서버 URL 설정
        string url = string.Format("{0}?page={1}&perPage={2}&serviceKey={3}", Constants.url, page, Constants.perPage, serviceKey);//여러 개의 문자열을 조합할 수 있다.

        UnityWebRequest request = new UnityWebRequest();
        using (request = UnityWebRequest.Get(url))
        {
            yield return request.SendWebRequest();

            if(request.result != UnityWebRequest.Result.Success)
            {
                Debug.Log(request.error);
            }
            else
            {
                string result = request.downloadHandler.text; //텍스트 형식으로 받아오기. 많이 불러왔었던 빼곡한 글들로 불러온다.

                LegacyData legacyData = JsonUtility.FromJson<LegacyData>(result); //파싱에서 가져온 이름 LegacyData
                Legacy[] legacyArray = legacyData.data;

                for(int i = 0; i < legacyArray.Length; i++)
                {
                    LegacyCellController legacyCellController = Instantiate(legacyCellPrefab, content);
                    legacyCellController.SetData(legacyArray[i].명칭, legacyArray[i].종목, legacyArray[i].지정일);
                }

                int currentTotalCount = legacyData.perPage * (legacyData.page - 1) + legacyData.currentCount; //전체 불러온 데이터 카운트
                if(currentTotalCount < legacyData.totalCount) //아직 불러올 데이터가 있다면
                {
                    MoreButtonCellController moreButtonCellController = Instantiate(moreButtonPrefab, content); //더보기 버튼 생성
                    moreButtonCellController.loadMoreData = () => //변수에게 함수를 전달하기. 람다 형식으로
                    {
                        Destroy(moreButtonCellController.gameObject); //더보기 버튼 지우기
                        StartCoroutine(LoadData(legacyData.page + 1));
                    };
                }
            }
        }
    }
}

 

스크롤뷰가 좀 튕겨올라가는 모습으로 생성된다. 이 부분을 수정해야한다.

 

 


 

 

LegacyDataManager 스크립트에서, previousMoreButton 생성해서 더보기 버튼의 제거를 컨트롤한다.

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Networking;

public class LegacyDataManager : MonoBehaviour
{
    [SerializeField] private string serviceKey; //서비스 키는 유출되지 않게 하기 위해서 Scene에서 관리할 예정이다.
    [SerializeField] private LegacyCellController legacyCellPrefab;
    [SerializeField] private MoreButtonCellController moreButtonPrefab;
    [SerializeField] private Transform content;

    private void Start()
    {
        StartCoroutine(LoadData(1));
    }
    IEnumerator LoadData(int page, GameObject previousMoreButton = null) //몇 번째 page를 로드할 것인지.
    {
        //서버 URL 설정
        string url = string.Format("{0}?page={1}&perPage={2}&serviceKey={3}", Constants.url, page, Constants.perPage, serviceKey);//여러 개의 문자열을 조합할 수 있다.

        UnityWebRequest request = new UnityWebRequest();
        using (request = UnityWebRequest.Get(url))
        {
            yield return request.SendWebRequest();

            if(request.result != UnityWebRequest.Result.Success)
            {
                Debug.Log(request.error);
            }
            else
            {
                string result = request.downloadHandler.text; //텍스트 형식으로 받아오기. 많이 불러왔었던 빼곡한 글들로 불러온다.

                LegacyData legacyData = JsonUtility.FromJson<LegacyData>(result); //파싱에서 가져온 이름 LegacyData
                Legacy[] legacyArray = legacyData.data;

                for(int i = 0; i < legacyArray.Length; i++)
                {
                    LegacyCellController legacyCellController = Instantiate(legacyCellPrefab, content);
                    legacyCellController.SetData(legacyArray[i].명칭, legacyArray[i].종목, legacyArray[i].지정일);
                }

                if(previousMoreButton != null) //더보기 버튼 제거
                {
                    Destroy(previousMoreButton.gameObject);
                }

                //더보기 버튼 추가
                int currentTotalCount = legacyData.perPage * (legacyData.page - 1) + legacyData.currentCount; //전체 불러온 데이터 카운트
                if(currentTotalCount < legacyData.totalCount) //아직 불러올 데이터가 있다면
                {
                    MoreButtonCellController moreButtonCellController = Instantiate(moreButtonPrefab, content); //더보기 버튼 생성
                    moreButtonCellController.loadMoreData = () => //변수에게 함수를 전달하기. 람다 형식으로
                    {
                        StartCoroutine(LoadData(legacyData.page + 1, moreButtonCellController.gameObject));
                    };
                }
            }
        }
    }
}

 

스크롤뷰가 잘 나오는 것을 볼 수 있다.

 

 


 

 

이제 목록에서 하나의 항목을 선택하면 디테일 팝업이 나오고, 팝업에서 닫기를 누르면 다시 항목으로 돌아가도록 구현해볼 것이다.

 

DetailPanel 이름으로 Panel 하나 추가.

 

 

그 밑에 DataPanel 하나 만들어서 위쪽으로 고정한 다음, 사이즈 조정

 

 

그 밑에 변수 이름으로 패널 하나 또 생성, 피봇은 가운데로 바꾸고 크기 수정

 

 

밑에 텍스트 만들어서 피봇은 왼쪽위로, 글 정렬은 왼쪽으로 설정.

 

 

TitleText와 DataText로 구분지어서 복사한다음, DataText는 PosX 200으로 옮긴다. Width도 400으로 수정.

 

DataText의 Overflow를 Ellipsis로 바꿔야 길어져도 ...으로 나온다.

 

간격 수정 위해서 Pos X 220, Width 380으로 맞춘다.

 

 

DataPanel에 Vertical Layout Group 추가.

 

 

이제 Panel을 복사해서 이름을 바꾸면 아래와 같다.

 

 

DataPanel의 Height 값을 줄여서 뒷배경이 보이지 않게 만들 수 있다.

 

 

DataPanel의 Pos Y는 -100으로, 패널이 살짝 아래로 내려온다.

 

 

 

DetailPanel에 Button 추가해서 CloseButton으로 이름 바꾸고 Width와 Height을 200, 80으로 맞춘다.

 

 

버튼의 피봇을 아래로 고정하고 Pos Y를 100정도 줘서 바닥과 떨어지게 한다.

 

 


 

 

DetailPanelController 스크립트 생성

생성해서 DetailPanel에 넣기

 

DetailPanelController 스크립트

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class DetailPanelController : MonoBehaviour
{
    [SerializeField] TMP_Text nameText; //명칭
    [SerializeField] TMP_Text typeText; //종목
    [SerializeField] TMP_Text designatedDateText; //지정일
    [SerializeField] TMP_Text standardDateText; //기준일
    [SerializeField] TMP_Text numberText; //연번

    private void Awake()
    {
        nameText.text = "";
        typeText.text = "";
        designatedDateText.text = "";
        standardDateText.text = "";
        numberText.text = "";
    }

    public void SetData(Legacy legacy)//Legacy 타입으로 받고 있다. 수업에선 Office.
    {
        nameText.text = legacy.명칭;
        typeText.text = legacy.종목;
        designatedDateText.text = legacy.지정일;
        standardDateText.text = legacy.기준일;
        numberText.text = legacy.연번.ToString();
    }

    public void OnColickCloseButton()
    {
        Destroy(gameObject);
    }
}

 

 

DetailPanel 프리팹화

 

 


 

 

기존 목록에 있던 셀에 버튼을 추가해야한다. 그냥 Component 추가로 Button을 선택하면 됨.

 

 

LegacyCellController 스크립트에서 OnClickCell 버튼 함수를 추가한 다음,

using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;

public class LegacyCellController : MonoBehaviour
{
    [SerializeField] TMP_Text legacyName;
    [SerializeField] TMP_Text type;
    [SerializeField] TMP_Text designatedDate;
    [SerializeField] TMP_Text standardDate;
    [SerializeField] TMP_Text numberText;

    private void Awake()
    {
        legacyName.text = "";
        type.text = "";
        designatedDate.text = "";
    }

    public void SetData(string legacyName, string type, string designatedDate)
    {
        this.legacyName.text = legacyName;
        this.type.text = type;
        this.designatedDate.text = designatedDate;
    }

    public void OnClickCell()
    {

    }
}

 

버튼에 매핑한다.

 

 

OnClickCell 버튼 함수를 작성한다. 대리자를 사용한다.

using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;

public interface ICellDelegate //셀의 일을 대신해줄 객체들
{
    void OnClickCell(int index); //몇 번째 셀을 눌렀는지 전달 받는다
}

public class LegacyCellController : MonoBehaviour
{
    [SerializeField] TMP_Text legacyName;
    [SerializeField] TMP_Text type;
    [SerializeField] TMP_Text designatedDate;

    public ICellDelegate cellDelegate; //대리자 사용. 인터페이스 약속을 지키는 객체만.

    private int index; //자신이 몇 번째 인덱스인지.

    private void Awake()
    {
        legacyName.text = "";
        type.text = "";
        designatedDate.text = "";
    }

    public void SetData(string legacyName, string type, string designatedDate, int index)
    {
        this.legacyName.text = legacyName;
        this.type.text = type;
        this.designatedDate.text = designatedDate;
        this.index = index;
    }

    public void OnClickCell()
    {
        cellDelegate.OnClickCell(this.index); //위의 OnClickCell과는 이름만 같고 다른 것이다.
    }
}

 

 

이제 LegacyDataManager에서도 스크립트를 수정해야한다. index를 받아서 써야한다.

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Networking;

public class LegacyDataManager : MonoBehaviour, ICellDelegate //나는 ICellDelegate를 따르겠다.
{
    [SerializeField] private string serviceKey; //서비스 키는 유출되지 않게 하기 위해서 Scene에서 관리할 예정이다.
    [SerializeField] private LegacyCellController legacyCellPrefab;
    [SerializeField] private MoreButtonCellController moreButtonPrefab;
    [SerializeField] private DetailPanelController detailPanelPrefab; //디테일 패널 받아오기
    [SerializeField] private Transform content;
    [SerializeField] private Transform canvas; //디테일 패널 위치 받아올 캔버스

    //불러온 모든 Legacy 리스트 10개씩 계속 데이터를 불러오면 결국 50~60개의 데이터가 쌓이고 전체를 저장해야 한다.
    private List<Legacy> legacyList = new List<Legacy>();

    private void Start()
    {
        StartCoroutine(LoadData(1));
    }
    IEnumerator LoadData(int page, GameObject previousMoreButton = null) //몇 번째 page를 로드할 것인지.
    {
        //서버 URL 설정
        string url = string.Format("{0}?page={1}&perPage={2}&serviceKey={3}", Constants.url, page, Constants.perPage, serviceKey);//여러 개의 문자열을 조합할 수 있다.

        UnityWebRequest request = new UnityWebRequest();
        using (request = UnityWebRequest.Get(url))
        {
            yield return request.SendWebRequest();

            if(request.result != UnityWebRequest.Result.Success)
            {
                Debug.Log(request.error);
            }
            else
            {
                string result = request.downloadHandler.text; //텍스트 형식으로 받아오기. 많이 불러왔었던 빼곡한 글들로 불러온다.

                LegacyData legacyData = JsonUtility.FromJson<LegacyData>(result); //파싱에서 가져온 이름 LegacyData
                Legacy[] legacyArray = legacyData.data;

                for(int i = 0; i < legacyArray.Length; i++)
                {
                    legacyList.Add(legacyArray[i]);

                    LegacyCellController legacyCellController = Instantiate(legacyCellPrefab, content);
                    legacyCellController.SetData(legacyArray[i].명칭, legacyArray[i].종목, legacyArray[i].지정일, legacyList.Count - 1);
                    
                    legacyCellController.cellDelegate = this; //너의 일을 대신해줄 사람은 나다!
                }

                if(previousMoreButton != null) //더보기 버튼 제거
                {
                    Destroy(previousMoreButton.gameObject);
                }

                //더보기 버튼 추가
                int currentTotalCount = legacyData.perPage * (legacyData.page - 1) + legacyData.currentCount; //전체 불러온 데이터 카운트
                if(currentTotalCount < legacyData.totalCount) //아직 불러올 데이터가 있다면
                {
                    MoreButtonCellController moreButtonCellController = Instantiate(moreButtonPrefab, content); //더보기 버튼 생성
                    moreButtonCellController.loadMoreData = () => //변수에게 함수를 전달하기. 람다 형식으로
                    {
                        StartCoroutine(LoadData(legacyData.page + 1, moreButtonCellController.gameObject));
                    };
                }
            }
        }
    }
    public void OnClickCell(int index) //위에 ICellDelegate 인터페이스 따른다고 하여 자동 생성
    {
        DetailPanelController detailPanelController = Instantiate(detailPanelPrefab, canvas);
        detailPanelController.SetData(legacyList[index]);
    }
}

비어있는 컴포넌트를 채운다.

 

DetailPanel 프리팹에서도 비어있는 컴포넌트를 채운다.

Close 버튼도 함수 매핑한다.

 

 

명칭과 종목이 나오지 않는데, 폰트 사이즈가 커서 나오지 않는 것으로 보인다. 폰트 사이즈를 줄인다. (36 -> 34)

 

728x90
반응형