1
+ using System ;
2
+ using System . Collections ;
3
+ using System . Collections . Generic ;
4
+ using UnityEngine ;
5
+ using UnityEngine . EventSystems ;
6
+ using UnityEngine . UI ;
7
+
8
+ namespace Wanderer . GameFramework
9
+ {
10
+ public class InfiniteListView : UIBehaviour , IInitializePotentialDragHandler , IEventSystemHandler , IBeginDragHandler , IEndDragHandler , IDragHandler , IScrollHandler , ICanvasElement , ILayoutElement , ILayoutGroup , ILayoutController
11
+ {
12
+
13
+ [ SerializeField ]
14
+ private RectTransform _mask ;
15
+
16
+ [ SerializeField ]
17
+ private RectTransform _rendererItemPrefab ;
18
+
19
+ [ SerializeField ]
20
+ private int _columns = 2 ;
21
+
22
+ private Vector2 _itemSize ;
23
+
24
+ [ SerializeField ]
25
+ private float _spacing = 5.0f ;
26
+
27
+ private List < InfiniteListItem > _items = new List < InfiniteListItem > ( ) ;
28
+
29
+ private Vector2 _maskSize ;
30
+
31
+ private Vector2 _dragMin = Vector2 . zero ;
32
+ private Vector2 _dragMax = Vector2 . zero ;
33
+ private Vector2 _dragPosition ;
34
+ private Vector2 _renderMin = Vector2 . zero ;
35
+ private Vector2 _renderMax = Vector2 . zero ;
36
+
37
+ private float _maxHeight ;
38
+
39
+ private List < RectTransform > _renderItems = new List < RectTransform > ( ) ;
40
+ private Queue < RectTransform > _rendererItemCachePool ;
41
+
42
+ private Action < List < InfiniteListItem > > _onRenderItems ;
43
+
44
+ protected override void Start ( )
45
+ {
46
+ base . Start ( ) ;
47
+
48
+ Setup ( 60 , ( ) =>
49
+ {
50
+ StartCoroutine ( TestItems ( 9999 ) ) ;
51
+ } , ( items ) =>
52
+ {
53
+ foreach ( var item in items )
54
+ {
55
+ item . RenderItem . transform . Find ( "Text" ) . GetComponent < Text > ( ) . text = item . Id . ToString ( "d5" ) ;
56
+ }
57
+ } ) ;
58
+
59
+ }
60
+
61
+
62
+ IEnumerator TestItems ( int num )
63
+ {
64
+ for ( int i = 0 ; i < num ; i ++ )
65
+ {
66
+ yield return null ;
67
+ AddItem ( 1 , UnityEngine . Random . Range ( 1.3f , 1.7f ) * _itemSize . y ) ;
68
+ }
69
+ //yield return new WaitForSeconds(5.0f);
70
+ //for (int i = 0; i < 18; i++)
71
+ //{
72
+ // yield return new WaitForSeconds(1.0f);
73
+ // AddItem(-1);
74
+ //}
75
+ }
76
+
77
+ public void Setup ( int renderMax , Action cacheInstantiateComplete , Action < List < InfiniteListItem > > onRenderLists )
78
+ {
79
+ _maskSize = _mask . rect . size ;
80
+ _itemSize = _rendererItemPrefab . rect . size ;
81
+ _itemSize . y = _itemSize . x = ( _maskSize . x - _spacing * ( _columns + 1 ) ) / _columns ;
82
+ //_spacing = (_maskSize.x - _itemSize.x * _itemColumns) / (_itemColumns + 1);
83
+ Debug . Log ( $ "InfiniteListView mask size: { _maskSize } . item prefab size: { _itemSize } . spacing: { _spacing } ") ;
84
+ _renderMin = new Vector2 ( 0 - _itemSize . x , - _maskSize . y - _itemSize . y ) ;
85
+ _renderMax = new Vector2 ( _maskSize . x + _itemSize . x , 0 + _itemSize . y ) ;
86
+ _onRenderItems = onRenderLists ;
87
+ StartCoroutine ( InstantiateRenderCache ( renderMax , cacheInstantiateComplete ) ) ;
88
+ }
89
+
90
+ public InfiniteListView AddItem ( int num = 1 , float height = 0.0f )
91
+ {
92
+ if ( num == 0 )
93
+ return this ;
94
+
95
+ if ( num > 0 )
96
+ {
97
+ Vector2 startPos = _itemSize * 0.5f ;
98
+ if ( _items . Count > 0 )
99
+ {
100
+ startPos = _items [ 0 ] . Size * 0.5f ;
101
+ }
102
+ startPos += Vector2 . one * _spacing ;
103
+
104
+ int idIndex = _items . Count ;
105
+ for ( int i = 0 ; i < num ; i ++ )
106
+ {
107
+ int id = idIndex + i ;
108
+ InfiniteListItem item = new InfiniteListItem ( ) { Id = id } ;
109
+ int arrayIndex = id % _columns ;
110
+ int groupIndex = id / _columns ;
111
+ item . Size = new Vector2 ( _itemSize . x , height > 0.0f ? height : _itemSize . y ) ;
112
+ int lastId = id - _columns ;
113
+ if ( lastId >= 0 )
114
+ {
115
+ var lastItem = _items [ lastId ] ;
116
+ startPos . y = lastItem . Position . y - lastItem . Size . y * 0.5f - _spacing - item . Size . y * 0.5f ;
117
+ }
118
+ else
119
+ {
120
+ startPos . y = - item . Size . y * 0.5f - _spacing ;
121
+ }
122
+ item . Position = new Vector2 ( startPos . x + arrayIndex * ( _itemSize . x + _spacing ) , startPos . y ) ;
123
+ _items . Add ( item ) ;
124
+ }
125
+ }
126
+ else if ( num < 0 )
127
+ {
128
+ num = Mathf . Abs ( num ) ;
129
+ for ( int i = 0 ; i < num ; i ++ )
130
+ {
131
+ if ( _items . Count > 0 )
132
+ {
133
+ _items . RemoveAt ( _items . Count - 1 ) ;
134
+ }
135
+ }
136
+ }
137
+
138
+ _maxHeight = 0.0f ;
139
+ _dragMax = Vector2 . zero ;
140
+ if ( _items . Count > 0 )
141
+ {
142
+ for ( int i = _items . Count - 1 ; i >= _items . Count - _columns ; i -- )
143
+ {
144
+ if ( i >= 0 )
145
+ {
146
+ var lastItem = _items [ i ] ;
147
+ float maxHeight = - lastItem . Position . y + lastItem . Size . y * 0.5f + _spacing ;
148
+ if ( maxHeight > _maxHeight )
149
+ {
150
+ _maxHeight = maxHeight ;
151
+ }
152
+ }
153
+ }
154
+
155
+ _dragMax . y = _maxHeight - _maskSize . y ;
156
+ }
157
+
158
+ RebuildItemRenderer ( ) ;
159
+ return this ;
160
+ }
161
+
162
+ private void RebuildItemRenderer ( )
163
+ {
164
+ ClampDragPosition ( 10.0f ) ;
165
+
166
+ if ( _renderItems . Count > _items . Count )
167
+ {
168
+ for ( int i = _items . Count ; i < _renderItems . Count ; i ++ )
169
+ {
170
+ RectTransform temp = _renderItems [ i ] ;
171
+ temp . gameObject . SetActive ( false ) ;
172
+ _rendererItemCachePool . Enqueue ( temp ) ;
173
+ _renderItems . RemoveAt ( i ) ;
174
+ i -- ;
175
+ }
176
+ }
177
+ else
178
+ {
179
+ for ( int i = _renderItems . Count ; i < _items . Count ; i ++ )
180
+ {
181
+ if ( _rendererItemCachePool . Count > 0 )
182
+ {
183
+ RectTransform temp = _rendererItemCachePool . Dequeue ( ) ;
184
+ temp . gameObject . SetActive ( true ) ;
185
+ _renderItems . Add ( temp ) ;
186
+ }
187
+ else
188
+ {
189
+ break ;
190
+ }
191
+ }
192
+ }
193
+
194
+ foreach ( var item in _renderItems )
195
+ {
196
+ item . gameObject . SetActive ( false ) ;
197
+ }
198
+
199
+ int renderIndex = - 1 ;
200
+
201
+ List < InfiniteListItem > renderItems = new List < InfiniteListItem > ( ) ;
202
+
203
+ for ( int i = 0 ; i < _items . Count ; i ++ )
204
+ {
205
+ var item = _items [ i ] ;
206
+ Vector2 renderPostion = _dragPosition + item . Position ;
207
+
208
+ if ( OutRenderBounds ( renderPostion ) )
209
+ {
210
+ item . RenderItem = null ;
211
+ }
212
+ else
213
+ {
214
+ renderIndex ++ ;
215
+ if ( renderIndex < _renderItems . Count )
216
+ {
217
+ item . RenderItem = _renderItems [ renderIndex ] ;
218
+ item . RenderItem . localPosition = renderPostion ;
219
+ item . RenderItem . sizeDelta = item . Size ;
220
+ item . RenderItem . gameObject . SetActive ( true ) ;
221
+ renderItems . Add ( item ) ;
222
+ }
223
+ else
224
+ {
225
+ break ;
226
+ }
227
+ }
228
+ }
229
+
230
+ _onRenderItems ? . Invoke ( renderItems ) ;
231
+ }
232
+
233
+ private IEnumerator InstantiateRenderCache ( int renderMax , Action cacheInstantiateComplete )
234
+ {
235
+ if ( _rendererItemCachePool == null || _rendererItemCachePool . Count == 0 )
236
+ {
237
+ _rendererItemCachePool = new Queue < RectTransform > ( ) ;
238
+ for ( int i = 0 ; i < renderMax ; i ++ )
239
+ {
240
+ GameObject clone = GameObject . Instantiate ( _rendererItemPrefab . gameObject ) ;
241
+ clone . transform . SetParent ( _mask . transform ) ;
242
+ clone . transform . localScale = Vector3 . one ;
243
+ clone . SetActive ( false ) ;
244
+ _rendererItemCachePool . Enqueue ( clone . GetComponent < RectTransform > ( ) ) ;
245
+ yield return null ;
246
+ }
247
+ }
248
+
249
+ cacheInstantiateComplete ? . Invoke ( ) ;
250
+ }
251
+
252
+
253
+ #region Interface
254
+ public float minWidth => throw new System . NotImplementedException ( ) ;
255
+
256
+ public float preferredWidth => throw new System . NotImplementedException ( ) ;
257
+
258
+ public float flexibleWidth => throw new System . NotImplementedException ( ) ;
259
+
260
+ public float minHeight => throw new System . NotImplementedException ( ) ;
261
+
262
+ public float preferredHeight => throw new System . NotImplementedException ( ) ;
263
+
264
+ public float flexibleHeight => throw new System . NotImplementedException ( ) ;
265
+
266
+ public int layoutPriority => throw new System . NotImplementedException ( ) ;
267
+
268
+
269
+ #endregion
270
+
271
+ #region Interface
272
+
273
+ public void SetLayoutHorizontal ( )
274
+ {
275
+ //throw new System.NotImplementedException();
276
+ }
277
+
278
+ public void SetLayoutVertical ( )
279
+ {
280
+ //throw new System.NotImplementedException();
281
+ }
282
+
283
+ public void CalculateLayoutInputHorizontal ( )
284
+ {
285
+ //throw new System.NotImplementedException();
286
+ }
287
+
288
+ public void CalculateLayoutInputVertical ( )
289
+ {
290
+ //throw new System.NotImplementedException();
291
+ }
292
+
293
+ public void GraphicUpdateComplete ( )
294
+ {
295
+ //throw new System.NotImplementedException();
296
+ }
297
+
298
+ public void LayoutComplete ( )
299
+ {
300
+ throw new System . NotImplementedException ( ) ;
301
+ }
302
+
303
+ public void OnBeginDrag ( PointerEventData eventData )
304
+ {
305
+ //throw new System.NotImplementedException();
306
+ }
307
+
308
+ public void OnDrag ( PointerEventData eventData )
309
+ {
310
+ _dragPosition . y += eventData . delta . y ;
311
+ RebuildItemRenderer ( ) ;
312
+ }
313
+
314
+ public void OnEndDrag ( PointerEventData eventData )
315
+ {
316
+ //throw new System.NotImplementedException();
317
+
318
+ ClampDragPosition ( ) ;
319
+ RebuildItemRenderer ( ) ;
320
+ }
321
+
322
+ public void OnInitializePotentialDrag ( PointerEventData eventData )
323
+ {
324
+ //throw new System.NotImplementedException();
325
+ }
326
+
327
+ public void OnScroll ( PointerEventData eventData )
328
+ {
329
+ }
330
+
331
+ public void Rebuild ( CanvasUpdate executing )
332
+ {
333
+ throw new System . NotImplementedException ( ) ;
334
+ }
335
+
336
+ #endregion
337
+
338
+
339
+ private void ClampDragPosition ( float floatDrag = 0.0f )
340
+ {
341
+ if ( _maxHeight > _maskSize . y )
342
+ {
343
+ _dragPosition . x = Mathf . Clamp ( _dragPosition . x , _dragMin . x - floatDrag , _dragMax . x + floatDrag ) ;
344
+ _dragPosition . y = Mathf . Clamp ( _dragPosition . y , _dragMin . y - floatDrag , _dragMax . y + floatDrag ) ;
345
+ }
346
+ else
347
+ {
348
+ _dragPosition = Vector2 . zero ;
349
+ }
350
+ Debug . Log ( $ "_dragPosition: { _dragPosition } { _dragMin } { _dragMax } ") ;
351
+ }
352
+
353
+ private bool OutRenderBounds ( Vector2 renderPosition )
354
+ {
355
+ if ( renderPosition . x < _renderMin . x || renderPosition . x > _renderMax . x )
356
+ return true ;
357
+
358
+ if ( renderPosition . y < _renderMin . y || renderPosition . y > _renderMax . y )
359
+ return true ;
360
+
361
+ return false ;
362
+ }
363
+
364
+ }
365
+
366
+
367
+
368
+ public struct InfiniteListItem
369
+ {
370
+ public int Id ;
371
+ public bool InRenderer
372
+ {
373
+ get
374
+ {
375
+ return RenderItem != null ;
376
+ }
377
+ }
378
+ public Vector2 Size ;
379
+ public Vector2 Position ;
380
+ public RectTransform RenderItem ;
381
+ }
382
+ }
0 commit comments