天天看點

UWP: ListView 中與滾動有關的兩個需求的實作

在 App 的開發過程中,ListView 控件是比較常用的控件之一。掌握它的用法,能幫助我們在一定程度上提高開發效率。本文将會介紹 ListView 的一種用法——擷取并設定 ListView 的滾動位置,以及擷取滾動位置處的項目。這裡多說一句,由于這個描述有點,是以本文的标題實在不好起。

舉個例子,如果你正在開發的應用有這樣一個需求,當使用者從一個清單頁(包括 ListView 控件)傳回到前一頁面時,你需要得到使用者在浏覽 ListView 中的内容到哪個位置以及哪一項了,以便告訴使用者最近浏覽項,并且可以讓使用者再次打開清單時,直接從上次浏覽的位置處繼續浏覽。如下圖:

UWP: ListView 中與滾動有關的兩個需求的實作

本文介紹了實作上述需求的方法。具體來說,這個需求可細分為兩個小需求,即:

擷取、設定 ListView 的滾動位置;

擷取 ListView 滾動位置處的項目。

以下我會通過上面配圖中的 Demo 應用逐一說明(本文末尾有源碼下載下傳連結),這個 Demo 包括兩個頁面,一個首頁 (MainPage),一個清單頁 (ItemsPage)。首頁中包括:

按鈕:可以導航到 ItemsPage;

最近浏覽資訊區域:可以檢視上次浏覽的項目,并提供一個按鈕可以導航到清單頁中上次浏覽的項目處;

而清單頁,則包括一個 ListView 控件,展示若幹個項目。

一、擷取、設定 ListView 的滾動位置

關于擷取、設定 ListView 的滾動位置,微軟已經提供了相關的例子,我在這個 Demo 中是直接套用的。這個功能主要是通過 ListViewPersistenceHelper 來實作的,它提供以下兩個方法:

這兩個方法中各有一個參考是委托類型,分别是 ListViewItemToKeyHandler 和 ListViewKeyToItemHandler,它們的作用是告訴這個類如何處理清單項與 Key 的對應關系,好使得該類可以正确地擷取或設定滾動位置。這裡的 Key 是 ListViewItem 所代表的項目的一個屬性(比如 Demo 中 Item 類的 Id 屬性),這個屬性的值在整個清單中是唯一的;而 Item 是在 Item 對象本身。在 Demo 中它們的實作分别如下:

實作這兩個方法後,重載清單頁的  OnNavigatingFrom 方法,在其中加入以下代碼,來實作擷取滾動位置并儲存:

繼續為頁面注冊 Loaded 事件,在 Loaded 事件中加入以下代碼來實作設定滾動位置:

這裡需要注意的是,設定滾動位置的方法是異步的,是以 Loaded 方法需要加上 async 修飾符。而上述代碼中對 navigationParameter 參數的判斷則是為了差別:在導航時是否定位到最近浏覽的位置,具體可參考 Demo 的代碼。

二、擷取 ListView 滾動位置處的項目

關于第二個需求的實作,我們首先需要明白以下三點:

ListView 的模闆 (Template) 中包括 ScrollViewer,我們可以通過 VisualTreeHelper 擷取到此控件;

ListView 提供 ContainerFromItem 方法,它使們可以通過傳遞 Item 擷取包括此 Item 的 Container,即 ListViewItem;

UIElement 提供 TransformToVisual 方法,可以得到某控件相對指定控件的位置轉換資訊;

是以我們的思路就是:得到 ListView 控件中的 ScrollViewer,并周遊 ListView 中所有的 Item,在周遊過程中,得到每一項目的 ListViewItem,并判斷它的位置是否位于 ScrollViewer 的位置中。以下是擷取 ListView 中目前所有可見項的代碼:

在上述代碼的 foreach 循環中的部分,正是我們前述思路的展現。而其中所調用的 IsVisibleToUser 方法,則是如何判斷某一 ListViewItem 是否在 ScrollViewer 中為目前可見。其代碼如下:

可以看出,我們是能過得到兩個 Rect 值。Rect 類型的值代表一個矩形區域的位置和大小,我們對這兩個值進行比較後,傳回最終的結果。

擷取 ListViewItem 的 Rect 值: element.TransformToVisual(container) 傳回的結果是 GeneralTransform 類型,這個值表明了 ListViewItem 相對于 Container(即 ScrollViewer)的位置轉換資訊。GeneralTransform 類型可能我們并不太熟悉,不過,從它派生出來的這些類: ScaleTransform、TranslateTransform ,我們就熟悉了,GeneralTransform 正是它們的基類。GeneralTransform 包括以下兩個重要的方法:

TransformPoint, 可以将得到的轉換資訊計算成 Point 值,表示某控件相對于另一控件的坐标位置

TransformBounds,可以将得到的轉換資訊計算成 Rect 值,表示某控件相對于另一控件的坐标位置及所占的區域。

是以,我們通過 TransformBounds 方法就得到了 ListViewItem 相對于 ScrollViewer 的位置和所占區域的資訊。

擷取 ScrollViewer 的 Rect 值: 直接執行個體化一個 Rect,以 0,0 作為你左上角的坐标位置點, ScrollViewer 的 ActualWidth 和 ActualHeight 作為其大小。

接下來,就是比較的過程:這裡,我們做了一個判斷,判斷是否要求元素 (ListViewItem) 完全在 ScrollViewer 中(而非僅部分在其中)。如果要求部分顯示即可,則隻要元素的 Top 小于 Container 的 Bottom 值,并且元素的 Bottom 大于 Container 的 Top;如果要求全部顯示,那麼算法是:元素的 Top 大于 Container 的 Top 并且元素的 Bottom 小于 Container 的 Bottom。如果您對語言描述或者代碼都還不明白,也可以在紙上畫一下進行比較。

接下來,我們照着 GetAllVisbleItems 方法的思路可以實作 GetFirstVisibleItem 方法,即擷取清單中第一個可見項,代碼可參考 Demo 的源碼,在此不再贅述。

我們在之前重載的方法 OnNavigatingFrom 中加上這句代碼,即可以擷取到使用者浏覽位置處的那一項。

至此,所有主要功能已經基本完成。

結語

本文介紹了如何擷取和設定 ListView 的滾動位置,以及擷取滾動位置處的那一項,前者主要是借助于 ListViewPersistenceHelper 來實作,後者則是通過擷取 ListViewItem 和 ScrollViewer 的 Rect 值并進行比較而最終實作的。如果您有更好的方法、不同的看見,請留言,共同交流。

源碼下載下傳

參考資料:

ListView Sample

How to get the first visible group key in the grouped listview

UWP: ListView 中與滾動有關的兩個需求的實作

作者:WPInfo

本文系作者原創,歡迎轉載;如需轉載,請注明出處。

公衆号:.NET之窗 (WinDotNET),更多原創、優質技術文章,歡迎掃碼關注。

繼續閱讀