설명과 package파일
https://gitlab.com/dooo3/scrollview_objpool
하는이유
100개 이상일때 scrollview가 퍼포먼스가 떨어진다...
Point!!!!
1.shopItemTableViewCell,
2.shopItemTableViewController
1,2 를 원하는대로 커스텀 하셔서 사용하면 됩니다.
예시는 Scripts/Custom 폴더 안에 있습니다
You can customize 1,2 to your liking and use them.
Examples are in the Scripts / Custom folder
ScrollView에 컨트롤러를 달고
Content밑에 Cell을 단 오브젝트를 달면된다.
계산에 의해서 Scrollview를 만들기 때문에
Content에
Content Size Fitter
Vertival Layout Group등
활성화 하면 안됨
ViewController
using UnityEngine;
[RequireComponent(typeof(RectTransform))] // RectTransform 컴포넌트가 필수이다public class ViewController : MonoBehaviour{ // Rect Transform 컴포넌트를 캐시한다 private RectTransform cachedRectTransform; public RectTransform CachedRectTransform { get { if(cachedRectTransform == null) { cachedRectTransform = GetComponent<RectTransform>(); } return cachedRectTransform; } }
// 뷰의 타이틀 public virtual string Title { get { return ""; } set {} }}
TableViewCell
using UnityEngine;
public class TableViewCell<T> : ViewController // ViewController 클래스를 상속한다{ // 셀의 내용을 갱신하는 메서드 public virtual void UpdateContent(T itemData) { // 실제 처리는 상속한 클래스에 구현한다 }
// 셀에 대응하는 리스트 항목의 인덱스 public int DataIndex { get; set; } // 셀의 높이 public float Height { get { return CachedRectTransform.sizeDelta.y; } set { Vector2 sizeDelta = CachedRectTransform.sizeDelta; sizeDelta.y = value; CachedRectTransform.sizeDelta = sizeDelta; } } // 셀의 위쪽 끝의 위치 public Vector2 Top { get { Vector3[] corners = new Vector3[4]; CachedRectTransform.GetLocalCorners(corners); return CachedRectTransform.anchoredPosition + new Vector2(0.0f, corners[1].y); } set { Vector3[] corners = new Vector3[4]; CachedRectTransform.GetLocalCorners(corners); CachedRectTransform.anchoredPosition = value - new Vector2(0.0f, corners[1].y); } } // 셀의 아래쪽 끝의 위치 public Vector2 Bottom { get { Vector3[] corners = new Vector3[4]; CachedRectTransform.GetLocalCorners(corners); return CachedRectTransform.anchoredPosition + new Vector2(0.0f, corners[3].y); } set { Vector3[] corners = new Vector3[4]; CachedRectTransform.GetLocalCorners(corners); CachedRectTransform.anchoredPosition = value - new Vector2(0.0f, corners[3].y); } }}
TableViewController
using UnityEngine;using UnityEngine.UI;using System.Collections.Generic;
[RequireComponent(typeof(ScrollRect))]public class TableViewController<T> : ViewController // ViewController 클래스를 상속{ protected List<T> tableData = new List<T>(); // 리스트 항목의 데이터를 저장 [SerializeField] private RectOffset padding; // 스크롤할 내용의 패딩 [SerializeField] private float spacingHeight = 4.0f; // 각 셀의 간격 // Scroll Rect 컴포넌트를 캐시한다 private ScrollRect cachedScrollRect; public ScrollRect CachedScrollRect { get { if(cachedScrollRect == null) { cachedScrollRect = GetComponent<ScrollRect>(); } return cachedScrollRect; } }
// 인스턴스를 로드할 때 호출된다 protected virtual void Awake() { }
// 리스트 항목에 대응하는 셀의 높이를 반환하는 메서드 protected virtual float CellHeightAtIndex(int index) { // 실제 값을 반환하는 처리는 상속한 클래스에서 구현한다 return 0.0f; }
// 스크롤할 내용 전체의 높이를 갱신하는 메서드 protected void UpdateContentSize() { // 스크롤할 내용 전체의 높이를 계산한다 float contentHeight = 0.0f; for(int i=0; i<tableData.Count; i++) { contentHeight += CellHeightAtIndex(i); if(i > 0) { contentHeight += spacingHeight; } }
// 스크롤할 내용의 높이를 설정한다 Vector2 sizeDelta = CachedScrollRect.content.sizeDelta; sizeDelta.y = padding.top + contentHeight + padding.bottom; CachedScrollRect.content.sizeDelta = sizeDelta; }
#region セルを作成するメソッドとセルの内容を更新するメソッドの実装 [SerializeField] private GameObject cellBase; // 복사 원본 셀 private LinkedList<TableViewCell<T>> cells = new LinkedList<TableViewCell<T>>(); // 셀을 저장
// 인스턴스를 로드할 때 Awake 메서드 다음에 호출된다 protected virtual void Start() { // 복사 원본 셀은 비활성화해둔다 cellBase.SetActive(false);
#region 셀을 재이용하는 처리를 구현 // Scroll Rect 컴포넌트의 On Value Changed 이벤트의 이벤트 리스너를 설정한다 CachedScrollRect.onValueChanged.AddListener(OnScrollPosChanged);#endregion }
// 셀을 생성하는 메서드 private TableViewCell<T> CreateCellForIndex(int index) { // 복사 원본 셀을 이용해 새로운 셀을 생성한다 GameObject obj = Instantiate(cellBase) as GameObject; obj.SetActive(true); TableViewCell<T> cell = obj.GetComponent<TableViewCell<T>>();
// 부모 요소를 바꾸면 스케일이나 크기를 잃어버리므로 변수에 저장해둔다 Vector3 scale = cell.transform.localScale; Vector2 sizeDelta = cell.CachedRectTransform.sizeDelta; Vector2 offsetMin = cell.CachedRectTransform.offsetMin; Vector2 offsetMax = cell.CachedRectTransform.offsetMax;
cell.transform.SetParent(cellBase.transform.parent);
// 셀의 스케일과 크기를 설정한다 cell.transform.localScale = scale; cell.CachedRectTransform.sizeDelta = sizeDelta; cell.CachedRectTransform.offsetMin = offsetMin; cell.CachedRectTransform.offsetMax = offsetMax;
// 지정된 인덱스가 붙은 리스트 항목에 대응하는 셀로 내용을 갱신한다 UpdateCellForIndex(cell, index);
cells.AddLast(cell);
return cell; }
// 셀의 내용을 갱신하는 메서드 private void UpdateCellForIndex(TableViewCell<T> cell, int index) { // 셀에 대응하는 리스트 항목의 인덱스를 설정한다 cell.DataIndex = index;
if(cell.DataIndex >= 0 && cell.DataIndex <= tableData.Count-1) { // 셀에 대응하는 리스트 항목이 있다면 셀을 활성화해서 내용을 갱신하고 높이를 설정한다 cell.gameObject.SetActive(true); cell.UpdateContent(tableData[cell.DataIndex]); cell.Height = CellHeightAtIndex(cell.DataIndex); } else { // 셀에 대응하는 리스트 항목이 없다면 셀을 비활성화시켜 표시되지 않게 한다 cell.gameObject.SetActive(false); } }#endregion
#region visibleRect의 정의와 visibleRect를 갱신하는 메서드 구현 private Rect visibleRect; // 리스트 항목을 셀의 형태로 표시하는 범위를 나타내는 사각형 [SerializeField] private RectOffset visibleRectPadding; // visibleRect의 패딩
// visibleRect을 갱신하기 위한 메서드 private void UpdateVisibleRect() { // visibleRect의 위치는 스크롤할 내용의 기준으로부터 상대적인 위치다 visibleRect.x = CachedScrollRect.content.anchoredPosition.x + visibleRectPadding.left; visibleRect.y = -CachedScrollRect.content.anchoredPosition.y + visibleRectPadding.top;
// visibleRect의 크기는 스크롤 뷰의 크기 + 패딩グ visibleRect.width = CachedRectTransform.rect.width + visibleRectPadding.left + visibleRectPadding.right; visibleRect.height = CachedRectTransform.rect.height + visibleRectPadding.top + visibleRectPadding.bottom; }#endregion
#region テーブルビューの表示内容を更新する処理の実装 protected void UpdateContents() { UpdateContentSize(); // 스크롤할 내용의 크기를 갱신한다 UpdateVisibleRect(); // visibleRect를 갱신한다
if(cells.Count < 1) { // 셀이 하나도 없을 때는 visibleRect의 범위에 들어가는 첫 번째 리스트 항목을 찾아서 // 그에 대응하는 셀을 작성한다 Vector2 cellTop = new Vector2(0.0f, -padding.top); for(int i=0; i<tableData.Count; i++) { float cellHeight = CellHeightAtIndex(i); Vector2 cellBottom = cellTop + new Vector2(0.0f, -cellHeight); if((cellTop.y <= visibleRect.y && cellTop.y >= visibleRect.y - visibleRect.height) || (cellBottom.y <= visibleRect.y && cellBottom.y >= visibleRect.y - visibleRect.height)) { TableViewCell<T> cell = CreateCellForIndex(i); cell.Top = cellTop; break; } cellTop = cellBottom + new Vector2(0.0f, spacingHeight); }
// visibleRect의 범위에 빈 곳이 있으면 셀을 작성한다 FillVisibleRectWithCells(); } else { // 이미 셀이 있을 때는 첫 번째 셀부터 순서대로 대응하는 리스트 항목의 // 인덱스를 다시 설정하고 위치와 내용을 갱신한다 LinkedListNode<TableViewCell<T>> node = cells.First; UpdateCellForIndex(node.Value, node.Value.DataIndex); node = node.Next; while(node != null) { UpdateCellForIndex(node.Value, node.Previous.Value.DataIndex + 1); node.Value.Top = node.Previous.Value.Bottom + new Vector2(0.0f, -spacingHeight); node = node.Next; }
// visibleRect의 범위에 빈 곳이 있으면 셀을 작성한다 FillVisibleRectWithCells(); } }
// visibleRect 범위에 표시될 만큼의 셀을 작성하는 메서드 private void FillVisibleRectWithCells() { // 셀이 없다면 아무 일도 하지 않는다 if(cells.Count < 1) { return; }
// 표시된 마지막 셀에 대응하는 리스트 항목의 다음 리스트 항목이 있고 // 또한 그 셀이 visibleRect의 범위에 들어온다면 대응하는 셀을 작성한다 TableViewCell<T> lastCell = cells.Last.Value; int nextCellDataIndex = lastCell.DataIndex + 1; Vector2 nextCellTop = lastCell.Bottom + new Vector2(0.0f, -spacingHeight);
while(nextCellDataIndex < tableData.Count && nextCellTop.y >= visibleRect.y - visibleRect.height) { TableViewCell<T> cell = CreateCellForIndex(nextCellDataIndex); cell.Top = nextCellTop;
lastCell = cell; nextCellDataIndex = lastCell.DataIndex + 1; nextCellTop = lastCell.Bottom + new Vector2(0.0f, -spacingHeight); } }#endregion
#region 셀을 재이용하는 처리 구현 private Vector2 prevScrollPos; // 바로 전의 스크롤 위치를 저장
// 스크롤 뷰가 스크롤됐을 때 호출된다 public void OnScrollPosChanged(Vector2 scrollPos) { // visibleRect를 갱신한다 UpdateVisibleRect(); // 스크롤한 방향에 따라 셀을 다시 이용해 표시를 갱신한다 ReuseCells((scrollPos.y < prevScrollPos.y)? 1: -1);
prevScrollPos = scrollPos; }
// 셀을 다시 이용해서 표시를 갱신하는 메서드 private void ReuseCells(int scrollDirection) { if(cells.Count < 1) { return; }
if(scrollDirection > 0) { // 위로 스크롤하고 있을 때는 visibleRect에 지정된 범위보다 위에 있는 셀을 // 아래를 향해 순서대로 이동시켜 내용을 갱신한다 TableViewCell<T> firstCell = cells.First.Value; while(firstCell.Bottom.y > visibleRect.y) { TableViewCell<T> lastCell = cells.Last.Value; UpdateCellForIndex(firstCell, lastCell.DataIndex + 1); firstCell.Top = lastCell.Bottom + new Vector2(0.0f, -spacingHeight);
cells.AddLast(firstCell); cells.RemoveFirst(); firstCell = cells.First.Value; }
// visibleRect에 지정된 범위 안에 빈 곳이 있으면 셀을 작성한다 FillVisibleRectWithCells(); } else if(scrollDirection < 0) { // 아래로 스크롤하고 있을 때는 visibleRect에 지정된 범위보다 아래에 있는 셀을 // 위를 향해 순서대로 이동시켜 내용을 갱신한다 TableViewCell<T> lastCell = cells.Last.Value; while(lastCell.Top.y < visibleRect.y - visibleRect.height) { TableViewCell<T> firstCell = cells.First.Value; UpdateCellForIndex(lastCell, firstCell.DataIndex - 1); lastCell.Bottom = firstCell.Top + new Vector2(0.0f, spacingHeight);
cells.AddFirst(lastCell); cells.RemoveLast(); lastCell = cells.Last.Value; } } }#endregion}
NavigationViewController
using UnityEngine;using UnityEngine.UI;using System.Collections.Generic;
[RequireComponent(typeof(CanvasGroup))]public class NavigationViewController : ViewController // ViewController 클래스를 상속{ private Stack<ViewController> stackedViews = new Stack<ViewController>(); // 뷰의 계층을 저장하는 스택 private ViewController currentView = null; // 현재 뷰를 저장 [SerializeField] private Text titleLabel; // 내비게이션 바의 타이틀을 표시하는 텍스트 [SerializeField] private Button backButton; // 내비게이션 바의 백 버튼 [SerializeField] private Text backButtonLabel; // 백 버튼의 텍스트 // 인스턴스를 로드할 때 호출된다 void Awake() { // 백 버튼의 이벤트 리스너를 설정한다 //backButton.onClick.AddListener(OnPressBackButton); // 처음에는 백 버튼을 표시하지 않는다 //backButton.gameObject.SetActive(false); } // 백 버튼이 눌러졌을 때 호출되는 메서드 public void OnPressBackButton() { // 이전 계층의 뷰로 되돌아간다 Pop(); } // 사용자의 인터랙션을 유효화/무효화하는 메서드 private void EnableInteraction(bool isEnabled) { GetComponent<CanvasGroup>().blocksRaycasts = isEnabled; } // 다음 계층의 뷰로 옮겨가는 처리를 수행하는 메서드 public void Push(ViewController newView) { if(currentView == null) { // 첫 뷰는 애니메이션 없이 표시한다 newView.gameObject.SetActive(true); currentView = newView; return; } // 애니메이션 도중에는 사용자의 인터랙션을 무효화한다 EnableInteraction(false); // 현재 표시된 뷰를 화면 왼쪽 밖으로 이동시킨다 ViewController lastView = currentView; stackedViews.Push(lastView); Vector2 lastViewPos = lastView.CachedRectTransform.anchoredPosition; lastViewPos.x = -this.CachedRectTransform.rect.width; //lastView.CachedRectTransform.MoveTo( // lastViewPos, 0.3f, 0.0f, iTween.EaseType.easeOutSine, ()=>{ // // 이동이 끝나면 뷰를 비활성화한다 // lastView.gameObject.SetActive(false); //});
// 새로운 뷰를 화면 왼쪽 밖으로부터 중앙으로 이동시킨다 newView.gameObject.SetActive(true); Vector2 newViewPos = newView.CachedRectTransform.anchoredPosition; newView.CachedRectTransform.anchoredPosition = new Vector2(this.CachedRectTransform.rect.width, newViewPos.y); newViewPos.x = 0.0f; //newView.CachedRectTransform.MoveTo( // newViewPos, 0.3f, 0.0f, iTween.EaseType.easeOutSine, ()=>{ // // 이동이 끝나면 사용자의 인터랙션을 유효화한다 // EnableInteraction(true); //}); // 새로운 뷰를 현재의 뷰로서 저장하고 내비게이션 바의 타이틀을 변경한다 currentView = newView; titleLabel.text = newView.Title;
// 백 버튼의 레이블을 변경한다 backButtonLabel.text = lastView.Title; // 백 버튼을 유효화한다 backButton.gameObject.SetActive(true); } // 이전 계층의 뷰로 되돌아가는 처리를 수행하는 메서드 public void Pop() { if(stackedViews.Count < 1) { // 이전 계층의 뷰가 없으므로 아무것도 없는 상태다 return; } // 애니메이션 도중에는 사용자의 인터랙션을 무효화한다 EnableInteraction(false); // 현재 표시된 뷰를 화면 오른쪽 밖으로 이동시킨다 ViewController lastView = currentView; Vector2 lastViewPos = lastView.CachedRectTransform.anchoredPosition; lastViewPos.x = this.CachedRectTransform.rect.width; //lastView.CachedRectTransform.MoveTo( // lastViewPos, 0.3f, 0.0f, iTween.EaseType.easeOutSine, ()=>{ // // 이동이 끝나면 뷰를 비활성화한다 // lastView.gameObject.SetActive(false); //}); // 이전 계층의 뷰를 스택으로부터 다시 가져오고 화면 왼쪽 밖으로부터 중앙으로 이동시킨다 ViewController poppedView = stackedViews.Pop(); poppedView.gameObject.SetActive(true); Vector2 poppedViewPos = poppedView.CachedRectTransform.anchoredPosition; poppedViewPos.x = 0.0f; //poppedView.CachedRectTransform.MoveTo( // poppedViewPos, 0.3f, 0.0f, iTween.EaseType.easeOutSine, ()=>{ // // 이동이 끝나면 사용자의 인터랙션을 유효화한다 // EnableInteraction(true); //});
// 스택에서 다시 가져온 뷰를 현재의 뷰로 저장하고 내비게이션 바의 타이틀을 변경한다 currentView = poppedView; titleLabel.text = poppedView.Title;
// 이전 계층의 뷰가 있을 때 백 버튼의 레이블을 변경해서 유효화한다 if(stackedViews.Count >= 1) { backButtonLabel.text = stackedViews.Peek().Title; backButton.gameObject.SetActive(true); } else { backButton.gameObject.SetActive(false); } }}
ManualResultTableViewController
using UnityEngine;using UnityEngine.UI;using System.Collections.Generic;
[RequireComponent(typeof(ScrollRect))]public class ManualResultTableViewController : TableViewController<ManualResultItem>{
public void ResetItem() { tableData = new List<ManualResultItem>(); UpdateContents(); }
public void AddData(ManualResultItem data) { tableData.Add(data); } public void ItemUpdate() { UpdateContents(); } // 리스트 항목에 대응하는 셀의 높이를 반환하는 메서드 protected override float CellHeightAtIndex(int index) { if (index >= 0 && index <= tableData.Count - 1) { //if (tableData[index].price >= 1000) //{ // // 가격이 1000 이상인 아이템을 표시하는 셀의 높이를 반환한다 // return 240.0f; //} //if (tableData[index].price >= 500) //{ // // 가격이 500 이상인 아이템을 표시하는 셀의 높이를 반환한다 // return 160.0f; //} }
//전부다 같은 높이로 반환한다. return 80.0f; }
// 인스턴스를 로드할 때 호출된다 protected override void Awake() { // 기반 클래스에 포함된 Awake 메서드를 호출한다 base.Awake();
// 아이콘의 스프라이트 시트에 포함된 스프라이트를 캐시해둔다 //SpriteSheetManager.Load("IconAtlas"); }
// 인스턴스를 로드할 때 Awake 메서드가 처리된 다음에 호출된다 protected override void Start() { // 기반 클래스의 Start 메서드를 호출한다 base.Start();
// 리스트 항목의 데이터를 읽어 들인다 //LoadData();
#region 아이템 목록 화면을 내비게이션 뷰에 대응시킨다 if (navigationView != null) { // 내비게이션 뷰의 첫 뷰로 설정한다 navigationView.Push(this); } #endregion }
#region 아이템 목록 화면을 내비게이션 뷰에 대응시킨다 // 내비게이션 뷰 [SerializeField] private NavigationViewController navigationView;
// 뷰의 타이틀을 반환한다 public override string Title { get { return "SHOP"; } } #endregion
#region 아이템 상세 화면으로 옮기는 처리 // 아이템 상세 화면의 뷰 //[SerializeField] private ShopDetailViewController detailView;
// 셀이 선택됐을 때 호출되는 메서드 public void OnPressCell(ShopItemTableViewCell cell) { if (navigationView != null) { // 선택된 셀로부터 아이템 데이터를 가져와서 아이템 상세 화면의 내용을 갱신한다 //detailView.UpdateContent(tableData[cell.DataIndex]); // 아이템 상세 화면으로 옮긴다 //navigationView.Push(detailView); } } #endregion}
################################################################################################################################################################################
using UnityEngine;using UnityEngine.UI;
public class ManualResultItem{ public string rollNo; public string date; public double avg_elecPrice; public double tot_elecValue; public double tot_elecCost; public double tot_slabCount; public string TYPE;
public ManualResultItem(string rollNo, string date, double avg_elecPrice, double tot_elecValue , double tot_elecCost, double tot_slabCount,string TYPE) { this.rollNo = rollNo; this.date = date; this.avg_elecPrice = avg_elecPrice; this.tot_elecValue = tot_elecValue; this.tot_elecCost = tot_elecCost; this.tot_slabCount = tot_slabCount; this.TYPE = TYPE; }}
public class ManualResultTableViewCell : TableViewCell<ManualResultItem>{ public Text rollNo; public Text date; public Text avg_elecPrice; public Text tot_elecValue; public Text tot_elecCost; public Text tot_slabCount; public Text TYPE;
public override void UpdateContent(ManualResultItem itemData) { rollNo.text = itemData.rollNo; date.text = itemData.date; avg_elecPrice.text = itemData.avg_elecPrice.ToString("N0"); tot_elecValue.text = itemData.tot_elecValue.ToString("N0"); tot_elecCost.text = itemData.tot_elecCost.ToString("N0"); tot_slabCount.text = itemData.tot_slabCount.ToString("N0"); TYPE.text = itemData.TYPE; }}
댓글 없음:
댓글 쓰기