Update list view on expand

2019-07-28 04:39发布

问题:

I am coding a Xamarin.Forms project and I have a list view but whenever I show hidden content, for example, make an entry visible it the ViewCell overlaps the one beneath it.

Is there a way I could .Update() the listview or something to refresh it and make them all fit.

I don't want the refresh to cause it to go back to the top though.

Android seems to be able to automatically update the height when I show something.

I tried using HasUnevenRows="True" but that still didn't fix it.

Code:

Message.xaml

<StackLayout>
        <local:PostListView x:Name="MessageView" HasUnevenRows="True" IsPullToRefreshEnabled="True" Refreshing="MessageView_Refreshing" SeparatorVisibility="None" BackgroundColor="#54a0ff">
            <local:PostListView.ItemTemplate>
                <DataTemplate>
                    <local:PostViewCell>
                        <StackLayout>
                            <Frame CornerRadius="10" Padding="0" Margin="10, 10, 10, 5" BackgroundColor="White">
                                <StackLayout>
                                    <StackLayout x:Name="MessageLayout" BackgroundColor="Transparent" Padding="10, 10, 15, 10">
                                        ...
                                        <Label Text="{Binding PostReply}" FontSize="15" TextColor="Black" Margin="10, 0, 0, 10" IsVisible="{Binding ShowReply}"/>
                                        <StackLayout Orientation="Vertical" IsVisible="{Binding ShowReplyField}" Spacing="0">
                                            <Entry Text="{Binding ReplyText}" Placeholder="Reply..." HorizontalOptions="FillAndExpand" Margin="0, 0, 0, 5"/>
                                            ...
                                        </StackLayout>
                                        <StackLayout x:Name="MessageFooter" Orientation="Horizontal" IsVisible="{Binding ShowBanners}">
                                            <StackLayout Orientation="Horizontal">
                                                ...
                                                <Image x:Name="ReplyIcon" Source="reply_icon.png" HeightRequest="20" HorizontalOptions="StartAndExpand" IsVisible="{Binding ShowReplyButton}">
                                                    <Image.GestureRecognizers>
                                                        <TapGestureRecognizer Command="{Binding ReplyClick}" CommandParameter="{Binding .}"/>
                                                    </Image.GestureRecognizers>
                                                </Image>
                                                ... 
                                            </StackLayout>
                                            ...
                                        </StackLayout>
                                    </StackLayout>
                                </StackLayout>
                            </Frame>
                        </StackLayout>
                    </local:PostViewCell>
                </DataTemplate>
            </local:PostListView.ItemTemplate>
        </local:PostListView>
    </StackLayout>

Message.cs

    using Newtonsoft.Json;
 using SocialNetwork.Classes;
 using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace SocialNetwork
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MessagePage : ContentPage
{
    public MessagePage()
    {
        InitializeComponent();
        LoadPage();
    }

    private async void LoadPage()
    {
        await LoadMessages();
    }

    private async void RefreshPage()
    {
        await LoadMessages();
        MessageView.EndRefresh();
    }

    private async Task LoadMessages()
    {
        //*Web Request*
        MessageView.ItemsSource = FormatPosts(this, Navigation, page_result);
        ...
    }

    public IList<MessageObject> FormatPosts(Page page, INavigation navigation, string json)
    {
        IList<MessageObject> Posts = new List<MessageObject>() { };
        var messages = JsonConvert.DeserializeObject<List<Message>>(json);

        foreach (var message in messages)
        {
            MessageObject mo = MessageObject.CreateMessage(...);
            Posts.Add(mo);
        }

        return Posts;
    }
    public async void ShowOptionActions(string id, string poster_id, object message)
    {
        ...
    }

    public async void ShowReportOptions(string id, string poster_id)
    {
        ...
    }

    public void SubmitReplyClick(string id, object msg)
    {
        ...
    }

    public async void SendReplyAsync(string id, object msg, string reply)
    {
        await SendReply(id, msg, reply);
    }

    public void ReplyCommandClick(string id, object msg)
    {
        MessageObject message = (MessageObject) msg;
        message.ShowReplyField = message.ShowReplyField ? false : true;
        //Update Cell Bounds
    }

    private async Task SendReply(string id, object msg, string reply)
    {
        MessageObject message = (MessageObject)msg;
        ...
        message.PostReply = reply;

        //Update Cell Bounds
    }

    public async void LikeMessageClick(string id, object message)
    {
        await LikeMessage(id, message);
    }

    private async Task LikeMessage(string id, object msg)
    {
       ...
    }

    public async void DeleteMessage(string id, object msg)
    {
        MessageObject message = (MessageObject)msg;

        message.ShowBanners = false;
        message.ShowReply = false;

        ...

        //Update Cell Bounds
    }

    public async Task ReportMessage(...)
    {
        ...
    }

    private void MessageView_Refreshing(object sender, EventArgs e)
    {
        RefreshPage();
    }
}

public class MessageObject : INotifyPropertyChanged
{
    private Boolean showBannersValue = true;
    private string replyValue = String.Empty;
    private bool showReplyValue;
    private bool showReplyButtonValue;
    private bool showReplyFieldValue;
    private Command replyCommandValue;
    private Command replySubmitValue;
    private string replyTextValue;
     ...

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private MessageObject(...)
    {
        ...
    }

