Creating 400 Image on a thread so slowly

MahdyarMahdyar USMember
edited August 2016 in Xamarin.Forms

I'm duplicating an Images 400 times in a page(Cause each of them has it's own tap gesture) , but It's so slowly and I get this warning :
Skipped 376 frames! The application may be doing too much work on its main thread.

Is this the correct way to do it ?

Note: I'm creating something like a map And My image is something like 0.5KB

    public partial class TestMap : ContentPage
    {
        public TestMap()
        {
            InitializeComponent();
            NavigationPage.SetHasNavigationBar(this, false);

            Grid mapGrid = new Grid();

            TapGestureRecognizer[] tapSquareArray = new TapGestureRecognizer[400];
            CachedImage[] mapSquareArray = new CachedImage[400];

            int leftNum = 0;
            int topNum = 1;
            int squareSide = 15;
            for (int i = 0; i < squareSide;i++ )
            {
                mapGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
                mapGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) });
            }


            for (int i = 0; i < 225; i++)
            {
                mapSquareArray[i] = new CachedImage()
                {
                    HorizontalOptions = LayoutOptions.FillAndExpand,
                    HeightRequest = 100,
                    DownsampleToViewSize = true,
                    TransparencyEnabled = false,
                    Aspect = Aspect.AspectFill,
                    TransformPlaceholders = false,
                };
                mapSquareArray[i].Source = ImageSource.FromResource("Funema.Resources.Square.png");


                int copy = i;
                tapSquareArray[i] = new TapGestureRecognizer();
                tapSquareArray[i].Tapped += (s, e) =>
                {
                    Debug.WriteLine("Tapped Image : " + copy);
                };
                mapSquareArray[i].GestureRecognizers.Add(tapSquareArray[i]);

                mapGrid.Children.Add(mapSquareArray[i], leftNum, topNum);

                leftNum++;
                if (leftNum == squareSide)
                {
                    leftNum = 0;
                    topNum += 1;
                }

            }

            ScrollView mapScroll = new ScrollView();
            mapScroll.Content = mapGrid;
            mapScroll.Orientation = ScrollOrientation.Both;

            this.Content = mapScroll;


        }
    }
}

