【问题标题】:Xamarin Forms ListView Programatic Refresh Not Stopping on Android When Page Loaded加载页面时,Xamarin 表单 ListView 程序刷新不会在 Android 上停止
【发布时间】:2017-02-10 17:25:20
【问题描述】:

我在 Visual Studio 2015 中有一个带有 ListView 的便携式项目,该项目通过 API 调用通过以下函数 refreshData 填充了一些数据:

async Task refreshData()
{
    myListView.BeginRefresh();
    var apiCallResult = await App.Api.myApiGetCall();            
    myListView.ItemsSource = apiCallResult;
    myListView.EndRefresh();    
}

refreshData() 被调用

protected override void OnAppearing()
{
    base.OnAppearing();
    refreshData();
}

一切正常,除了在 Android 上,当页面最初加载时,刷新指示器在 EndRefresh() 上没有停止或消失。该页面位于TabbedPage 中,因此我可以转到其他选项卡,然后返回此页面,刷新指示器会随着我的 API 调用正确启动和停止。

为什么页面最初在 Android 上加载时刷新没有停止?任何帮助将不胜感激。

注意:当我在 iOS 上运行时,这完全可以正常工作。

到目前为止我已经尝试过:

  1. myListView.BeginRefresh() 替换为myListView.IsRefreshing = truemyListView.EndRefresh() 替换为myListView.IsRefreshing = false

  2. 使用Device.BeginInvokeOnMainThread(() => {//update list and endRefresh})

  3. 使用async void refreshData() 代替async Task refreshData()

【问题讨论】:

  • 尝试使用Device.BeginInvokeOnMainThread(//你的代码相关更新列表视图);
  • 刚试过,没用。
  • 尝试只使用 myListView.EndRefresh(); ..remove myListView.Isreferhing=false
  • 我也试过了。没什么区别。
  • 找到解决办法了吗

标签: c# android visual-studio-2015 xamarin.forms pull-to-refresh


【解决方案1】:

当我在 Page Contructor 中启动 ListView 刷新并在加载数据后停止它时,我个人会遇到这个问题。有时(经常)Xamarin.Forms ListView 不会取消刷新动画。

我相信您在使用 Android SwipeRefreshLayout 时遇到了一个很常见的问题:在调用 setRefreshing(false) 之后,它可能不会停止刷新动画。原生 Android 开发者使用以下方法:

swipeRefreshLayout.post(new Runnable() {
    @Override
        public void run() {
            mSwipeRefreshLayout.setRefreshing(refreshing);
    }
});

有趣的是,Xamarin.Forms 在设置初始刷新状态时使用了这种方法 (code);然而,这还不够。你需要一个自定义渲染器:

public class ExtendedListViewRenderer : ListViewRenderer
{
    /// <summary>
    /// The refresh layout that wraps the native ListView.
    /// </summary>
    private SwipeRefreshLayout _refreshLayout;

    public ExtendedListViewRenderer(Android.Content.Context context) : base(context)
    {
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _refreshLayout = null;
        }
        base.Dispose(disposing);
    }

    protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
    {
        base.OnElementChanged(e);
        _refreshLayout = (SwipeRefreshLayout)Control.Parent;
    }

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == ListView.IsRefreshingProperty.PropertyName)
        {
            // Do not call base method: we are handling it manually
            UpdateIsRefreshing();
            return;
        }
        base.OnElementPropertyChanged(sender, e);
    }

    /// <summary>
    /// Updates SwipeRefreshLayout animation status depending on the IsRefreshing Element 
    /// property.
    /// </summary>
    protected void UpdateIsRefreshing()
    {
        // I'm afraid this method can be called after the ListViewRenderer is disposed
        // So let's create a new reference to the SwipeRefreshLayout instance
        SwipeRefreshLayout refreshLayoutInstance = _refreshLayout;

        if (refreshLayoutInstance == null)
        {
            return;
        }

        bool isRefreshing = Element.IsRefreshing;
        refreshLayoutInstance.Post(() =>
        {
            refreshLayoutInstance.Refreshing = isRefreshing;
        });
    }
}

【讨论】:

    【解决方案2】:

    试试这个:

    async void refreshData()
    {
        Device.BeginInvokeOnMainThread(() => {
            myListView.BeginRefresh();
            var apiCallResult = await App.Api.myApiGetCall();
            myListView.ItemsSource = apiCallResult;
            myListView.EndRefresh();
        });
    }
    

    显然,不再需要“任务”了。

    如果错误仅发生在 Android 中,则可能只是 Android 处理线程的方式。它不允许线程直接更改视觉元素。有时当我们尝试这样做时,它会抛出一个静默异常并且代码操作没有实际效果。

    【讨论】:

    • > 显然你也不需要“任务”返回。好吧,你可能需要它,否则你会在 UI 线程中执行网络调用
    • 你是对的@NikolaiDoronin。有道理。所以我回答中的整个方法都是错误的。这不是最好的方法
    【解决方案3】:

    你需要遵循 MVVM 模式

    在您的 ViewModel 上,您需要:

    • 实现INotifyPropertyChanged

    • 定义如下属性:

       private bool _IsRefreshing;
       public bool IsRefreshing
       {
           get { return _IsRefreshing; }
           set { SetProperty(ref _IsRefreshing, value; } 
           /*So every time the property changes the view is notified*/
       }
      
    • 定义获取数据的方法,在您的情况下为refreshData()

    • 在需要时切换 IsRefreshing true/false

    在您的主页上,您需要:

    • 使用SetPropertyValue 将listview itemSource 绑定到VM 属性

    • 将 ListView.IsRefreshing 绑定到 ViewModel 的 IsRefreshing:

      MyListView.SetBinding&lt;MyViewModel&gt;(ListView.IsRefreshing, vm =&gt; vm.IsRefreshing);

    Here 是一篇关于 INotifyPropertyChanged 的​​精彩文章

    【讨论】:

    • 为什么这个答案被否决了?即使共享的代码使用了额外的框架,也应该这样做。但想法是为IsRefreshing 属性进行单向绑定,并使其实现INotifyPropertyChanged,并在更新列表的代码之后将其设置为false。
    • @Elisabeth 确实如此,但我可以确认这会导致标签页中出现无休止的刷新指示。我将它放在Task.Run(async () =&gt; await //.. 中,并且更改绑定到 ListView 的 VM 的“IsRefreshing”布尔属性有时不会将更改事件分配给 View。 Ingenator 所描述的只是一种“MVVM 模式”。仅此而已。
    • @Csharpest 在任务中更新时要小心。如果你已经跑出主线程或没有完成工作,那么你的属性更改将永远不会传播到视图。使用‘Device.BeginInvokeOnMainThread’进行更新。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-27
    相关资源
    最近更新 更多