    public static MessageObject CreateMessage(...)
    {
        return new MessageObject(...);
    }

    public Boolean ShowBanners
    {
        get
        {
            return this.showBannersValue;
        }

        set
        {
            if (value != this.showBannersValue)
            {
                this.showBannersValue = value;
                NotifyPropertyChanged();
            }
        }
    }

    public Boolean ShowReplyField
    {
        get
        {
            return this.showReplyFieldValue;
        }

        set
        {
            if(value != this.showReplyFieldValue)
            {
                this.showReplyFieldValue = value;
                NotifyPropertyChanged();
            }
        }
    }

    public string PostReply
    {
        get
        {
            return this.replyValue;
        }

        set
        {
            if (value != this.replyValue)
            {
                this.replyValue = value;
                NotifyPropertyChanged();
            }
        }
    }

    public Boolean ShowReply
    {
        get
        {
            return this.showReplyValue;
        }

        set
        {
            if(value != this.showReplyValue)
            {
                this.showReplyValue = value;
                NotifyPropertyChanged();
            }
        }
    }

    public Boolean ShowReplyButton
    {
        get
        {
            return this.showReplyButtonValue;
        }

        set
        {
            if (value != this.showReplyButtonValue)
            {
                this.showReplyButtonValue = value;
                NotifyPropertyChanged();
            }
        }
    }

      public string ReplyText
    {
        get
        {
            return this.replyTextValue;
        }

        set
        {
            if(value != this.replyTextValue)
            {
                this.replyTextValue = value;
                NotifyPropertyChanged();
            }
        }
    }

    public Command ReplyClick
    {
        get
        {
            return this.replyCommandValue;
        }

        set
        {
            if (value != this.replyCommandValue)
            {
                this.replyCommandValue = value;
                NotifyPropertyChanged();
            }
        }
    }

    ...

}
}

回答1:

Save your IList<MessageObject> which gets returned from your FormatPosts method in a field IList<MessageObject> _messages = new List<MessageObject>()

And use the following snippet to update the ListView whenever you need, includes a check to see if the device runs on iOS:

if(Device.RuntimePlatform == Device.iOS) 
{ 
    MessageView.ItemsSource = null; 
    MessageView.ItemsSource = _messages; 
}


回答2:

Especially with iOS there are issues resizing rows in a ListView according to changes of cells (see here). There is a method ForceUpdateSize on Cell, which should notify the ListView that the size of the cell has changed, which should cause the ListView to resize its rows.



回答3:

Oh, I faced the same thing. I guess that you just need to add this somewhere in your listview:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <Grid Grid.row='0'>
    ...
    </Grid>

 // This, in my case, makes my cell expand when it's true. Normal behavior
    <Grid Grid.row='1' isVisible="{Binding Expand}">
    ...
    </Grid>
</Grid>

Plus, if you want to update cells individually, I use a CustomObservableCollection:

public class CustomObservableCollection<T> : ObservableCollection<T>
    {
        public CustomObservableCollection() { }
        public CustomObservableCollection(IEnumerable<T> items) : this()
        {
            foreach(var item in items)
                this.Add(item);
        }
        public void ReportItemChange(T item)
        {
            NotifyCollectionChangedEventArgs args =
                new NotifyCollectionChangedEventArgs(
                    NotifyCollectionChangedAction.Replace,
                    item,
                    item,
                    IndexOf(item));
            OnCollectionChanged(args);
        }
    }

With a Custom ListView to do ItemClickCommand:

public class CustomListView : ListView
    {
#pragma warning disable 618
        public static BindableProperty ItemClickCommandProperty = BindableProperty.Create<CustomListView, ICommand>(x => x.ItemClickCommand, null);
#pragma warning restore 618

        public CustomListView(ListViewCachingStrategy cachingStrategy = ListViewCachingStrategy.RetainElement) :
                base(cachingStrategy)
        {
            this.ItemTapped += this.OnItemTapped;
        }

        public ICommand ItemClickCommand
        {
            get { return (ICommand)this.GetValue(ItemClickCommandProperty); }
            set { this.SetValue(ItemClickCommandProperty, value); }
        }

        private void OnItemTapped(object sender, ItemTappedEventArgs e)
        {
            if(e.Item != null && this.ItemClickCommand != null && this.ItemClickCommand.CanExecute(e.Item))
            {
                this.ItemClickCommand.Execute(e.Item);
                this.SelectedItem = null;
            }
        }
    }

then in xaml:

...
...
<Customs:CustomListView
      HasUnevenRows="true"
      ItemsSource="{Binding PersonList}"
      IsPullToRefreshEnabled="True"
      RefreshCommand="{Binding DoRefreshCommand}"
      ItemClickCommand="{Binding ItemClickCommand}">
...
...
</Customs:CustomListView>

Finally:

public Command<Person> ItemClickCommand { get; set; }
...
ItemClickCommand = new Command<Person>(SelectionExecute);
...
private void SelectionExecute(Person arg)
        {
            arg.Expand = !arg.Expand;

            foreach(var item in PersonList)
            {
                if(item.Key == arg.Id)// you will change this probably 
                    item.ReportItemChange(arg);
            }
        }

Hope it help a bit :)