Sprout 工程:構建 Android 月份選擇器

已發表: 2020-06-26

注意:本文基於截至 2020 年 6 月 1 日的 Material Components 版本1.2.0-beta01

在我在 Sprout Social 的一個小型 Android 團隊工作的三年半中,激勵我每天上班的主要因素之一是我們公司的自由和信任,可以以我們認為最好的方式解決問題。

自由研究和探索我們認為必要的問題的許多不同解決方案,同時考慮交付產品更新的時間框架,使我們能夠為我們的客戶和我們的軟件找到最佳解決方案。

其中一項挑戰涉及為我們的新移動報告功能構建 UI 組件。 這個新組件是一個月份選擇器,它允許我們的用戶確定分析報告的日期範圍。

我們選擇的起點是現有的材料組件庫。 這個庫不是從頭開始,而是積極維護並與材料規範保持一致。 以這個庫為基礎,我們可能會減少我們必須自己編寫的邏輯量。

在本文中,我將介紹我們如何處理此過程、構建 Sprout Android 應用程序的一些獨特因素、沿途出現(並已修復)的一些“陷阱”,以及如果您正在從事類似的項目。

介紹

Android Material Components 1.1.0 Release 引入了一個新的 Date Picker UI 組件。 這個新的MaterialDatePicker在 AppCompat CalendarView上的一個受歡迎的新增功能是能夠使用日曆視圖或文本輸入字段選擇日期範圍。

舊的 AppCompat CalendarView 不是很靈活。 對於要解決的有限用例來說,這是一個很好的組件; 也就是說,選擇單個日期和可選的最小和最大日期來指定允許的日期範圍限制。

新的 MaterialDatePicker 具有更大的靈活性,允許使用擴展的行為功能。 它通過一系列接口工作,人們可以實現這些接口來調整和修改選擇器的行為。

這種行為修改是在運行時通過MaterialDatePicker.Builder類上的一組構建器模式函數完成的。

這意味著我們能夠通過可組合的接口組件擴展這個MaterialDatePicker的基本行為。

注意:雖然MaterialDatePicker使用了許多不同的組件,但在本文中,我們將僅介紹日期選擇組件。

日期範圍選擇器

Sprout Social Android 團隊正在構建我們的分析報告部分。

這個新部分將允許我們的用戶選擇一組過濾器和一組報告將涵蓋的日期範圍。

MaterialDatePicker附帶了一些預構建的組件,我們可以利用這些組件來完成我們的用例。

對於我們最常見的情況,允許用戶選擇日期範圍,預構建的MaterialDatePicker就足夠了:

通過這個代碼塊,我們得到了一個允許用戶選擇日期範圍的日期選擇器。

每月日期選擇器

具有更多獨特日期選擇的 Sprout Social 報告之一是 Twitter 趨勢報告。

此報告與其他報告的不同之處在於,它不允許任何日期範圍,而是強制選擇單個月份,這意味著用戶只能選擇 2020 年 3 月與 2020 年 3 月 3 日至 3 月 16 日。

我們的 Web 應用程序通過使用下拉表單字段來處理此問題:

MaterialDatePicker無法使用上一節中討論的預構建的 Material Date Range Picker 來強制執行此類限制。 幸運的是,MaterialDatePicker 是使用可組合部件構建的,允許我們為特定用例擴展默認行為。

日期選擇行為

MaterialDatePicker利用DateSelector作為用於選擇器選擇邏輯的接口。

來自 Javadoc:

{@link MaterialCalendar<S>}用戶控制日曆如何顯示和返回選擇的界面……”

您會注意到MaterialDatePicker.Builder.dateRangePicker()返回RangeDateSelector的構建器實例,我們在上面的示例中使用了它。

此類是實現DateSelector的預構建選擇器。

頭腦風暴每月的日期選擇行為

對於我們的用例,我們想要一種方法讓我們的用戶選擇整個月份作為選定的日期範圍; 例如 2020 年 5 月、2020 年 4 月等。

我們認為上面引用的預先構建的RangeDateSelector讓我們大部分時間到達了那裡。 該組件允許用戶選擇日期範圍並強制執行[lower, upper] 界限

唯一缺少的是一種強制選擇以自動選擇整個月的方法。 RangeDateSelector的默認行為讓用戶選擇開始日期和結束日期。

我們想要一種行為,當用戶選擇一個月中的某一天時,選擇器會自動選擇整個月份作為日期範圍。

我們決定的解決方案是擴展RangeDateSelector ,然後覆蓋日期選擇行為以自動選擇整個月份。

幸運的是,我們可以從DateSelector接口重寫一個名為select(selection: Long)的函數。

當用戶在選擇器中選擇一天時,將調用此函數,所選日期以 UTC 毫秒為單位從紀元開始傳遞。

實施每月日期選擇行為

結果證明是最簡單的部分,因為我們有一個明確的函數,我們可以重寫以獲得我們想要的行為。

基本邏輯是這樣的:

  1. 用戶選擇一天。
  2. select()函數在從紀元開始的Long UTC 毫秒內調用所選日期。
  3. 從傳遞給我們的給定日期開始查找該月的第一天和最後一天。
  4. 打電話給super.select(1st of month) & super.select(last day of month)
  5. RangeDateSelector的父行為應按預期工作,並選擇月份作為日期範圍。

把它們放在一起

現在我們有了自定義MonthRangeDateSelector ,我們可以設置MaterialDatePicker

為了進一步舉例,我們可以像這樣處理選擇的結果:

結果將如下所示:

陷阱

只有一個主要問題導致難以達成此解決方案。

用於構建MonthRangeDateSelector的主要組件是DateSelector類和RangeDateSelector接口。 本文中使用的庫版本 (1.2.0-beta01) 限制了這兩個文件的可見性,以阻止擴展或實現它們。

結果,儘管我們可以成功編譯新的MonthRangeDateSelector ,但編譯器確實顯示了一個非常可怕的警告來阻止我們這樣做:

隱藏此編譯器警告的一種方法是添加@Suppress("RestrictedApi") ,如下所示:

這一經歷說明,儘管 Material Components Library 已經為 Android 開發者社區提供了一些很棒的新組件,但它仍然是一項正在進行的工作。

這個庫的很大一部分是對來自 Android 社​​區的反饋的開放性! 發現這個組件可見性限制後,我在 Github Project 上打開了一個 issue,甚至打開了一個 PR 來解決它。

Material Components 團隊和 Android 社​​區之間的這種開放式反饋循環為每個人帶來了良好的協作和成果。

結論

新的MaterialDatePicker具有一些開箱即用的強大功能,可能涵蓋大多數日期選擇用例。

但是,與 AppCompat CalendarView 之類的東西相比,它最好的部分是它是以可組合的方式構建的。 因此,它可以很容易地針對特定用例進行擴展和修改,而在CalendarView中完成這樣的事情要困難得多。

特別感謝

我想強調一些幫助同行評審這篇文章的人:

  • 尼克·勞特(Github)
  • 邁克沃爾夫森(Github)
  • 瑞恩菲利普斯(領英)
  • 盧卡斯·默勒斯(Github)
  • 米特·帕特爾(領英)