水平方向的 ListView Xamarin.Forms

37

有没有办法在Xamarin.Forms中创建水平滚动的ListView,就像图片所示:

ListView Horizontal

这是我为垂直滚动做的:

var myListView = new ListView
{
    ItemTemplate = new DataTemplate(typeof(ImageCell))
};

我需要相同的内容,但希望在Xamarin Android中实现。 - Ajay Sharma
1
正确的做法是使用Carousel View,由Korayem在下面解释:https://dev59.com/BGAf5IYBdhLWcg3wu0xD#37245057。Xamarin.Forms不支持ListView中的水平滚动。您可以使用以下建议创建一个,但它们是不受支持的,并被认为是“hack”,这些不受支持的“hack”可能会在将来的Xamarin.Forms更新中出现问题。 - Brandon Minnick
据我所知,Carousel View仅支持滑动一次移动一个项目。 这与列表视图不同,后者允许一次滑过多个项目。请参见Ricardo's answer,其中提供了一个看起来更有用的选项(我还没有尝试过)。 - ToolmakerSteve
请在此类似的帖子中阅读我的答案: https://stackoverflow.com/a/55918563/10455239 - Giuseppe Laera
14个回答

16

自Xamarin Forms 2.3起,CarouselView实现了这一点,而且更多功能。在此处阅读更多信息

<ContentView HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
  <CarouselView ItemsSource="{Binding MyDataSource}">
    <CarouselView.ItemTemplate>
      <DataTemplate>
        <Label Text="{Binding LabelText}" />
      </DataTemplate>
    </CarouselView.ItemTemplate>
  </CarouselView>
</ContentView>

很不错的发现。CarouselView仍未被记录为此类,而且在回答问题时还不存在,但我认为如果您正在使用Xamarin Forms 2.2(或将来的版本),这应该是可行的解决方案。不过要小心,因为它还有一些问题(因为它很新)。CarouselView有时可能无法在Android上显示内容,并且程序化移动索引也会出现问题。 - Patrick Graham
Xamarin团队实际上在Xamarin Forms 2.2中将其删除,以准备在自己的nuget中发布。 - Korayem
他们现在已经在自己的nuget中发布了它 https://www.nuget.org/packages/Xamarin.Forms.CarouselView - Korayem
14
它并不能“完全做到那样”。它不可滚动,更适合“轻扫”。你无法像希望的那样滚动,而是在轻扫时精确定位到下一个或上一个项目。 - nicks
感谢您发布这个答案;这是在Xamarin.Forms中正确的方法。这是Xamarin.Forms Carousel View NuGet包的链接:https://www.nuget.org/packages/Xamarin.Forms.CarouselView/2.3.0-pre2 - Brandon Minnick

12

是的,从技术上讲你可以这样做。将旋转角度设置为270度(所有VisualElements都有一个可绑定的Rotation属性)。然而,这看起来像是一个次优解决方案,因为顶部和底部会有空白,并且你必须左右拖动视图才能完全查看所有内容。

public static readonly BindableProperty RotationProperty;
public static readonly BindableProperty RotationXProperty;
public static readonly BindableProperty RotationYProperty;

上面的代码来自于VisualElement类。下面的代码是我自己写的一个小样例。

                                              ∨∨∨                                                  
<ListView x:Name="MessagesListView" Rotation="270" ItemsSource="{Binding Items}" RowHeight="40">
  <ListView.ItemTemplate>
    <DataTemplate>
      <ViewCell>
        <ViewCell.View>
          <StackLayout>
            <!--mylayouthere-->
          </StackLayout>
        </ViewCell.View>
      </ViewCell>
    </DataTemplate>
  </ListView.ItemTemplate>
</ListView>

4
这样一来,我还在细胞内部也有了旋转的图像。创意萌芽! - Luigi Saggese
@LuigiSaggese,将图像向后旋转...确实是多重启示! - Shimmy Weitzhandler

11

正如其他人所说,Xamarin.Forms中没有现成的可用项。

然而,任何人都可以编写自己的自定义渲染器来实现此类型的控件。

正如Stephane Delcroix所提到的那样,您可以创建一个ScrollView,然后将StackLayout作为其子元素来创建相同的效果。

然后,您需要实现以下内容:

*) 可绑定属性,接受需要创建的(IEnumerableItemsSource属性。

*) 可绑定属性,接受需要创建的(DataTemplateItemTemplate属性。

*) 绑定代码,以实例化ItemTemplate的实例,取特定数据源项并将其呈现到StackLayout中。您还需要考虑移除的项目等。

*) 为项目选择附加事件处理程序/轻敲手势。

*) 实现选定状态/停用其他选定项目。

......等等,以获取完整实现。

以上所有方法存在的问题是它们适用于相对较小的项目列表。

但是,如果您正在寻找一个包含大量条目的长列表,则以上方法会有点不太理想,因为您要创建所有视图。即使您延迟加载它们,您仍然需要考虑所有视图的内存占用。

这就引出了另一种处理虚拟项的可能实现,这是一个完全不同的故事需要考虑。


