xamarin scrollview прыжки при добавлении детей - PullRequest
1 голос
/ 14 октября 2019

У меня есть <ScrollView/>, который содержит <Grid/> (из <images/>), когда пользователь приближается к нижней части прокрутки, я подключаюсь к веб-сайту и загружаю следующий набор изображений (фактически, ссылки, содержащие JSON дляImageSource's), который создает «бесконечный» блок прокрутки изображений.

моя проблема в том, что когда я загружаю следующий набор изображений, приложение на мгновение зависает, а затем прокручивается окно прокрутки, чтобы наверстать упущенное при добавлении нового набора. как я могу предотвратить этот "Прыжок"?

    private async void OnScrolled(object sender, ScrolledEventArgs e)
    {
        ScrollView scroller = (ScrollView)sender;
        //threshhold == bottom of scrollveiw + height of one image (aka just before it's visible)
        double threashold = (e.ScrollY + scroller.Height) + preview_size;

        //if we touch the threshhold...
        if (threashold > scroller.ContentSize.Height)
        {
            //one row of images
            int TilePreload = (Tiles.Count + ColCount);

            //if the next row exceeds the total available post count, download and append more posts
            if (TilePreload >= Posts.Count)
            {
                //we have reached the end of our postlist, we must get more!
                var results = await Task.Run(()=>FetchResults<List<CPost>>());
                Posts.AddRange( results);
            }

            //then, add the tiles to UI 
            //AddRow();// <- jumpy

            //calling this as a task results in no tiles added, and eventually an execption
            await Task.Run( () => AddRow() );
        }
    }

// разделил цикл for как функцию, чтобы ее можно было запускать как задачу (при необходимости)

    public void AddRow()
    {
        for (int i = 0; i < RowCount; i++)
        {
            //wrapper for assigning Image to Grid
            //aka ImageSourec = some URL
            AddTile(i);
        }
    }

примечание: FetchResults<T>(); является более или менее оберткой для

//fyi using System.Net.Http;
public static string GetResponse(string page, Dictionary<String, String> arguments, bool IsPost = false)
{
    HttpMethod Method = IsPost ? HttpMethod.Post : HttpMethod.Get;

    var request = new HttpRequestMessage(Method, page)
    {
        Content = new FormUrlEncodedContent(arguments)
    };

    HttpResponseMessage httpResponse = Client.SendAsync(request).Result;
    if (httpResponse.IsSuccessStatusCode)
    {
        return httpResponse.Content.ReadAsStringAsync().Result;
    }
    return null;
}

1 Ответ

1 голос
/ 15 октября 2019

Если вы обновляете пользовательский интерфейс, это необходимо сделать в основном потоке пользовательского интерфейса.

Когда вы вызываете

await Task.Run( () => AddRow() );

Это означает, что AddRow (и любые вызываемые им методы) не работают в потоке пользовательского интерфейса и вызовет сбой.

Что выможет попробовать что-то вроде этого (не проверено):

private async void OnScrolled(object sender, ScrolledEventArgs e)
{
    await Task.Run(async () => 
    {
        ScrollView scroller = (ScrollView)sender;
        //threshhold == bottom of scrollveiw + height of one image (aka just before it's visible)
        double threashold = (e.ScrollY + scroller.Height) + preview_size;

        //if we touch the threshhold...
        if (threashold > scroller.ContentSize.Height)
        {
            //one row of images
            int TilePreload = (Tiles.Count + ColCount);

            //if the next row exceeds the total available post count, download and append more posts
            if (TilePreload >= Posts.Count)
            {
                //we have reached the end of our postlist, we must get more!
                var results = await Task.Run(()=>FetchResults<List<CPost>>()).ConfigureAwait(false);
                Posts.AddRange( results);
            }
        }
    });

    //then, add the tiles to UI 
    AddRow();
}  

Кроме того, почему GetResponse не является асинхронным методом ?? (использование .Result блокирует поток) И, таким образом, почему FetchResults> () не асинхронен?

Чтобы сделать GetResponse асинхронным:

public static async Task<string> GetResponse(string page, Dictionary<String, String> arguments, bool IsPost = false)
{
    HttpMethod Method = IsPost ? HttpMethod.Post : HttpMethod.Get;

    var request = new HttpRequestMessage(Method, page)
    {
        Content = new FormUrlEncodedContent(arguments)
    };

    HttpResponseMessage httpResponse = await Client.SendAsync(request).ConfigureAwait(false);
    if (httpResponse.IsSuccessStatusCode)
    {
        return await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
    }
    return null;
}

В том виде, как у вас это было, у вас было много потоков, которые прыгают вокруг и блокируют потоки кода из-за использования .Result Итак, поместив всекод, который не требует выполнения в потоке пользовательского интерфейса внутри Задачи, вы можете избежать любого кода, выполняющегося в потоке пользовательского интерфейса, до тех пор, пока он вам не понадобится, т.е. при добавлении элементов пользовательского интерфейса.

Использование .ConfigureAwait(false) означает, что когда эта задача завершится, следующий код не будет маршалирован обратно в вызывающий поток, сохраняя маршалинг некоторого потока, который занимает время. Когда .ConfigureAwait(false) не вызывается, по умолчанию используется значение .ConfigureAwait(true), что означает «Когда эта задача выполнена, направьте следующий код обратно в поток, из которого была вызвана эта задача. Поэтому, выполнив вышеописанное, вы должны избежать некоторых задержек потоков и, надеюсь, эторазрешит скачок.

Хотя вам может потребоваться выполнить тест, потому что с учетом вышеизложенного событие OnScrolled будет по-прежнему запускаться, пока эта работа завершается. Поэтому вы можете установить флажок только для запуска кодачтобы получить новые предметы один раз, например:

bool _isGettingNewItems;
private async void OnScrolled(object sender, ScrolledEventArgs e)
{
    // Don't run the code to get new items if it is already running
    if (_isGettingNewItems) 
        return;

    _isGettingNewItems = true;
    await Task.Run(async () => 
    {
        ...
    });

    //then, add the tiles to UI 
    AddRow();

    // finished getting new items, so set the boolean back to false
    _isGettingNewItems = false;
}  
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...