UWP 帶左右滾動按鈕的橫向ListView———仿NetFlix首頁河的設計

  • 2019 年 11 月 11 日
  • 筆記

1. 需求

 

也是之前寫的控件了,模仿NetFlix的河的設計。

大體要求如下:

1. 橫向的ListView

2. 左右按鈕,可以左右移動河卡片,左右的滾動條不可見

3. 左右按鈕僅在鼠標Hover事件中可見

大家可以看下NetFlix uwp版,國內愛奇藝也是這樣設計的,芒果TV之前是,但是新版去掉了這種風格。

 

 

其實我也是一個菜鳥,一開始糾結怎麼寫這個控件,怎麼傳遞ItemsSource,而且還在SO上發起了提問

How to customize a horizontal listview with left/right button on uwp?

很遺憾,uwp幾乎滅絕,沒有人回答我的問題。於是各種尋找,終於發現了大神  Pieter Nijs 

寫的文章https://blog.pieeatingninjas.be/2016/01/17/custom-uwp-control-step-through-listview/

大神的需求和我的類似,見圖

 

 

於是膜拜文章,寫下了適合我自己的控件見圖:

 

 

2. 實現

 

?好了,下面開始擼代碼,先新建一個用戶控件,名字叫 StepThroughListView ,看看Xaml的布局,

<Grid>          <ListView              x:Name="ListViewRiver"              HorizontalAlignment="Stretch"              SingleSelectionFollowsFocus="True"              ScrollViewer.VerticalScrollBarVisibility="Hidden"              ScrollViewer.HorizontalScrollBarVisibility="Hidden"              ScrollViewer.VerticalScrollMode="Disabled"              ScrollViewer.HorizontalScrollMode="Disabled"              >                <ListView.ItemsPanel>                  <ItemsPanelTemplate>                      <VirtualizingStackPanel Orientation="Horizontal"/>                  </ItemsPanelTemplate>              </ListView.ItemsPanel>            </ListView>            <Button x:Name="ButtonLeft"/>            <Button x:Name="ButtonRight" HorizontalAlignment="Right"/>      </Grid>

 

這樣大體創建了一個這樣的橫向的ListView,橙色矩形為ListView,灰色圓圈是左右按鈕Button,藍色是ListViewItem

按鈕是默認垂直居中

 

 

然後我們需要給ListView賦值ItemsSource、SelectedItem、ItemTemplate,這些需要在C#代碼中使用依賴屬性來完成。

        public IEnumerable ItemsSource          {              get { return (IEnumerable)GetValue(ItemsSourceProperty); }              set { SetValue(ItemsSourceProperty, value); RaisePropertyChanged(); }          }            public static readonly DependencyProperty ItemsSourceProperty =              DependencyProperty.Register(nameof(ItemsSource), typeof(IEnumerable),                  typeof(StepThroughListView), new PropertyMetadata(DependencyProperty.UnsetValue));            public DataTemplate ItemTemplate          {              get { return (DataTemplate)GetValue(ItemTemplateProperty); }              set { SetValue(ItemTemplateProperty, value); RaisePropertyChanged(); }          }            public static readonly DependencyProperty ItemTemplateProperty =              DependencyProperty.Register(nameof(ItemTemplate), typeof(DataTemplate),                  typeof(StepThroughListView), new PropertyMetadata(DependencyProperty.UnsetValue));            public object SelectedItem          {              get { return GetValue(SelectedItemProperty); }              set              {                  if (SelectedItem != value)                  {                      SetValue(SelectedItemProperty, value);                      RaisePropertyChanged();                  }              }          }            public static readonly DependencyProperty SelectedItemProperty =              DependencyProperty.Register(nameof(SelectedItem), typeof(object),                  typeof(StepThroughListView), new PropertyMetadata(DependencyProperty.UnsetValue));

 

然後在Xaml中,需要給ListView做雙向綁定,

ItemsSource="{Binding ItemsSource, ElementName=root}"  SelectedItem="{x:Bind SelectedItem, Mode=TwoWay}"  ItemTemplate="{x:Bind ItemTemplate, Mode=TwoWay}"

 

接下來就可以處理左右按鈕的響應事件了,因為橫向的ListView左右滾動事件被我們禁用了,所以需要手動控制。

首先定義一個私有變量

ScrollViewer _InternalListScrollViewer;

在ListView的Loaded事件中,通過VisualTreeHelper查找?ListView內部的ScrollViewer。

        private void ListView_Loaded(object sender, RoutedEventArgs e)          {              _InternalListScrollViewer = TreeHelper.FindVisualChild<ScrollViewer>((DependencyObject)sender);          }

public static TChild FindVisualChild<TChild>(DependencyObject obj) where TChild : DependencyObject          {              if (obj == null) return null;                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)              {                  DependencyObject child = VisualTreeHelper.GetChild(obj, i);                  if (child != null && child is TChild found)                      return found;                  else                  {                      TChild childOfChild = FindVisualChild<TChild>(child);                      if (childOfChild != null)                          return childOfChild;                  }              }              return null;          }

 

 

左右按鈕的Tapped事件,左按鈕和右按鈕的的方向正好相反。所以合併一下

private void ButtonRight_Tapped(object sender, TappedRoutedEventArgs e)          {              ScrollList(shouldScrollDown: true);          }            private void ButtonLeft_Tapped(object sender, TappedRoutedEventArgs e)          {              ScrollList(shouldScrollDown: false);          }            private void ScrollList(bool shouldScrollDown)          {              SelectedItem = null;                var step = Math.Floor(Window.Current.Bounds.Width / 400);                if (!shouldScrollDown)                  step *= -1;                //_InternalListScrollViewer.ScrollToVerticalOffset(              //    _InternalListScrollViewer.VerticalOffset + height);              System.Diagnostics.Debug.WriteLine(_InternalListScrollViewer.ScrollableWidth);              _InternalListScrollViewer.ChangeView(_InternalListScrollViewer.HorizontalOffset + step, null, null);          }

 

上面的變量step,我用的是根據窗體大小寬度來決定步長。不過你也可以用自己的一個固定值,比如2。

然後我又加了一個屬性,叫AlwaysShowButton。如果設置Visible,那麼左右按鈕一直可見。

設置為Collapse的話,只有當鼠標移動到ListView上,才會顯示左右按鈕。

 

 3. 源碼&用法

 

好了,啰嗦那麼多,控件的代碼奉上:

https://github.com/hupo376787/StepThroughListView

 

用法也很簡單:

    <ctl:StepThroughListView            AlwaysShowButton="Collapsed"            ItemsSource="{x:Bind product}"            Tapped="StepThroughListView_Tapped">      </ctl:StepThroughListView>          private void StepThroughListView_Tapped(object sender, TappedRoutedEventArgs e)      {          var item = (sender as Controls.StepThroughListView).SelectedItem as ProductItem;          if (item != null)          {              //TODO...          }      }

 

 

4. 結語

 

特別感謝   Pieter Nijs 。

 

 

OK,下班!!!