FastCell on github - silky smooth scrolling in ListView while using XAML

GeorgeCookGeorgeCook PEUniversity ✭✭✭

The xaml in the complex example is really over-the-top (3 stack layouts, and 9 images) and performance is still good. Let me know how you get on!
Implementation is very easy and unobtrusive. Instructions are in the readme.

https://github.com/georgejecook/xamarinFastCell

If anyone wants to do the android renderer, that'd be awesome.

«1

Posts

  • 15mgm1515mgm15 USMember ✭✭✭✭

    Really nice, do you plan to an android version of this?

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭

    I'll need help with that as I'm not an android Dev. I can likely do the cell by myself. But not sure how to do the fast image

  • TektonTekton USMember ✭✭✭

    @GeorgeCook I'm not sure if this is at all relevant, as I'm pretty swamped with projects at the moment, so that's where my head is primarily. However, here is a custom renderer for Android that I've been using, for Images, to try to get around a memory leak that was causing our app to crash. (Still does, technically, if you go in and out of navigation pages with images, over and over. Seems much more resilient now, though.) I'm pretty sure the code here is from another post in the forums:

    using Xamarin.Forms;
    
    namespace TodaysPick.Controls
    {
        public class TPImage : Image { }
    }
    
    using System;
    using Android.Util;
    using TodaysPick.Controls;
    using TodaysPick.Droid.Renderer;
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.Android;
    
    [assembly: ExportRenderer(typeof(TPImage), typeof(TPImageRenderer))]
    namespace TodaysPick.Droid.Renderer
    {
        public class TPImageRenderer : ImageRenderer
        {
            Page page;
            NavigationPage navPage;
    
            protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
            {
                base.OnElementChanged(e);
                if (e.OldElement == null)
                {
                    if (GetContainingViewCell(e.NewElement) != null)
                    {
                        page = GetContainingPage(e.NewElement);
                        if (page.Parent is TabbedPage)
                        {
                            page.Disappearing += PageContainedInTabbedPageDisapearing;
                            return;
                        }
    
                        navPage = GetContainingNavigationPage(page);
                        if (navPage != null)
                            navPage.Popped += OnPagePopped;
                    }
                    else if ((page = GetContainingTabbedPage(e.NewElement)) != null)
                    {
                        page.Disappearing += PageContainedInTabbedPageDisapearing;
                    }
                }
            }
    
            void PageContainedInTabbedPageDisapearing (object sender, EventArgs e)
            {
                this.Dispose(true);
                page.Disappearing -= PageContainedInTabbedPageDisapearing;
            }
    
            protected override void Dispose(bool disposing)
            {
                //Log.Info("**** AdLayoutRenderer *****", "Image got disposed");
                base.Dispose(disposing);
            }
    
            private void OnPagePopped(object s, NavigationEventArgs e)
            {
                if (e.Page == page)
                {
                    this.Dispose(true);
                    navPage.Popped -= OnPagePopped;
                }
            }
    
            private Page GetContainingPage(Xamarin.Forms.Element element)
            {
                Element parentElement = element.ParentView;
    
                if (typeof(Page).IsAssignableFrom(parentElement.GetType()))
                    return (Page)parentElement;
                else
                    return GetContainingPage(parentElement);
            }
    
            private ViewCell GetContainingViewCell(Xamarin.Forms.Element element)
            {
                Element parentElement = element.Parent;
    
                if (parentElement == null)
                    return null;
    
                if (typeof(ViewCell).IsAssignableFrom(parentElement.GetType()))
                    return (ViewCell)parentElement;
                else
                    return GetContainingViewCell(parentElement);
            }
    
            private TabbedPage GetContainingTabbedPage(Element element)
            {
                Element parentElement = element.Parent;
    
                if (parentElement == null)
                    return null;
    
                if (typeof(TabbedPage).IsAssignableFrom(parentElement.GetType()))
                    return (TabbedPage)parentElement;
                else
                    return GetContainingTabbedPage(parentElement);
            }
    
            private NavigationPage GetContainingNavigationPage(Element element)
            {
                Element parentElement = element.Parent;
    
                if (parentElement == null)
                    return null;
    
                if (typeof(NavigationPage).IsAssignableFrom(parentElement.GetType()))
                    return (NavigationPage)parentElement;
                else
                    return GetContainingNavigationPage(parentElement);
            }
        }
    }
    

    Might not even be relevant (or even functions as intended lol), but thought it might be something to look at.

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭

    @Tekton thanks. I've used the MonoDroidToolkit ImageLoader, and it works a charm.

    I'm almost finished with the android view renderer to, which I think will give the same performance improvements as iOS. however I've got a crash on it, which I can't work out.. I don't do android. ugly bloody thing - on the screen and in the apis. I'll ask someone if they can help me solve it.

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭

    By the way, I know there's an issue with cell recycling for the items which act as the cell cache (i.e. the first X cells that appear on screen). I'll fix that later on by adding a simple lookup. It will be just as fast. Be sure to check on github for fixes these next couple of days, and I advise not using it in prod code till I say it's ready - it's still a WIP. I can hopefully get it stabilized this week.

  • 15mgm1515mgm15 USMember ✭✭✭✭

    Double like!

  • OtaMaresOtaMares DEMember ✭✭

    So the major change is that you reuse previously created cells and just switch the binding context, right?
    Would be interesting what reason the xamarin.forms guys had to not implement it that way in the first place.

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭
    edited May 2015

    @Ota yes - we don't feel it so much when not parsing xaml (but still do). but it's really felt when the views are xaml.

    I can only assume they painted themselves into a corner with the DataTemplates - it's so odd, as the mechanism is there, I just think they implemented it slightly wrong and never got around to reviewing it properly to make the necessary changes. From looking at their binary, they could probably rectify this in about a day for someone who understands the code. I can only guess it got done in haste, and "you need native cells to get good performance" is the bs mantra that got repeated ever since.

    My implementation quite soundly dispels that nonsense, so hopefully they'll respond. the only reason to not give us this performance out of the box is that they are not prioritising it, which is just crazy.

    Hopefully someone from Xamarin will pick up on this and go and chat with whoever implemented this and tell them to rethink what they did slightly : none of this is hard (well, from this side of the fence it's a pita; but I was a Flex dev for years, so I've learned how to peer through these kind of frameworks to get gains many times before, often for the same reasons).

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭
    edited May 2015

    This is updated now, working with correct recycling on android and iOS (first few cells weren't working when set to original values).

    I also tested bindings, to my surprise they work and it's just as fast.

    I also added an api to reset the cache (cache is android only : surprise surprise, the android implementation is more complicated than the iOS one ;) ). That will need to be called when the ListView is destroyed - I'll update github tomorrow.

  • TektonTekton USMember ✭✭✭

    @GeorgeCook If you ever make it out to Vegas, I owe you a drink. :smile:

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭

    @tekton you never know.

    I'm afraid there is a limiation. I can't get it to work with uneven row sizes. I suspect it never will work in that configuraiton. If anyone has any ideas, then please let me know. If not, then you know the rules, guys : caveat emptor.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    @GeorgeCook, can you explain this code?

        public void RecycleCell (FastCell newCell)
        {
            if (newCell == _fastCell) {
                _fastCell.BindingContext = _originalBindingContext;
            }
            _fastCell.BindingContext = newCell.BindingContext;
        }
    

    Why would you set BindingContext to _originalBindingContext and then immediately set it to something else? What effect does the first assignment have?

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭

    Good spot @adamkemp Be happy to be on a team with you any day. That's a bug.

    Hopefully the naming makes the intent clear. There should be an if there. I need to cache the original binding contexts as I reuse the xamarin cells from the first few items.

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭

    @adamkemp Seeing as you are casting your keen eye on this, can you spot any way to get variable height rows going? I tried setting height in the setup method of the cell subclass (ie simplefastcell.xaml.cs) and then picking up non negative heights in the layout code of the cell. Ive ran out of time to keep trying things but still had a couple more ideas. Just thought if you were playing with this that something might spring to mind. If not, no worries :)

  • TektonTekton USMember ✭✭✭
    edited May 2015

    @MichaelWatson I think one thing Xamarin might keep in mind is that one of the appeals of Xamarin Forms is that it allows you to kind of get a bootstrapped start to mobile development--even if you have to revert to native controls to do so. What this has meant for me, being one of those developers, is that it can get extremely confusing, when A) documentation is incomplete and B) there is a ton of cross-referencing that could be done, to aid developers, but while in some places it is plentiful, in others it is greatly lacking.

    There is an enormous amount of information that is lurking in blog posts, forums, and gists. However, it's not easy to find that information, especially if you're new and you get tunnel vision more often. I think that if a lot of Xamarin (Forms, etc.) devs are lurking in the forums, it would be cool if someone could be responsible for revamping this kind of stuff. Considering how new some of the Xamarin stuff is, it would help us early adopters keep the platform momentum going. This is especially true for the more advanced use-cases, such as custom renderers and the things being discussed here.

    If the platform costs aren't going to be affordable, to receive support or use some advanced use-cases (e.g. command-line build support), adding a little more cohesion to aid with adoption, especially when it comes to thinking you could make some bad arse stuff, but hit a wall soon thereafter, might soften the blow of being (having lack of a better word) gimped [unwittingly].

  • adamkempadamkemp USInsider, Developer Group Leader mod

    George, I think the context you're missing is that there have actually been a significant number of people trying to use Xamarin.Forms for applications that it's not suitable for (and there are many of those) and getting frustrated with the entire platform because Forms couldn't do what they wanted. It's a serious issue for Xamarin right now that threatens the perception of their entire platform. That is why they are being very careful about how they position Forms, and why they are trying to be very clear about its limitations.

    As for code reuse, I think what Michael was saying is that for the given sample app the amount of code reuse is actually pretty small. That probably wouldn't be true if this code was used in a much larger application, though. I don't think it's fair to judge a custom renderer's value by looking at the amount of total code reuse in just the simple demo app. Also, obviously if you packaged this into a reusable component for others to use then their code reuse would be just as high as stock Xamarin.Forms (because they're not maintaining your custom renderers). So I think Michael kind of missed the point here.

    As for your multiple rants about their implementation I'm going to give them the benefit of the doubt and say it's possible that you're missing some subtleties that they had to account for. I don't know for sure because I haven't studied either your code or their code in enough detail, but I don't think it's fair to be so critical. Ease up a bit. Let's be constructive.

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭

    @adamkemp thanks for clarifying.

    I'm sorry if I've made multiple rants about their implementation - it certainly wasn't my intention, and I am trying to be constructive, please don't think I've activated troll mode or something ;). And apologies to Xamarin staff too, if I was being too blunt (if it helps you guys read my posts in a less offensive way : I'm British, we just talk very offensively all day long ;))

    And you are quite right that it's hard to know what all the forces shaping the xamarin implementation are without looking in their code. I know for one thing that without help from Xamarin, it'll be impossible to use this mechanism with multiple row-height cells.

    I'm glad that you agree that this is would be of benefit to others. I feel like Forms has failed every time I have to write a trivial cell without xaml - it's not as readable, nor maintainable, and feels plain wrong when it's basic stuff.

    Saying that though, I can't agree with @MichaelWatson's assertions:

    (21 layout calculations for ComplexViewCell.xaml ouch!)

    For a start, I couldn't realistically get that many cells on any device with that much complexity to each cell ; but I do concede that 4 cells previously wasn't enough. I've updated it for a more realistic use case, with cell heights of 100.. (that's 12 per screen).

    The initial page load is slow; but the cell complexity and the ham-fisted way I wrote it is to prove a worse-performance case - the same cell with one absolute layout, or a grid layout would be way faster to instantiate. When it scrolls, there's not much slowdown at all - my sample is doing a pretty decent job, with what is a totally unrealistic example - here's a video of it with smaller cells (more on screen).. to make it fit, I've added even more stack layouts.. I think a c# authored cell would perform the same, not that I can be bothered to write one (another reason I like xaml for cells is that it's way way quicker to write).

    The problem is that internally ListView is creating the same amount of ComplexViewCell.xaml as the number of items in the list - one new one each time it first appears on screen. For uniform row-sizes this is trivial to fix on Xamarin's side. I suspect even with un-even row sizes that there's something that could be done with a bit more thought.

    Baring in mind your "setting expectations" perspective, I can understand if Xamarin don't prioritize xaml view cell list performance, and advise against it at the current time.

    I'm happy to maintain this fast cell for myself and others who choose to use it for the use cases that work for them. But I hope very much that they will at least assist us in making it possible to add support for variable sized rows using this pattern (by providing some kind of HeightForItem() delegate method).xaml

    I've added a bugzilla : https://bugzilla.xamarin.com/show_bug.cgi?id=29820

  • MichaelWatsonMichaelWatson USXamarin Team Xamurai
    edited May 2015

    @Tekton, you're definitely right on information being scattered everywhere. We are working on making our documentation more complete and creating better samples. I think the Creating Mobile Apps with Xamarin.Forms Book Preview 2 is a great step in the right direction, but there is definitely more work to be done with Xamarin.Forms!

    @GeorgeCook, I do understand your code, I studied it for some time today. I'm speaking with the intention of trying to share information about Xamarin.Forms and how you can see performance increases from some common pitfalls that are made. I'm not saying you have made any of these, but I know a lot of new Xamarin developers might read this blog post and may not understand what you have done or how to increase performance for their use case.

    I also wasn't referring to 21 ViewCells, but rather 21 Layout calculations per a view cell (1 top level StackLayout with 3 StackLayouts, each having 4, 7 and 7 elements in it, 21 elements in top level StackLayout). The StackLayouts will have to measure calls for the elements inside because StackLayouts take over how the elements are spaced on the page. That's how you get the nice resizing capabilities when you rotate the phone. This is why we mention the Absolute Layout in the ListView performance documentation I referenced.

    The main point I was trying to get across is that Xamarin.Forms is an awesome platform (I use it all the time), but the fact is that it should not be used for every application. Xamarin Forms is absolutely ready for serious apps (check out the IMSA application on any platform). My goal is to help in mobile development and share as much information as I can.

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭

    But @MichaelWatson I don't get your point. My video clearly shows that the list view can cope with these cells. It's just that for whatever reason you guys haven't implemented a flyweight with good performance.

    The example is contrived. I wrote t because with xamarin forms I couldn't evens do a basic cell with 2 images 2 labels and a star rating control without it stuttering and glitching. My code resolves that issue while exposing that your standard implementation is really inefficient and you keep responding saying that my xaml code is really inefficient. That's the point. It was meant to be to demonstrate the issue.

    So if you are here to share information, why does the list view create viewcells for every single item in the data source? Is that not, as my code shows needlessly imposing a performance constraint that need not exist?

  • MichaelRidlandMichaelRidland AUInsider, University ✭✭✭

    Hi @GeorgeCook

    Thanks for taking the time to research this and provide this prototype. It's always great to hold platforms to high standards and continually push them forward. Especially considering Xamarin.Forms has the opportunity to be one of the best platforms for mobile app development, even more so if we push it. So thankyou.

    I feel that this time and effort will not go to waste and the Xamarin.Forms team and @TheRealJasonSmith will look at this in time. But I imaging that they are fairly busy at the moment.

    Thanks

    Michael
    http://www.michaelridland.com

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭

    Yeah. I gathered they're probably busy. I think xamarin forms is way cooler than a lot of folks realise and part of the reason I spent the time doing this was to prove that to myself and others (let's face it, cross platform native speed xaml ui's are pretty cool)

    I'll actively support my sample as a library and Nuget and hopefully the forms team will find time to add a row height callback into one of their releases so those of us who want fast xaml cells can do so.

  • voidvoid DKBeta ✭✭✭

    So if you are here to share information, why does the list view create viewcells for every single item in the data source?

    I would like to know this as well. This forum is littered with posts asking the same question. This is not a new topic. Xamarin, please come forward and tell us why Forms has been designed this way. What are we not seeing?

  • ApurvaGoyalApurvaGoyal USMember ✭✭

    @MichaelWatson I have a question regarding your following statement-

    "Another thing that helps speed up performance for Android is sizing your images to the proper size prior to loading them. If you have a huge image (3000x2000) and you are using it as the source for an image that is 30x20, then you will have a performance cost there. Either trim the images down before including in the bundle (preferred way) or scale the images prior to loading."

    So on my application, I have a bunch of 800x600 on webserver and I am putting them in an image control in my view cell using following code-

      _image = new Image
            {
                HorizontalOptions = LayoutOptions.Start,
            };
            _image.WidthRequest = 70;
            _image.HeightRequest = 70;
            _image.Aspect = Aspect.AspectFit;
            _image.VerticalOptions = LayoutOptions.Center;
    

    Now I assumed that I am scaling the images here. Or do you want the scaling to be done on web server? How should the image be pre-cropped or scaled on server if we dont know how that size might look on hdpi..mdpi...xxxhdpi etc devices. SHould I crop the images to different sizes that are compatible with these device densities?

    Thanks

    Apurva

  • MichaelWatsonMichaelWatson USXamarin Team Xamurai

    @ApurvaGoyal So in your example, the image actually isn't being scaled, just the size of what is holding the image. You want to read the BitMap and scale it in the Xamarin.Android project. We outline this in our Android docs, but there is some extra work to make it work in Xamarin.Forms.

    I went ahead and created a sample that shows how you can do this. I'm loading an image that is 5.3 MB in size (NEVER ACTUALLY PUT AN IMAGE THIS BIG IN YOUR APP BUNDLE) that is 2832 × 4256 pixels. If you click the load standard forms image button in the example, it takes almost a second to load the image, grows the heap to ~250 MB, and displays the image in about 5 seconds total time. If you load the scaled image, the time is cut down to 30-50 ms to load the image and the heap only grows to 14 MB. Success!

    This is an important trick because if you are loading images in a ListView, you can imagine how large the heap will have to grow to make this work; it may just crash your app. The sample could easily be transformed to work for images from a server, the principle is still the same. Hope this helps!

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭
    edited May 2015

    @MichaelWatson I'm glad that you had time to answer apurva's question, which as far as I can see no one else asked you to do. Any chance you can answer the on topic and not already documented all over the Internet ones, which other xamarin users have directly asked you to answer to respond to?

    1. Why does the xamarin implementation insist on one viewcell per data source item and can we expect you guys will optimise that at some point?
    2. Will you guys consider making the api for variable row heights more open so we can have a delegate method for row height, to allow those of us who want performance lists to do our own optimisations?

    Many thanks. I'm sorry if I seem peeved; I am not; but I must confess I'm confused : interfacing with xamarin staff is hard, producing code examples is hard, reaearching performance issues is hard, Google searches and reading docs is easy. So your answer priorities seem somewhat strange to me.

  • ApurvaGoyalApurvaGoyal USMember ✭✭

    @MichaelWatson Thanks so much! I will give your project a try now. Appreciate your help!

  • ApurvaGoyalApurvaGoyal USMember ✭✭

    @MichaelWatson I looked at your sample and it gives good idea! So if I use Picasso on Android side and create a custom renderer as you did, will it achieve the same results as you suggested in your sample?

    Also these images that we are displaying in listview are actual thumbnails for products (since its an ecom app). Do you still suggest that we should create these different sizes of thumbnails for each density (xhdpi, xxhdpi etc) so that we have to do less scaling on server?

    Thanks

  • _PK__PK_ USMember ✭✭

    @GeorgeCook Thanks for sharing!

    I've been trying to implement the FastCell for iOS in my project but I am coming across an issue where it seems like the incorrect BindingContext is being set for the cells, so the cells don't appear in the same order after scrolling up and down. I was able to reproduce the issue in the example project provided here: https://github.com/twintechs/TwinTechsFormsLib.

    The modifications I made to the sample project:
    I added an index parameter to the MediaItem: public MediaItem (int index, ..., ..., ..)
    In the DataProvider class' GetMediaItems() method, I pass in the index value for each media item.
    Add a label in the ComplexFastCell XAML layout with an x:Name property of "IndexLabel"
    Set the Text value for the IndexLabel to the index of the Media Item.

    After scrolling up or down a few times, you can notice the index numbers displayed on the label are inconsistent.

    Strangely enough, once I do a pull to refresh on the list in my app, the cells work fine. I suspect it's a bug with the iOS ViewCellRenderer's GetCell method where the "Cell item" parameter is returning the wrong item and thus we assign the incorrect BindingContext, but I am still trying to verify.

    Has anyone else using this been able to reproduce this?

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭

    PK awesome! I guess I should've also done a test like that! It sounds like I might need to add some more logic to the cells. I'll look into it. Can you pleas raise an issue in github? https://github.com/twintechs/TwinTechsFormsLib/issues

    Many thanks.

  • _PK__PK_ USMember ✭✭

    @GeorgeCook Issue raised on GitHub :smile:

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭

    PK I fixed it. It turns out my implementation was flawed. Thankfully my android cell Cache class is pretty tight, and was easy to port. Keeps the API better synced between platforms too. It does mean that we need to pay attention to clearing the cache on all platforms.

    I will get round to testing this for leaks/memory usage over the next couple of weeks. As I said in github, I'm heavily committed to this solution as we use it internally. So please keep bugs/feature requests coming : you will get rapid responses.

  • _PK__PK_ USMember ✭✭
    edited May 2015

    @GeorgeCook Wow that was fast, thanks! Looking forward to seeing how you solved the issue. I'll definitely be using this in my project so I'll raise issues on GitHub if I come across anything else.

    Thanks again!

  • johanksonjohankson SEInsider, University ✭✭
    edited June 2015

    Do you need help with a WP renderer? Or is performance good enough out of the box on WP?
    EDIT: Seems like you're using Xamarin Studio and some C#6 constructs. That will slow down the WP development at the moment. :P

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭

    Hi. Wp help would be lost helpful. Especially as I'm using xam studio and don't have access to any wp devices (I'll be doing wp in a few months)

    Is there no c# 6 for wp? What needs to change.

    Also why will my using xs slow down Dev? What's the issue? Can a windows user not just add a wp project?

  • Info-FrameLtdInfo-FrameLtd HUMember

    @GeorgeCook the simulator is not a good way to test it - for me, my currently developed app ran perfectly fine on the simulator, but had huge problems on a real device. Including ViewCells (ViewCells on the simulator were perfectly smooth, on the device it was laggier than a droid).

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭
    edited June 2015

    @Info-FrameLtd I don't follow. You can see many of my videos are recorded on a device and the performance is awesome. What is it your are doing? Running the samples? If so, which device are you running on?

    And I was not testing performance on a simulator. I was using that to show PK i resolved a bug he raised. Did you even look at the GitHub page or the blog article? I have no idea what you are referring to. Both of them are littered with videos of devices running smoothly.

  • Info-FrameLtdInfo-FrameLtd HUMember

    @GeorgeCook sorry, I mixed up two videos :) apologies!

«1
Sign In or Register to comment.