Xamarin.Forms でListViewの高さが更新されない問題について
Xamarin.FormsのListViewの高さが更新されない問題
最近はListViewを使用することが多いのですが、これがまた厄介で、普通に使用する分にはいいのですがカスタマイズしようとすると色々問題が起きます。
今回起きた問題は、ListView項目の高さが更新されない問題です。
XAMLで項目の高さを指定することはできるのですが、例えば項目要素の文字数などが変わった際に高さが固定されたままなので文字の一部が表示されないと言った問題です。
実際に今回実装しようと思ったものは、アイテムをタップすると隠してあったLabelが表示される機能でした。
| → |
実装について
Labelの非表示などはIsVisibleで管理できるのですが、ListViewの要素には「x:Name」を使用することはできません。これはListViewの要素は複数あり、一つを指定する事ができないためです。
では、どうやって管理するのか。このようにListViewでまとめて管理するためにはデータバインディングを使用します。
ListViewのソースとして指定しているリストのクラスにIsVisible用のbool変数を用意しておくだけです。
<ViewCell>
<StackLayout>
<Label Text="{Binding .Question}" FontSize="25" TextColor="Black"/>
<Label Text="{Binding .Answer}" FontSize="20" IsVisible="{Binding .IsVisible}" />
</StackLayout>
</ViewCell>
このようにViewCellを作成します。次にListViewのソースのクラスを作成します。
public class QuestionData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[PrimaryKey, AutoIncrement]
public int Id { set; get; }
public string Question { set; get; }
public string Answer { set; get; }
private bool _isVisible;
public bool IsVisible
{
get { return _isVisible; }
set
{
if (_isVisible == value) return;
_isVisible = value;
OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ListViewの項目要素に変更があった場合、リアルタイムで画面に反映するためには、このOnPropertyChangedメソッドが必要となります。今回はこの部分に詳しく触れません。あとは、メインのコードにIsVisibleの値を変更する処理を追加するだけです。
void QuestionTapped(object sender, ItemTappedEventArgs e)
{
if (e.Item == null)
return;
var questionItem = e.Item as QuestionData;
if (questionItem.IsVisible)
{
questionItem.IsVisible = false;
}
else
{
questionItem.IsVisible = true;
}
}
こんな感じです。しかし、Androidではうまくいったのですが、iosでは実行しても画面に変化が起きませんでした。
解決策
Webで「Xamarin.Forms ListView 高さ」などと検索すると、ListViewの要素にHasUnevenRows="True"を指定すると出てくるのですが実装していました。
ではどうすればいいのか、更に調べて出てくるものは試しに実装してみたのですが結局解決には至りませんでした、、、
一応少しうまく行ったのが、こちらのサイトに載っている最後のカスタムレンダラーでした。
しかし、タップしても表示されるときと表示されないときがあり、製品としては不具合があったので採用できませんでした。
悩んだ挙げ句、次のメソッドを先程のQuestionTappedメソッドに追加しました。
private void RefreshData()
{
Device.BeginInvokeOnMainThread(() => {
MyListView.BeginRefresh();
MyListView.ItemsSource = null;
MyListView.ItemsSource = questionDatas;
MyListView.EndRefresh();
});
}
これは、ListViewのソースを一度nullとして初期化、その後もう一度リストを指定し直す処理をしています。なんとこれでiosでも正しく動きました。正直あまり良い実装方法であるとは思いませんが、、
まとめ
ListViewは複数のデータを表示するのが簡単な反面、色々な機能を追加しようとすると難しいですね。
今回も、QuestionとAnswerを最初から表示していれば何も問題なかったのですが、やはりデータが増えれば見づらくなってしまいます。そのため通常はAnswerを非表示にしておき、タップされたときのみ表示するようにするようにすれば見やすくなります。
最終的に動くものが実装できましたが、わざわざnullを指定してからという作業をいれるのはどうなのでしょうか。もっと良い実装方法があれば教えて下さい。
よく考えると前の記事Xamarin.FormsのLabelにタップイベントをつけるでもGestureRecognizersのClearがiosのみ正しく動作しない問題があったので、iosの実装には注意が必要ですね。
今回はこの辺で、ではまた!

