Xamarin.FormsでListViewの高さを自動調節する


## 画面いっぱいに表示されてしまう

ListView を素のまま配置すると、項目数の多寡にかかわらず、画面いっぱいに ListView が表示されてしまった。
この例では項目が2個しかないんだから、3行目以降は表示してほしくないんだが...


```xml:XAML
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:vm="clr-namespace:MyApp.ViewModels"
    Padding="30"
    x:Class="MyApp.Views.MainPage">

    <ContentPage.BindingContext>
        <vm:MainPageViewModel />
    </ContentPage.BindingContext>

    <ContentView BackgroundColor="Gray" Padding="5">
        <ListView ItemsSource="{Binding Items}" />
    </ContentView>

</ContentPage>
```

```c#:ViewModel
using System.Collections.Generic;
using System.Windows.Input;

namespace MyApp.ViewModels
{
    public class MainPageViewModel
    {
        public List<string> Items { protected set; get; } = new List<string>();

        public MainPageViewModel()
        {
            Items.Add("First Item");
            Items.Add("Second Item");
		}
    }
}
```

![before.png](https://qiita-image-store.s3.amazonaws.com/0/169798/cbc6942c-a24c-805f-8042-a7cd02b7f3ab.png "before.png")

そもそも、ListView は Page にめいっぱい表示するものであって、画面の一部として表示することを前提としていないらしい。

https://forums.xamarin.com/discussion/19874/listview-inside-stacklayout-a-height-problem

>ListView is not intended to always size itself to just fit its contents.
>
Consider the common use-case: a ListView is added to a Page but the data in the list will be downloaded from a web service, so initially it is empty (the data comes later after an async web call). What size should the ListView be? It is intended to be a scrolling container that will be able to display all its cells, by scrolling, not by initially sizing itself. The number of cells will never affect its height.


## 項目数に合った高さにする

しかし、アイテム数が膨大なリストならともく、アイテム数が大して多くないリストを複数個並べたいこともあるかと思う。
そういう場合は ListView の高さが自動調整されてくれないと困る。

実はXAMLを少し修正するだけで可能な模様。

```xml:XAML
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:vm="clr-namespace:MyApp.ViewModels"
    Padding="30"
    x:Class="MyApp.Views.MainPage">

    <ContentPage.BindingContext>
        <vm:MainPageViewModel />
    </ContentPage.BindingContext>

    <StackLayout>
        <ContentView BackgroundColor="Gray" Padding="5">
            <ListView ItemsSource="{Binding Items}" HasUnevenRows="false" RowHeight="40" />
        </ContentView>
    </StackLayout>

</ContentPage>
```

ContentView を StackLayout で囲んで、ListView のプロパティに、

- HasUnevenRows="false"
- RowHeight="任意の行高さ"

を追加する。

ListView のソースコードで、OnSizeRequest メソッドを見てみるとそんな感じになっていた。

https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Core/ListView.cs

ただ、なぜ StackLayout で囲む必要があるのかはよく分からない。
もうちょっとXAMLレイアウトの特性を調査する必要がありますね。

そして、その結果は...

![after.png](https://qiita-image-store.s3.amazonaws.com/0/169798/02334b12-71a7-b2db-4690-ebc2dba399db.png "after.png")


高さが縮まりました。


## スクロールを無効化

高さは調整されましたが、UIを触ってみると ListView が上下にスクロールしてしまいました。
これはカスタムレンダラーで対処する必要があるようです。

### PCL

```c#:カスタムView
using Xamarin.Forms;

namespace MyApp.Views
{
    public class FixListView : ListView
    {
        public FixListView()
        {
        }
    }
}
```

```xml:XAML
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:v="clr-namespace:MyApp.Views"
    xmlns:vm="clr-namespace:MyApp.ViewModels"
    Padding="30"
    x:Class="MyApp.Views.MainPage">

    <ContentPage.BindingContext>
        <vm:MainPageViewModel />
    </ContentPage.BindingContext>

    <StackLayout>
        <ContentView BackgroundColor="Gray" Padding="5">
            <v:FixListView ItemsSource="{Binding Items}" HasUnevenRows="false" RowHeight="40" />
        </ContentView>
    </StackLayout>

</ContentPage>

```

### iOS

```c#:カスタムレンダラー
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

using MyApp.Views;
using MyApp.iOS.Renderers;

[assembly: ExportRenderer(typeof(FixListView), typeof(FixListViewRenderer))]

namespace MyApp.iOS.Renderers
{
    public class FixListViewRenderer: ListViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
        {
            base.OnElementChanged(e);
            Control.ScrollEnabled = false;
        }
    }
}
```

### Android

```c#:カスタムレンダラー
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

using MyApp.Views;
using MyApp.Droid.Renderers;

[assembly: ExportRenderer(typeof(FixListView), typeof(FixListViewRenderer))]

namespace MyApp.Droid.Renderers
{
    public class FixListViewRenderer: ListViewRenderer
    {
        public override bool DispatchTouchEvent(Android.Views.MotionEvent e)
        {
            if (e.Action == Android.Views.MotionEventActions.Move) {
                return true;
            }
            
            return base.DispatchTouchEvent(e);
        }
    }
}
```

これで ListView がスクロールしなくなりました。
OKのようですね。

とりあえずは以上です。


## こちらを参考にしました
Is it Possible to disable scrolling in ListView?
https://forums.xamarin.com/discussion/48783/is-it-possible-to-disable-scrolling-in-listview

Disable scrolling in listview
https://stackoverflow.com/questions/7611085/disable-scrolling-in-listview