嗨,谢谢提供信息。您有一个相关的例子吗?如果可以分享一下就太好了。 - Anoop
1
这是迄今为止最合理的答案。 - nicks
+1 绑定布局与 StackLayout Orientation="Horizontal":无需其他包或测试组件或任何其他东西:它只是有效的。我已经使用了这个。但仅适用于小集合,否则缺乏虚拟化将成为一个问题。 - Nk54

10

如上所述,没有标准的方法来实现此操作,但是使用标准的 ListView 和 @MillieSmiths 方法可以绕过此问题。

解决方案需要多层嵌套布局。从 ListView 开始,我们将其旋转 270 度,但这也会旋转我们的项目内容,因此我们需要再旋转回来 90 度。

旋转 ListView 会创建大量空白,通过将 ListView 包装在绝对布局中,我们可以解决这个问题(我们需要在其中添加额外的 contentview 以解决一些剪裁问题)。

最后,在代码后台中,我们需要呈现布局剪切。

完整解决方案如下:

<AbsoluteLayout x:Name="MessagesLayoutFrame" Padding="0" HorizontalOptions="FillAndExpand">
  <ContentView x:Name="MessagesLayoutFrameInner"  Padding="0"  HorizontalOptions="FillAndExpand">
    <ListView x:Name="MessagesListView"
              ItemsSource="{Binding Images}"
              RowHeight="240"
              VerticalOptions="Start"
              HeightRequest="240"
              WidthRequest="240"
              SeparatorVisibility="None"
              Rotation="270"
              HorizontalOptions="Center">
      <ListView.ItemTemplate>
        <DataTemplate>
          <ViewCell>
            <ContentView Rotation="90" Padding="12">
              <Image Source="{Binding Source}" Aspect="AspectFill" />
            </ContentView>
          </ViewCell>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  </ContentView>
</AbsoluteLayout>

对于后台代码,我们只需要检查是否已经设置好了,如果是,则让它继续运行。基本上我们正在查找页面的宽度(NameGrid只是其他地方的全宽容器),然后将直接的 ListView 容器向上移动一半的空白,并在底部剪裁另一半。

    bool hasAppearedOnce = false;
    protected override void OnAppearing() {
        base.OnAppearing();

        if (!hasAppearedOnce) {

            hasAppearedOnce = true;
            var padding = (NameGrid.Width - MessagesListView.Height) / 2;

            MessagesListView.HeightRequest = MessagesLayoutFrame.Width;
            MessagesLayoutFrameInner.WidthRequest = MessagesLayoutFrame.Width;
            MessagesLayoutFrameInner.Padding = new Thickness(0);
            MessagesLayoutFrame.Padding = new Thickness(0);
            MessagesLayoutFrame.IsClippedToBounds = true;
            Xamarin.Forms.AbsoluteLayout.SetLayoutBounds(MessagesLayoutFrameInner, new Rectangle(0, 0 - padding, AbsoluteLayout.AutoSize, MessagesListView.Height - padding));
            MessagesLayoutFrameInner.IsClippedToBounds = true;
             // */
        } 
    }

警告 不要使用<FRAMES>来进行布局的移动和旋转。它会在Windows Phone上崩溃。

P.S. 我确信这可以封装在一个漂亮的UserControl中供大家使用。


3
这个 NameGrid 是什么? - MetaSnarf
请注意,正如@niels所指出的那样,这需要平方项目。 - Peter
我也很难理解NameGrid是什么! - Amir Hajiha
NameGrid只是Forms页面上其他位置的一个元素,它占据了整个页面的宽度。需要它来获取整个页面的宽度。 - Peter
但我认为现在比创建时有更好的选择。然而,它确实展示了移动Xamarin Forms对象的灵活性。 - Peter

9
在Xamarin.Forms 4.0-pre版本中,您可以使用CollectionView(集合视图),该视图简化了这种类型的布局。
示例:
<CollectionView ItemsSource="{Binding Monkeys}">
    <CollectionView.ItemsLayout>
        <ListItemsLayout>
            <x:Arguments>
                <ItemsLayoutOrientation>Horizontal</ItemsLayoutOrientation>    
            </x:Arguments>
        </ListItemsLayout>
    </CollectionView.ItemsLayout>
</CollectionView>

7
像其他人所说,使用ListView不可能实现此功能,我认为这是Xamarin Forms的一个重大疏忽。我们需要在不仅在列表中动态显示数据驱动对象....来吧!!
然而,在Xamarin Labs项目中,有一个可以使用的GridView。它仍然存在一些问题,人们正在解决选择项目方面的一些错误。
https://github.com/XForms/Xamarin-Forms-Labs
似乎有人针对该问题有一个解决方法:
https://github.com/XForms/Xamarin-Forms-Labs/issues/236

同意。这个问题的正确答案不是旋转listview或将stacklayout包装在scrollview中。而是“哦,人们搞砸了这里”。 - ClintL
它也支持Windows Phone和UWP吗? - Emil

6

不,没有水平的ListView的方式。您可以将水平的StackLayout包装在水平的ScrollView中,以实现相同的视觉效果,但这并不完全相同,因为您将无法使用DataTemplating。


