├── .gitignore
├── Binwell.Controls
├── FastGrid.Android
│ ├── FastGrid.Android.csproj
│ └── FastGrid
│ │ ├── EndlessRecyclerViewScrollListener.cs
│ │ ├── FastGridAdapter.cs
│ │ ├── FastGridViewRenderer.cs
│ │ ├── FormsToNativeDroid.cs
│ │ ├── SmoothGridLayoutManager.cs
│ │ ├── SwipeRefreshLayoutWithDisabling.cs
│ │ └── ViewHolders.cs
├── FastGrid.iOS
│ ├── FastGrid.iOS.csproj
│ └── FastGrid
│ │ ├── FastCollectionView.cs
│ │ ├── FastCollectionViewCell.cs
│ │ ├── FastCollectionViewDataSource.cs
│ │ ├── FastCollectionViewDelegate.cs
│ │ ├── FastGridViewRenderer.cs
│ │ └── LeftFlowCollectionViewLayout.cs
└── FastGrid
│ ├── FastGrid.csproj
│ └── FastGrid
│ ├── FastGridCell.cs
│ ├── FastGridDataTemplate.cs
│ ├── FastGridEventArgs.cs
│ ├── FastGridTemplateSelector.cs
│ ├── FastGridView.cs
│ └── IScrollAwareElement.cs
├── FastGrid.sln
├── LICENSE
├── README.md
└── Sample
├── FastGridSample.Android
├── Assets
│ └── AboutAssets.txt
├── FastGridSample.Android.csproj
├── MainActivity.cs
├── Properties
│ ├── AndroidManifest.xml
│ └── AssemblyInfo.cs
└── Resources
│ ├── AboutResources.txt
│ ├── Resource.Designer.cs
│ ├── layout
│ ├── Tabbar.axml
│ └── Toolbar.axml
│ ├── mipmap-anydpi-v26
│ ├── icon.xml
│ └── icon_round.xml
│ ├── mipmap-hdpi
│ ├── Icon.png
│ └── launcher_foreground.png
│ ├── mipmap-mdpi
│ ├── icon.png
│ └── launcher_foreground.png
│ ├── mipmap-xhdpi
│ ├── Icon.png
│ └── launcher_foreground.png
│ ├── mipmap-xxhdpi
│ ├── Icon.png
│ └── launcher_foreground.png
│ ├── mipmap-xxxhdpi
│ ├── Icon.png
│ └── launcher_foreground.png
│ └── values
│ ├── colors.xml
│ └── styles.xml
├── FastGridSample.iOS
├── AppDelegate.cs
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon1024.png
│ │ ├── Icon120.png
│ │ ├── Icon152.png
│ │ ├── Icon167.png
│ │ ├── Icon180.png
│ │ ├── Icon20.png
│ │ ├── Icon29.png
│ │ ├── Icon40.png
│ │ ├── Icon58.png
│ │ ├── Icon60.png
│ │ ├── Icon76.png
│ │ ├── Icon80.png
│ │ └── Icon87.png
├── Entitlements.plist
├── FastGridSample.iOS.csproj
├── Info.plist
├── Main.cs
├── Properties
│ └── AssemblyInfo.cs
└── Resources
│ ├── Default-568h@2x.png
│ ├── Default-Portrait.png
│ ├── Default-Portrait@2x.png
│ ├── Default.png
│ ├── Default@2x.png
│ └── LaunchScreen.storyboard
└── FastGridSample
├── App.cs
├── Cells
├── CategoryCell.xaml
├── CategoryCell.xaml.cs
└── ProductCell.cs
├── DataObjects
├── CategoryObject.cs
└── ProductObject.cs
├── FastGridSample.csproj
├── MainPage.xaml
├── MainPage.xaml.cs
└── MainViewModel.cs
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xamarin Components
2 | **/Components/*
3 |
4 | # Created by https://www.gitignore.io
5 |
6 | ### Windows ###
7 | # Windows image file caches
8 | Thumbs.db
9 | ehthumbs.db
10 |
11 | # Folder config file
12 | Desktop.ini
13 |
14 | # Recycle Bin used on file shares
15 | $RECYCLE.BIN/
16 |
17 | # Windows Installer files
18 | *.cab
19 | *.msi
20 | *.msm
21 | *.msp
22 |
23 | # Windows shortcuts
24 | *.lnk
25 |
26 |
27 | ### Xcode ###
28 | build/
29 | *.pbxuser
30 | !default.pbxuser
31 | *.mode1v3
32 | !default.mode1v3
33 | *.mode2v3
34 | !default.mode2v3
35 | *.perspectivev3
36 | !default.perspectivev3
37 | xcuserdata
38 | *.xccheckout
39 | *.moved-aside
40 | DerivedData
41 | *.xcuserstate
42 |
43 |
44 | ### VisualStudio ###
45 | ## Ignore Visual Studio temporary files, build results, and
46 | ## files generated by popular Visual Studio add-ons.
47 |
48 | # User-specific files
49 | *.suo
50 | *.user
51 | *.userosscache
52 | *.sln.docstates
53 |
54 | # User-specific files (MonoDevelop/Xamarin Studio)
55 | *.userprefs
56 |
57 | # Build results
58 | [Dd]ebug/
59 | [Dd]ebugPublic/
60 | [Rr]elease/
61 | [Rr]eleases/
62 | x64/
63 | x86/
64 | build/
65 | bld/
66 | [Bb]in/
67 | [Oo]bj/
68 |
69 | # Visual Studo 2015 cache/options directory
70 | .vs/
71 |
72 | # JetBrains IDE cache/options directory
73 | .idea/
74 |
75 | # MSTest test Results
76 | [Tt]est[Rr]esult*/
77 | [Bb]uild[Ll]og.*
78 |
79 | # NUNIT
80 | *.VisualState.xml
81 | TestResult.xml
82 |
83 | # Build Results of an ATL Project
84 | [Dd]ebugPS/
85 | [Rr]eleasePS/
86 | dlldata.c
87 |
88 | *_i.c
89 | *_p.c
90 | *_i.h
91 | *.ilk
92 | *.meta
93 | *.obj
94 | *.pch
95 | *.pdb
96 | *.pgc
97 | *.pgd
98 | *.rsp
99 | *.sbr
100 | *.tlb
101 | *.tli
102 | *.tlh
103 | *.tmp
104 | *.tmp_proj
105 | *.log
106 | *.vspscc
107 | *.vssscc
108 | .builds
109 | *.pidb
110 | *.svclog
111 | *.scc
112 |
113 | # Chutzpah Test files
114 | _Chutzpah*
115 |
116 | # Visual C++ cache files
117 | ipch/
118 | *.aps
119 | *.ncb
120 | *.opensdf
121 | *.sdf
122 | *.cachefile
123 |
124 | # Visual Studio profiler
125 | *.psess
126 | *.vsp
127 | *.vspx
128 |
129 | # TFS 2012 Local Workspace
130 | $tf/
131 |
132 | # Guidance Automation Toolkit
133 | *.gpState
134 |
135 | # ReSharper is a .NET coding add-in
136 | _ReSharper*/
137 | *.[Rr]e[Ss]harper
138 | *.DotSettings.user
139 |
140 | # JustCode is a .NET coding addin-in
141 | .JustCode
142 |
143 | # TeamCity is a build add-in
144 | _TeamCity*
145 |
146 | # DotCover is a Code Coverage Tool
147 | *.dotCover
148 |
149 | # NCrunch
150 | _NCrunch_*
151 | .*crunch*.local.xml
152 |
153 | # MightyMoose
154 | *.mm.*
155 | AutoTest.Net/
156 |
157 | # Web workbench (sass)
158 | .sass-cache/
159 |
160 | # Installshield output folder
161 | [Ee]xpress/
162 |
163 | # DocProject is a documentation generator add-in
164 | DocProject/buildhelp/
165 | DocProject/Help/*.HxT
166 | DocProject/Help/*.HxC
167 | DocProject/Help/*.hhc
168 | DocProject/Help/*.hhk
169 | DocProject/Help/*.hhp
170 | DocProject/Help/Html2
171 | DocProject/Help/html
172 |
173 | # Click-Once directory
174 | publish/
175 |
176 | # Publish Web Output
177 | *.[Pp]ublish.xml
178 | *.azurePubxml
179 | # TODO: Comment the next line if you want to checkin your web deploy settings
180 | # but database connection strings (with potential passwords) will be unencrypted
181 | *.pubxml
182 | *.publishproj
183 |
184 | # NuGet Packages #Disable for store local nupkg
185 | # *.nupkg
186 | # The packages folder can be ignored because of Package Restore
187 | **/packages/*
188 | # except build/, which is used as an MSBuild target.
189 | !**/packages/build/
190 | # Uncomment if necessary however generally it will be regenerated when needed
191 | !**/packages/repositories.config
192 | *.lock.json
193 | *.nuget.targets
194 | *.nuget.props
195 |
196 |
197 | # Windows Azure Build Output
198 | csx/
199 | *.build.csdef
200 |
201 | # Windows Store app package directory
202 | AppPackages/
203 |
204 | # Others
205 | *.[Cc]ache
206 | ClientBin/
207 | [Ss]tyle[Cc]op.*
208 | ~$*
209 | *~
210 | *.dbmdl
211 | *.dbproj.schemaview
212 | *.pfx
213 | *.publishsettings
214 | node_modules/
215 | bower_components/
216 |
217 | # RIA/Silverlight projects
218 | Generated_Code/
219 |
220 | # Backup & report files from converting an old project file
221 | # to a newer Visual Studio version. Backup files are not needed,
222 | # because we have git ;-)
223 | _UpgradeReport_Files/
224 | Backup*/
225 | UpgradeLog*.XML
226 | UpgradeLog*.htm
227 |
228 | # SQL Server files
229 | *.mdf
230 | *.ldf
231 |
232 | # Business Intelligence projects
233 | *.rdl.data
234 | *.bim.layout
235 | *.bim_*.settings
236 |
237 | # Microsoft Fakes
238 | FakesAssemblies/
239 |
240 | # Node.js Tools for Visual Studio
241 | .ntvs_analysis.dat
242 |
243 | # Visual Studio 6 build log
244 | *.plg
245 |
246 | # Visual Studio 6 workspace options file
247 | *.opt
248 |
249 |
250 | ### XamarinStudio ###
251 | bin/
252 | obj/
253 | *.userprefs
254 |
255 |
256 | ### Objective-C ###
257 | # Xcode
258 | #
259 | build/
260 | *.pbxuser
261 | !default.pbxuser
262 | *.mode1v3
263 | !default.mode1v3
264 | *.mode2v3
265 | !default.mode2v3
266 | *.perspectivev3
267 | !default.perspectivev3
268 | xcuserdata
269 | *.xccheckout
270 | *.moved-aside
271 | DerivedData
272 | *.hmap
273 | *.ipa
274 | *.xcuserstate
275 |
276 | # CocoaPods
277 | #
278 | # We recommend against adding the Pods directory to your .gitignore. However
279 | # you should judge for yourself, the pros and cons are mentioned at:
280 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
281 | #
282 | #Pods/
283 |
284 |
285 | ### Eclipse ###
286 | *.pydevproject
287 | .metadata
288 | .gradle
289 | bin/
290 | tmp/
291 | *.tmp
292 | *.bak
293 | *.swp
294 | *~.nib
295 | local.properties
296 | .settings/
297 | .loadpath
298 |
299 | # Eclipse Core
300 | .project
301 |
302 | # External tool builders
303 | .externalToolBuilders/
304 |
305 | # Locally stored "Eclipse launch configurations"
306 | *.launch
307 |
308 | # CDT-specific
309 | .cproject
310 |
311 | # JDT-specific (Eclipse Java Development Tools)
312 | .classpath
313 |
314 | # PDT-specific
315 | .buildpath
316 |
317 | # sbteclipse plugin
318 | .target
319 |
320 | # TeXlipse plugin
321 | .texlipse
322 |
323 |
324 | ### OSX ###
325 | .DS_Store
326 | .AppleDouble
327 | .LSOverride
328 |
329 | # Icon must end with two \r
330 | Icon
331 |
332 |
333 | # Thumbnails
334 | ._*
335 |
336 | # Files that might appear in the root of a volume
337 | .DocumentRevisions-V100
338 | .fseventsd
339 | .Spotlight-V100
340 | .TemporaryItems
341 | .Trashes
342 | .VolumeIcon.icns
343 |
344 | # Directories potentially created on remote AFP share
345 | .AppleDB
346 | .AppleDesktop
347 | Network Trash Folder
348 | Temporary Items
349 | .apdisk
350 |
351 |
352 | ### Android ###
353 | # Built application files
354 | *.apk
355 | *.ap_
356 |
357 | # Files for the Dalvik VM
358 | *.dex
359 |
360 | # Java class files
361 | *.class
362 |
363 | # Generated files
364 | bin/
365 | gen/
366 |
367 | # Gradle files
368 | .gradle/
369 | build/
370 | /*/build/
371 |
372 | # Local configuration file (sdk path, etc)
373 | local.properties
374 |
375 | # Proguard folder generated by Eclipse
376 | proguard/
377 |
378 | # Log Files
379 | *.log
380 |
381 | ### Android Patch ###
382 | gen-external-apklibs
383 |
384 |
385 | *.stackdump
386 | *.exe
387 | *.lock.json.stamp
388 |
--------------------------------------------------------------------------------
/Binwell.Controls/FastGrid.Android/FastGrid.Android.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | {A25A0002-A767-4554-A6D5-31D91E7F3545}
7 | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
8 | {c9e5eea5-ca05-42a1-839b-61506e0a37df}
9 | Library
10 | Binwell.Controls.FastGrid.Android
11 | Binwell.Controls.FastGrid.Android
12 | True
13 | Resources
14 | Assets
15 | false
16 | v8.1
17 | Xamarin.Android.Net.AndroidClientHandler
18 |
19 |
20 |
21 |
22 | true
23 | portable
24 | false
25 | bin\Debug
26 | DEBUG;
27 | prompt
28 | 4
29 | None
30 |
31 |
32 | true
33 | pdbonly
34 | true
35 | bin\Release
36 | prompt
37 | 4
38 | true
39 | false
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | C:\Users\slava\.nuget\packages\xamarin.android.support.v7.recyclerview\27.0.2.1\lib\MonoAndroid81\Xamarin.Android.Support.v7.RecyclerView.dll
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | 27.0.2.1
59 |
60 |
61 | 3.4.0.1008975
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | {7b749930-552f-429f-b303-18878fdb264c}
76 | FastGrid
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/Binwell.Controls/FastGrid.Android/FastGrid/EndlessRecyclerViewScrollListener.cs:
--------------------------------------------------------------------------------
1 | // Based on https://github.com/ardok/codepath/blob/master/TwitterClient/app/src/main/java/com/codepath/twitterclient/listeners/EndlessRecyclerViewScrollListener.java
2 |
3 | using System;
4 | using Android.Support.V7.Widget;
5 | using Android.Widget;
6 | using Binwell.Controls.FastGrid.FastGrid;
7 |
8 | namespace Binwell.Controls.FastGrid.Android.FastGrid
9 | {
10 | public class EndlessRecyclerViewScrollListener : RecyclerView.OnScrollListener
11 | {
12 | readonly int _visibleThreshold = 5;
13 | bool _loading = true;
14 | int _previousTotalItemCount;
15 | int _startScrollPosition;
16 | ScrollState _lastState = ScrollState.Idle;
17 |
18 | readonly RecyclerView.LayoutManager _layoutManager;
19 | readonly FastGridView _fastGridView;
20 | readonly ScrollRecyclerView _recyclerView;
21 | readonly float _density;
22 |
23 | public bool EnableLoadMore { get; set; }
24 |
25 | public event Action LoadMore;
26 |
27 | public EndlessRecyclerViewScrollListener(LinearLayoutManager layoutManager,
28 | FastGridView fastGridView, ScrollRecyclerView recyclerView)
29 | {
30 | _layoutManager = layoutManager;
31 | _fastGridView = fastGridView;
32 | _recyclerView = recyclerView;
33 | _density = recyclerView.Resources.DisplayMetrics.Density;
34 | }
35 |
36 |
37 | public static int GetLastVisibleItem(int[] lastVisibleItemPositions)
38 | {
39 | var maxSize = 0;
40 | for (var i = 0; i < lastVisibleItemPositions.Length; i++)
41 | if (i == 0 || lastVisibleItemPositions[i] > maxSize)
42 | maxSize = lastVisibleItemPositions[i];
43 | return maxSize;
44 | }
45 |
46 | public override void OnScrollStateChanged(RecyclerView recyclerView, int newState)
47 | {
48 | base.OnScrollStateChanged(recyclerView, newState);
49 | var state = (ScrollState) newState;
50 | var x = _recyclerView.GetHorizontalScrollOffset() / _density;
51 | var y = _startScrollPosition / _density;
52 |
53 | if (_lastState == ScrollState.Idle && (state == ScrollState.TouchScroll || state == ScrollState.Fling))
54 | _startScrollPosition = _recyclerView.GetVerticalScrollOffset();
55 |
56 | if (state == ScrollState.TouchScroll || state == ScrollState.Fling)
57 | _fastGridView.RaiseOnStartScroll(x, y,
58 | state == ScrollState.TouchScroll ? ScrollActionType.Finger : ScrollActionType.Fling);
59 |
60 | if (_lastState == ScrollState.TouchScroll && (state == ScrollState.Fling || state == ScrollState.Idle))
61 | _fastGridView.RaiseOnStopScroll(x, y, ScrollActionType.Finger, state == ScrollState.Idle);
62 |
63 | if (_lastState == ScrollState.Fling && state == ScrollState.Idle)
64 | _fastGridView.RaiseOnStopScroll(x, y, ScrollActionType.Fling, true);
65 |
66 | _lastState = state;
67 | }
68 |
69 | public override void OnScrolled(RecyclerView view, int dx, int dy)
70 | {
71 | if (dy == 0) return;
72 | _startScrollPosition += dy;
73 | _fastGridView.RaiseOnScroll(dy / _density, _recyclerView.GetHorizontalScrollOffset() / _density,
74 | _startScrollPosition / _density, ScrollActionType.Finger);
75 |
76 |
77 | if (!EnableLoadMore) return;
78 | var lastVisibleItemPosition = 0;
79 | var totalItemCount = _layoutManager.ItemCount;
80 |
81 | switch (_layoutManager)
82 | {
83 | case StaggeredGridLayoutManager manager:
84 | var lastVisibleItemPositions = manager.FindLastVisibleItemPositions(null);
85 | lastVisibleItemPosition = GetLastVisibleItem(lastVisibleItemPositions);
86 | break;
87 | case LinearLayoutManager _:
88 | lastVisibleItemPosition = ((LinearLayoutManager) _layoutManager).FindLastVisibleItemPosition();
89 | break;
90 | }
91 |
92 | if (totalItemCount < _previousTotalItemCount)
93 | {
94 | _previousTotalItemCount = totalItemCount;
95 | if (totalItemCount == 0)
96 | _loading = true;
97 | }
98 |
99 | if (_loading && (totalItemCount > _previousTotalItemCount))
100 | {
101 | _loading = false;
102 | _previousTotalItemCount = totalItemCount;
103 | }
104 |
105 | if (_loading || (lastVisibleItemPosition + _visibleThreshold) <= totalItemCount) return;
106 |
107 | LoadMore?.Invoke();
108 | _loading = true;
109 | }
110 |
111 | public void ResetState()
112 | {
113 | _loading = true;
114 | _previousTotalItemCount = 0;
115 | }
116 | }
117 | }
--------------------------------------------------------------------------------
/Binwell.Controls/FastGrid.Android/FastGrid/FastGridAdapter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Specialized;
4 | using System.Linq;
5 | using System.Reflection;
6 | using Android.Content;
7 | using Android.Support.V4.Widget;
8 | using Android.Support.V7.Widget;
9 | using Android.Util;
10 | using Android.Views;
11 | using Binwell.Controls.FastGrid.FastGrid;
12 | using Xamarin.Forms;
13 | using Xamarin.Forms.Platform.Android;
14 | using Size = Xamarin.Forms.Size;
15 | using View = Android.Views.View;
16 |
17 | namespace Binwell.Controls.FastGrid.Android.FastGrid {
18 | public class FastGridAdapter : RecyclerView.Adapter {
19 | readonly RecyclerView _recyclerView;
20 | IEnumerable _items;
21 |
22 | readonly DisplayMetrics _displayMetrics;
23 | readonly FastGridViewRenderer _fastGridViewRenderer;
24 |
25 | readonly FastGridView _fastGridView;
26 | readonly PropertyInfo _realParentProperty;
27 |
28 | FastGridView Element { get; }
29 |
30 | public IEnumerable Items {
31 | get => _items;
32 | set {
33 | if (_items is INotifyCollectionChanged oldCollection) {
34 | oldCollection.CollectionChanged -= NewCollection_CollectionChanged;
35 | }
36 |
37 | _items = value;
38 | if (_items is INotifyCollectionChanged newCollection) {
39 | newCollection.CollectionChanged += NewCollection_CollectionChanged;
40 | }
41 |
42 | NotifyDataSetChanged();
43 | }
44 | }
45 |
46 | void NewCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
47 | _fastGridViewRenderer.CalculateLayoutRects();
48 | try {
49 | ((DefaultItemAnimator) _recyclerView.GetItemAnimator()).SupportsChangeAnimations = !Element.CollectionChangedWithoutAnimation;
50 | }
51 | catch {
52 | //ignored
53 | }
54 |
55 | RecyclerView.ItemAnimator animator = null;
56 | if (Element.CollectionChangedWithoutAnimation) {
57 | animator = _recyclerView.GetItemAnimator();
58 | _recyclerView.SetItemAnimator(null);
59 | }
60 |
61 | switch (e.Action) {
62 | case NotifyCollectionChangedAction.Add:
63 | if (e.NewItems == null) return;
64 | var oneAdd = e.NewItems.Count == 1;
65 | if (oneAdd) NotifyItemInserted(e.NewStartingIndex);
66 | else NotifyItemRangeInserted(e.NewStartingIndex, e.NewItems.Count);
67 | break;
68 | case NotifyCollectionChangedAction.Remove:
69 | if (e.OldItems == null) return;
70 | var oneRemove = e.OldItems.Count == 1;
71 | if (oneRemove) NotifyItemRemoved(e.OldStartingIndex);
72 | else NotifyItemRangeRemoved(e.OldStartingIndex, e.OldItems.Count);
73 | break;
74 | case NotifyCollectionChangedAction.Replace:
75 | NotifyItemChanged(e.NewStartingIndex);
76 | break;
77 | case NotifyCollectionChangedAction.Move:
78 | NotifyItemMoved(e.OldStartingIndex, e.NewStartingIndex);
79 | break;
80 | case NotifyCollectionChangedAction.Reset:
81 | NotifyDataSetChanged();
82 | break;
83 | }
84 |
85 | if (Element.CollectionChangedWithoutAnimation)
86 | _recyclerView.SetItemAnimator(animator);
87 | }
88 |
89 | public FastGridAdapter(IEnumerable items, RecyclerView recyclerView, FastGridView fastGridView, DisplayMetrics displayMetrics, FastGridViewRenderer fastGridViewRenderer) {
90 | Items = items;
91 | _recyclerView = recyclerView;
92 | Element = fastGridView;
93 | _displayMetrics = displayMetrics;
94 | _fastGridViewRenderer = fastGridViewRenderer;
95 | _fastGridView = fastGridView;
96 | _realParentProperty = typeof(Element).GetProperty("RealParent");
97 | }
98 |
99 | public override int GetItemViewType(int position) {
100 | var item = Items.Cast