Answers

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    What part of that surprises you? You're doing a ton of work on the UI thread, and you got a warning about it. You should never do that kind of work on the UI thread.

    UI is just a reflection of data. It represents your ViewModel and allows the user to interact with it. You should be creating all your data (that includes images) in the ViewModel on its own thread. Then update the collection property that holds your images, and is binded to the UI control.

  • MahdyarMahdyar USMember
    edited August 2016

    @ClintStLaurent said:
    What part of that surprises you? You're doing a ton of work on the UI thread, and you got a warning about it. You should never do that kind of work on the UI thread.

    UI is just a reflection of data. It represents your ViewModel and allows the user to interact with it. You should be creating all your data (that includes images) in the ViewModel on its own thread. Then update the collection property that holds your images, and is binded to the UI control.

    I Knew that my solution is not good but I wanted to know the best way to do that.
    Can you gimme an example or a guide ?(My image is only 0.5KB)

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    Are you familiar with the MVVM pattern? It is the architectural design of the application where you have a Model, View and ViewModel - and your UI uses data binding to properties on the ViewModel.

    Is any of that familiar?

  • MahdyarMahdyar USMember

    @ClintStLaurent said:
    Are you familiar with the MVVM pattern? It is the architectural design of the application where you have a Model, View and ViewModel - and your UI uses data binding to properties on the ViewModel.

    Is any of that familiar?

    Nope I'm very new to Xamarin

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    MVVM isn't just for Xamarin. Its a 10+year old design pattern. Its been in use since the early days of WPF development.

    Getting a solid understanding of MVVM is critical to any good project for WPF or Xamarin. Its a foundation concept that you'll build everything else on top of. So that's your best place to start learning.

  • MahdyarMahdyar USMember

    @ClintStLaurent said:
    MVVM isn't just for Xamarin. Its a 10+year old design pattern. Its been in use since the early days of WPF development.

    Getting a solid understanding of MVVM is critical to any good project for WPF or Xamarin. Its a foundation concept that you'll build everything else on top of. So that's your best place to start learning.

    I'm new to .NET not only Xamarin

    https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/
    Is it the good place to start ?

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    Yep. There's a ton of MVVM tutorials on the 'net. That's as good a place as any to start - especially since they are gearing it toward Xamarin.

    Once you get proficient with data binding you'll be amazed at what you can do with it an how it shapes your programming into better designs. Less work for more WOW.

    There's a learning curve with MVVM if you've never worked with anything like it before. I'd suggest you start small... just work tutorials that have a definite start, middle, end so you can duplicate the expected results. Don't try to just read them like a reference then apply it to your project. Just set your project aside for a few days while you learn this new concept.

  • MahdyarMahdyar USMember
    edited August 2016

    @ClintStLaurent said:
    Yep. There's a ton of MVVM tutorials on the 'net. That's as good a place as any to start - especially since they are gearing it toward Xamarin.

    Once you get proficient with data binding you'll be amazed at what you can do with it an how it shapes your programming into better designs. Less work for more WOW.

    There's a learning curve with MVVM if you've never worked with anything like it before. I'd suggest you start small... just work tutorials that have a definite start, middle, end so you can duplicate the expected results. Don't try to just read them like a reference then apply it to your project. Just set your project aside for a few days while you learn this new concept.

    I read it carefully and practiced it with some simple projects ...

    But I understand that ViewModel is for Changing Already Created UI elements or Just using some buttons on it...

    The problem that my app is so slow is I'm creating the UI elements with the C# code and creating UI elements is not the ViewModel job as I understand, and I can not create 400 Image on the XAML then Binds the Images to ViewModel... I do not think it's the correct way to do that...

    The only thing I can do is to create the images in my UI with C# code and then bind it's source to the ViewModel... But I do not think it solve the problem

    EDIT : I searched and find this topic says adding elements to view dynamically is not go through a ViewModel.

    https://social.msdn.microsoft.com/Forums/silverlight/en-US/c6fa6b94-950c-4ed2-af38-68510c40fa9a/adding-a-child-element-to-a-grid-in-the-viewmodel?forum=silverlightmvvm

  • JohnHardmanJohnHardman GBUniversity mod

    @Mahdyar - can you explain why you want to load 400 images? If it's just to work out where on a page is being tapped, you could use MRGestures instead (see http://www.mrgestures.com/ ). MRGestures provide position information about where a tap occurred. That would save you having to load all those images.

  • DavidDancyDavidDancy AUMember ✭✭✭✭

    @Mahdyar I think if I were you I'd try a different approach.

    Do you really need 400 images? Regardless of how you do it, painting 400 images on a mobile device is always going to take a long time. Perhaps you could have 1 image that's pre-composed from those 400, and then put a TapGestureRecognizer on it with some logic to see where the user tapped. You could translate the X, Y coordinates to your original matrix of 400 images, and then run a command based on what you find.

    Perhaps if you really need those 400 images, and you have to scale them to device size, you might find it worthwhile to adopt the above approach but compose the 1 master image dynamically. Translating X,Y into position in your image matrix is then just (fairly simple) maths.

  • MahdyarMahdyar USMember

    @JohnHardman said:
    @Mahdyar - can you explain why you want to load 400 images? If it's just to work out where on a page is being tapped, you could use MRGestures instead (see http://www.mrgestures.com/ ). MRGestures provide position information about where a tap occurred. That would save you having to load all those images.

    @DavidDancy said:
    @Mahdyar I think if I were you I'd try a different approach.

    Do you really need 400 images? Regardless of how you do it, painting 400 images on a mobile device is always going to take a long time. Perhaps you could have 1 image that's pre-composed from those 400, and then put a TapGestureRecognizer on it with some logic to see where the user tapped. You could translate the X, Y coordinates to your original matrix of 400 images, and then run a command based on what you find.

    Perhaps if you really need those 400 images, and you have to scale them to device size, you might find it worthwhile to adopt the above approach but compose the 1 master image dynamically. Translating X,Y into position in your image matrix is then just (fairly simple) maths.

    I just want to handle the tap on each one , and It's hard to have scroll view and translate x and y to position of my image + Sometimes Some of these 400 Images will change regarding to my API info , For example 150th Image should be another image, (Imagine a map of a game, Some of the villages have owner, and Some of them do not)

  • DavidDancyDavidDancy AUMember ✭✭✭✭

    @Mahdyar I think I understand a little more.

    However 400 images is a lot to fit on a phone's screen.

    I presume that if they are in a ScrollView, some of them are off-screen? I suppose they must be, since to be able to tap on an individual image it probably needs to be quite big (say 30x30 px minimum, but I'm guessing).

    If so, then I would only have enough images in memory to fill the screen (plus a few to allow smooth scrolling). If your images are all the same size, one possible solution is to create a custom ViewCell and fill a ListView with enough of those ViewCells to create the scrolling impression, with an image cache to make sure that the images load optimally from disk/network.

    In the custom ViewCell I would put a row of images, each with a TapGestureRecognizer, and index them so the position in the matrix can be deduced. (You could get away with a single TapGestureRecognizer for each ViewCell too).

    This would mean that your matrix of 400 images would be split into offscreen (lots of images, mostly not in memory) and onscreen (only a few tens of images, all in memory). As the ListView scrolls, the custom ViewCells coming into view would load their images and the ones going out of view would unload theirs. The image cache can take care of making the load efficient.

    All you would need to do then is arrive at some calculation that enables you to translate from {row, column} in the ViewCell's TapGestureRecognizer to {row, column} in your image matrix so you know what to do when the user taps a particular image.

    I think this would help a lot with performance because instead of trying to pre-load all 400 images the screen would load only enough to fill the screen (probably about 50 or so, which is still a lot but much more manageable) and then load the rest as the ListView scrolls. This will also enable you to swap out images in the matrix as needed - just invalidate the cached image when it happens and your loading logic should take care of loading the new image.

  • MahdyarMahdyar USMember

    @DavidDancy said:
    @Mahdyar I think I understand a little more.

    However 400 images is a lot to fit on a phone's screen.

    I presume that if they are in a ScrollView, some of them are off-screen? I suppose they must be, since to be able to tap on an individual image it probably needs to be quite big (say 30x30 px minimum, but I'm guessing).

    If so, then I would only have enough images in memory to fill the screen (plus a few to allow smooth scrolling). If your images are all the same size, one possible solution is to create a custom ViewCell and fill a ListView with enough of those ViewCells to create the scrolling impression, with an image cache to make sure that the images load optimally from disk/network.

    In the custom ViewCell I would put a row of images, each with a TapGestureRecognizer, and index them so the position in the matrix can be deduced. (You could get away with a single TapGestureRecognizer for each ViewCell too).

    This would mean that your matrix of 400 images would be split into offscreen (lots of images, mostly not in memory) and onscreen (only a few tens of images, all in memory). As the ListView scrolls, the custom ViewCells coming into view would load their images and the ones going out of view would unload theirs. The image cache can take care of making the load efficient.

    All you would need to do then is arrive at some calculation that enables you to translate from {row, column} in the ViewCell's TapGestureRecognizer to {row, column} in your image matrix so you know what to do when the user taps a particular image.

    I think this would help a lot with performance because instead of trying to pre-load all 400 images the screen would load only enough to fill the screen (probably about 50 or so, which is still a lot but much more manageable) and then load the rest as the ListView scrolls. This will also enable you to swap out images in the matrix as needed - just invalidate the cached image when it happens and your loading logic should take care of loading the new image.

    First : Thank you for your answer

    I want to have 49 images on a screen not all 400 images on the screen lol, These 400 Images I load are only the small part of my map , and I wanted to load 400 more images when the scroll ends , and unload these 400 images , But with your solution I guess I can get the detail of only 400 images that fit on the screen and then with each scroll I load the new ones and unload the old ones, and when the scroll ends , I load more details
    Will it be slow again ?

    And will ListView Unload the images that go off screen automatically?

  • DavidDancyDavidDancy AUMember ✭✭✭✭

    @Mahdyar so 49 images on screen. Is that 7 rows of 7 images? Or are they all different sizes?

    My suggestion would probably only work if you have images that are all the same size. If this is not the case, I do not think we have any control in Xamarin Forms that can help you. You'd either need to make one (e.g. following the ideas of UICollectionView) or drop into "native" code.

    However, if your images are all the same size, I would have thought that loading 49 of them, even though it wouldn't be lightning-fast, would happen "fast enough" to get your initial view on screen. Then as the view scrolls, each row of the view as it becomes visible on screen would load another set (of 7?).

    In addition, if you use an image cache (for example like https://github.com/luberda-molinet/FFImageLoading) you can leave the details of what's in memory and what's not to the cache. You can also populate the cache in the background while the user is busy looking at the first 49 images, so that the next few rows will be loaded already by the time they start scrolling.

    Your view would then simply request the images from the cache as it scrolls. If the images are in memory already, performance is fast; if not, they get loaded and (probably) stay in memory ready for the next use. This will be especially good if some of your 400 images are actually the same (just at a different position in the matrix). And the maths to work out which one the user tapped on would not be very hard either.

Sign In or Register to comment.