2020년 5월 25일 월요일

Unity 무한 스크롤 뷰 _ (ScrollView_Infinity_objectPool)


설명과 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 { getset; }
    
    // 셀의 높이
    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.0fcorners[1].y);
        }
        set {
            Vector3[] corners = new Vector3[4];
            CachedRectTransform.GetLocalCorners(corners);
            CachedRectTransform.anchoredPosition = 
                value - new Vector2(0.0fcorners[1].y);
        }
    }
    
    // 셀의 아래쪽 끝의 위치
    public Vector2 Bottom
    {
        get {
            Vector3[] corners = new Vector3[4];
            CachedRectTransform.GetLocalCorners(corners);
            return CachedRectTransform.anchoredPosition + 
                new Vector2(0.0fcorners[3].y);
        }
        set {
            Vector3[] corners = new Vector3[4];
            CachedRectTransform.GetLocalCorners(corners);
            CachedRectTransform.anchoredPosition = 
                value - new Vector2(0.0fcorners[3].y);
        }
    }
}




TableViewController

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

[RequireComponent(typeof(ScrollRect))]
public class TableViewController<T> : ViewController        // ViewController 클래스를 상속
{
    protected List<TtableData = new List<T>();            // 리스트 항목의 데이터를 저장
    [SerializeFieldprivate RectOffset padding;            // 스크롤할 내용의 패딩
    [SerializeFieldprivate 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=0i<tableData.Counti++)
        {
            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 セルを作成するメソッドとセルの内容を更新するメソッドの実装
    [SerializeFieldprivate 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<TCreateCellForIndex(int index)
    {
        // 복사 원본 셀을 이용해 새로운 셀을 생성한다
        GameObject obj = Instantiate(cellBaseas GameObject;
        obj.SetActive(true);
        TableViewCell<Tcell = 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(cellindex);

        cells.AddLast(cell);

        return cell;
    }

    // 셀의 내용을 갱신하는 메서드
    private void UpdateCellForIndex(TableViewCell<Tcellint 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;                               // 리스트 항목을 셀의 형태로 표시하는 범위를 나타내는 사각형
    [SerializeFieldprivate 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=0i<tableData.Counti++)
            {
                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<Tcell = CreateCellForIndex(i);
                    cell.Top = cellTop;
                    break;
                }
                cellTop = cellBottom + new Vector2(0.0fspacingHeight);
            }

            // visibleRect의 범위에 빈 곳이 있으면 셀을 작성한다
            FillVisibleRectWithCells();
        }
        else
        {
            // 이미 셀이 있을 때는 첫 번째 셀부터 순서대로 대응하는 리스트 항목의
            // 인덱스를 다시 설정하고 위치와 내용을 갱신한다
            LinkedListNode<TableViewCell<T>> node = cells.First;
            UpdateCellForIndex(node.Valuenode.Value.DataIndex);
            node = node.Next;
            
            while(node != null)
            {
                UpdateCellForIndex(node.Valuenode.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<TlastCell = 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<Tcell = 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<TfirstCell = cells.First.Value;
            while(firstCell.Bottom.y > visibleRect.y)
            {
                TableViewCell<TlastCell = cells.Last.Value;
                UpdateCellForIndex(firstCelllastCell.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<TlastCell = cells.Last.Value;
            while(lastCell.Top.y < visibleRect.y - visibleRect.height)
            {
                TableViewCell<TfirstCell = cells.First.Value;
                UpdateCellForIndex(lastCellfirstCell.DataIndex - 1);
                lastCell.Bottom = firstCell.Top + new Vector2(0.0fspacingHeight);

                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<ViewControllerstackedViews = 
        new Stack<ViewController>();                // 뷰의 계층을 저장하는 스택
    private ViewController currentView = null;      // 현재 뷰를 저장
    
    [SerializeFieldprivate Text titleLabel;       // 내비게이션 바의 타이틀을 표시하는 텍스트
    [SerializeFieldprivate Button backButton;     // 내비게이션 바의 백 버튼
    [SerializeFieldprivate 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.widthnewViewPos.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 아이템 목록 화면을 내비게이션 뷰에 대응시킨다
    // 내비게이션 뷰
    [SerializeFieldprivate 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 rollNostring datedouble avg_elecPricedouble tot_elecValue
        , double tot_elecCostdouble 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;
    }
}



댓글 없음:

댓글 쓰기

git rejected error(feat. cherry-pick)

 문제 아무 생각 없이 pull을 받지않고 로컬에서 작업! 커밋, 푸시 진행을 해버렷다. push에선 remote와 다르니 당연히 pull을 진행해라고 하지만 로컬에서 작업한 내용을 백업하지 않고 진행하기에는 부담스럽다(로컬작업 유실 가능성) 해결하려...