3

这个NuGet软件包非常适合你的情况。我以前使用过它,真的很喜欢:

https://github.com/SuavePirate/DynamicStackLayout

为了让事情变得更好,下载这3个Nuget包,可以在您的照片上进行图像加载、缓存和转换。照片将呈圆形,但此Nuget还有其他类型的转换:
Xamarin.FFImageLoading (https://github.com/luberda-molinet/FFImageLoading/wiki/Xamarin.Forms-API)
Xamarin.FFImageLoading.Forms
Xamarin.FFImageLoading.Transformations (https://github.com/luberda-molinet/FFImageLoading/wiki/Transformations-Guide)

这里是一段帮助你开始的代码:

<!--Add this code to the top of your page-->
xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
xmlns:fftransformations="clr-namespace:FFImageLoading.Transformations;assembly=FFImageLoading.Transformations"
xmlns:dynamicStackLayout="clr-namespace:SuaveControls.DynamicStackLayout;assembly=SuaveControls.DynamicStackLayout"


<!-- Here is your control inside a ScrollView. The property Photos is a list of images address (Urls)  -->

<ScrollView Orientation="Horizontal" HorizontalOptions="FillAndExpand">
    <dynamicStackLayout:DynamicStackLayout ItemsSource="{Binding Photos}" HorizontalOptions="Fill" Orientation="Horizontal" Padding="10, -0, 100, 10">
        <dynamicStackLayout:DynamicStackLayout.ItemTemplate>
            <DataTemplate>
                <StackLayout BackgroundColor="Transparent" >
                    <ffimageloading:CachedImage HorizontalOptions="Start" VerticalOptions="Center" DownsampleToViewSize="true" Aspect="AspectFit" Source="{Binding .}">
                        <ffimageloading:CachedImage.GestureRecognizers>
                            <TapGestureRecognizer Command="{Binding Path=PhotoCommand}" CommandParameter="{Binding .}" NumberOfTapsRequired="1" />
                        </ffimageloading:CachedImage.GestureRecognizers>
                        <ffimageloading:CachedImage.HeightRequest>
                            <OnPlatform x:TypeArguments="x:Double">
                                <On Platform="iOS" Value="50" />
                                <On Platform="Android" Value="60" />
                            </OnPlatform>
                        </ffimageloading:CachedImage.HeightRequest>
                        <ffimageloading:CachedImage.WidthRequest>
                            <OnPlatform x:TypeArguments="x:Double">
                                <On Platform="iOS" Value="50" />
                                <On Platform="Android" Value="60" />
                            </OnPlatform>
                        </ffimageloading:CachedImage.WidthRequest>
                        <ffimageloading:CachedImage.Transformations>
                            <fftransformations:CircleTransformation BorderHexColor="#eeeeee">
                                <fftransformations:CircleTransformation.BorderSize>
                                    <OnPlatform x:TypeArguments="x:Double">
                                        <On Platform="iOS" Value="10" />
                                        <On Platform="Android" Value="10" />
                                    </OnPlatform>
                                </fftransformations:CircleTransformation.BorderSize>
                            </fftransformations:CircleTransformation>
                        </ffimageloading:CachedImage.Transformations>
                    </ffimageloading:CachedImage>
                </StackLayout>
            </DataTemplate>
        </dynamicStackLayout:DynamicStackLayout.ItemTemplate>
    </dynamicStackLayout:DynamicStackLayout>
</ScrollView>

我希望可以帮到你 :)

1
到目前为止,这是最好的。我会查看更多你的NuGet。 - Olorunfemi Davis

3

谢谢@Damien,但我正在寻找Xamarin.Forms版本,你的版本是关于旧版MVVMCross的。无论何时感谢。 - Luigi Saggese
很酷。你看过这个吗?也许你可以从那个控件中剥离MVVMCross的东西,为该控件做一个xamforms包装器。再次强调,这个建议可能有点盲目。我还没有详细研究过那个Cheesebaron项目。 - Damien Sawyer

2
我已经尝试过上述“旋转”解决方案,它不仅是一个“丑陋”的解决方案,而且还有几个限制
  1. 列表视图WidthRequest必须与HeightRequest相同
  2. listView行高度不再正常工作,因为它变成了单元格宽度
  3. verticalalign变成horizontalalign等,不太可维护。
更好的选择是制作自己的自定义控件或者像我一样使用现有的HorizontalListView:https://www.nuget.org/packages/HorizontalListView1.1/这个很容易使用。你可以在这里找到源代码和文档。
实现:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
    x:Class="test.ListPage" 
    xmlns:Controls="clr-namespace:HorizontalList;assembly=HorizontalList"> 

<Controls:HorizontalListView ItemsSource="{Binding Categories}" ListOrientation="Horizontal"> 
  <Controls:HorizontalListView.ItemTemplate> 
    <DataTemplate> 
    <Label Text="{Binding Name}" /> 
    </DataTemplate> 
  </Controls:HorizontalListView.ItemTemplate> 
  </Controls:HorizontalListView>
</ContentPage>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接