使用 Compose 構建 Wear OS 應用

image

適用于 Wear OS 的 Compose 已推出了 開發(fā)者預覽版跺株,使用 Compose 構建 Wear OS 應用间涵,不僅可以輕松遵循 Material You 指南纳鼎,同時可以將 Compose 的優(yōu)點發(fā)揮出來。開箱即用,幫助開發(fā)者使用更少的代碼快速構建出更精美的 Wear OS 應用。本文將通過 Wear Compose 主要的可組合項 (Composable) 來幫助您更好地了解如何使用 Compose 來進行構建扇谣。

如果您更喜歡通過視頻了解此內容,請 點擊此處 查看闲昭。

△ 主應用界面和通知界面

移動應用往往需要針對多種不同的界面種類進行開發(fā)罐寨,通常情況下,承載應用的主界面由 Fragment序矩、Activity 和 View 構成鸯绿,而在 Compose 的世界中則是由可組合項構成,作為開發(fā)者您需要了解并適應這種變化。除此之外瓶蝴,您還要針對額外的通知界面進行開發(fā)毒返,這意味著您需要在主應用界面之外提醒用戶注意某些重要信息,或讓他們在啟動主應用后繼續(xù)完成剛剛執(zhí)行的操作舷手,例如跟蹤跑步路線或者播放音樂拧簸。如果您使用了 Widget,也可以借助此類界面向用戶提供信息男窟。

△ Wear OS 中不同的應用界面

Wear OS 擁有 多種界面盆赤,在打造完備的 Wear OS 應用體驗時,需要您全部考慮:

  • 疊加層 (Overlay) 與移動應用的主界面類似歉眷,之前由 Activity弟劲、View 和 Fragment 組成,現在由可組合項構成姥芥,非常適合流程較長或較為復雜的交互;

  • 通知 (Notification) 界面同樣符合移動應用開發(fā)準則汇鞭;

  • 復雜功能 (Complication) 可在表盤中提供信息便于用戶直接查看凉唐,用戶只需在表盤上輕點一下,Complication 即可打開相關聯的應用霍骄,或執(zhí)行獨立操作台囱,例如飲水記錄功能,記錄您一天用水杯喝水的次數读整;

  • 圖塊 (Tile) 提供了更多展示內容的空間簿训,用戶可在表盤上通過任意方向滑動,快速訪問信息米间、執(zhí)行操作强品。

本文我們將著重介紹 Overlay 界面,并快速演示幾項 Wear 可組合項屈糊,了解它們的工作原理及其與移動平臺的相似之處的榛。

添加依賴項

在使用 Wear Compose 之前,我們需要先確保已有正確的依賴項逻锐,它同移動版 Compose 略有不同夫晌。在移動版上,主要使用的依賴項有 Material昧诱、Foundation晓淀、UI、Runtime 和 Compiler盏档,您還可以選擇使用 Navigation 和 Animation 依賴凶掰。但在 Wear 中,您可以使用一樣的 UI 依賴項,Runtime锄俄、Compiler 和 Animation 也都是相同的局劲。此外,其他一些方面也都是相同的奶赠,比如工具和一些 Compose 設計理念鱼填,比如使用雙向數據流。

但還是有一些不同之處的毅戈,比如您需要使用 Wear Compose Material 替換 Material苹丸,單從技術上來說移動版 Material 也是可以直接用的,但它并沒有針對 Wear 的一些特性進行優(yōu)化苇经,類似的我們還推薦您使用 Wear Navigation 來替換 Navigation赘理。

雖然我們建議直接使用 Wear Compose Material,但您仍然可以使用 Material Ripple 和 Material Icons Extended 等扇单。在添加正確的 Wear 依賴項后商模,您就可以著手進行開發(fā)了。

在 Wear Compose 文檔頁面 查看依賴項蜘澜。

Wear OS Material 庫介紹

Compose Wear OS Material 庫 提供了很多與移動平臺上相同的可組合項施流,您可以替換 Material 主題,并且自定義顏色鄙信、字體等瞪醋,不同的是它們都針對手表進行了優(yōu)化。接下來我們就為您介紹一些常用的可組合項装诡。

Button

Button 屬于緊湊的界面元素银受,用戶可通過點按 Button 執(zhí)行操作或做出選擇。

通過如下代碼可輕松添加 Button鸦采,雖然樣式與移動版不同宾巍,但代碼一樣。我們在代碼里初始化了一個 Button 可組合項渔伯,然后聲明了一些參數蜀漆,它們被稱為 Modifier,通過 Modifier 可以更改很多屬性咱旱,比如這里的 onClick确丢、same、enabled吐限,若您還想為 Button 添加一個圖標鲜侥,那就需要用到包含 painter、contentDescription 和 modifier 的 Icon 可組合項:

Button(
    modifier = Modifier.size (ButtonDefaults.LargeButtonSize),
    onClick = {... },
    enabled = enabledState
) {
    Icon(
        painter = painterResource (id = R.drawable.ic_phone),
        contentDescription = "phone",
        modifier = Modifier
            .size(24. dp)
            .wrapContentSize(align = Alignment.Center),
    )
}

△ Button 可組合項代碼

通過上述代碼诸典,我們可以創(chuàng)建精美小巧的 Button描函,顯示效果如下:

△ Button 代碼效果

Card

Card 可針對單一主題的內容和操作進行呈現,十分靈活。

如圖左側 Card 展示了一些圖標和文字舀寓,中間界面只保留了文字胆数,右側使用了一張圖片作為背景。

△ Card 用例

在 Wear OS 中互墓,主要有 AppCard 和 TitleCard 兩種 Card必尼,TitleCard 更側重文字展示,本文我們將著重介紹 AppCard篡撵。如下示例代碼創(chuàng)建了一個 AppCard判莉,并相繼通過 Image、Text 和 Column 定制內容:

AppCard(
    appImage = {
        Image(painter = painterResource(id = R.drawable.ic_message), …)
    },
    appName = { Text ("Messages") },
    time = { Text ("12m" ) },
    title = { Text("Kim Green") },
    onClick = { … },
    body = {
        Column(modifier = Modifier.fillMaxWidth()) {
            Text("On my way!")
        }
    },
)

△ Card 可組合項代碼

通過上述代碼育谬,我們創(chuàng)建出了一個精美的 Card券盅,顯示效果如下:

△ Card 代碼效果

如需獲得不同外觀的精美卡片顯示效果,僅需對代碼進行輕微調整即可膛檀。

Chip

Chip 旨在實現快捷的一鍵操作锰镀,對屏幕空間有限的 Wear 設備尤其有用,各種 Chip 變體也能讓您盡情揮灑創(chuàng)意咖刃。

下面是實現 Chip 可組合項的代碼和實現效果互站,您會發(fā)現它十分易用,同上述的一些代碼也大致相似:

Chip(
    modifier = Modifier.align(Alignment.CenterHorizontally),
    onClick = { … },
    enabled = enabledState,
    label = {
        Text(
            text = "1 minute Yoga",
            maxLines = 1,
            overflow = TextOverflow.Ellipsis
        )
    },
    icon = {
        Icon(
            painter = painterResource (id = R.drawable.ic_yoga),
            contentDescription= "yoga icon",
            modifier = Modifier
                .size(24.dp)
                .wrapContentSize(align = Alignment.Center),
        )
    },
)

△ Chip 可組合項代碼

△ Chip 代碼效果

ToggleChip

ToggleChip 和 Chip 類似僵缺,區(qū)別是用戶使用單選按鈕、切換開關踩叭、復選框:

如下所示 ToggleChip 用例:

△ ToggleChip 用例

圖左展示磕潮,只需輕點即可開關聲音;圖右則對 ToggleChip 進行了拆分容贝,提供了兩個不同的可點擊區(qū)域自脯,通過右側按鈕可將其關閉,點擊左側可以進入應用以便對鬧鐘進行編輯斤富。其代碼大同小異:

ToggleChip(
    modifier = Modifier.height(32.dp)
    checked = checkedState,
    onCheckedChange = { … }
    label = {
        Text(
            text = "Sound",
            maxLines = 1,
            overflow = TextOverflow.Ellipsis
        )
    }
)

△ ToggleChip 代碼

△ ToggleChip 代碼效果

CurvedText 和 TimeText

CurvedText 專門針對了圓形屏幕進行了優(yōu)化膏潮,這對圓形設備來說非常重要,而 TimeText 是基于 CurvedText 所創(chuàng)建的可組合項满力,它為您處理時間方面的所有文字顯示工作焕参。

如下代碼示例展示了如何創(chuàng)建 TimeText,并以 CurvedText 的方式進行展示:

var textBeforeTime by rememberSaveable { mutableStateOf("ETA 99 hours") }
// 首先創(chuàng)建在時間之前顯示的前綴字符串
TimeText(  
// 創(chuàng)建 TimeText 可組合項
    leadingCurvedContent = {
        BasicCurvedText(
            text = textBeforeTime,
            style = TimeTextDefaults.timeCurvedTextStyle()
        )
    },
// 指定 leadingCurvedContent油额,在時間文本前顯示文字叠纷,以 CurvedText 的方式在曲面設備上展示。
    leadingLinearContent = {
        Text(
            text = textBeforeTime,
            style = TimeTextDefaults.timeTextStyle()
        )
    },
// 指定 leadingLinearContent潦嘶,在時間文本前顯示文字涩嚣,常規(guī)顯示,適用于非曲面設備。
)

△ TimeText 代碼

通過上述代碼航厚,我們可以看到時間文本在圓形屏幕的顯示效果如下:

△ TimeText 顯示效果

ScalingLazyColumn

列表幾乎是每個應用中都會用到的組件顷歌,它縱向展示了連續(xù)的界面元素。但由于 Wear OS 手表設備的屏幕頂部和底部空間都非常小幔睬,因此 Material Design 引入了新的 ScalingLazyColumn 來進行縮放和透明度的展示眯漩,這樣有助于您在較小的空間內查看列表的內容。

image
image

△ ScalingLazyColumn 顯示效果

上圖展示了 ScalingLazyColumn 的效果溪窒,您可以看到隨著列表內元素的滑入坤塞,當列表的某一行靠近中心位置時,會放大到完整尺寸澈蚌,而隨著該元素的滑出摹芙,會變得越來越小 (并且變得更透明) 直至完全消失,這種效果十分有利于內容的展示宛瞄,內容更易于用戶閱讀浮禾。

ScalingLazyColumn 底層是由 LazyColumn 實現的,它只會對即將要在屏幕上呈現的內容進行處理份汗,這樣能夠高效地處理大量數據盈电,且能夠以縮放和透明效果進行展示,因此它應該成為 Wear OS 的默認組件杯活。

如上效果匆帚,ScalingLazyColumn 代碼示例如下:

val scalingLazyListState: ScalingLazyListState = rememberScalingLazyListState()
 
ScalingLazyColumn(
    modifier = Modifier.fillMaxSize(),
    verticalArrangement = Arrangement.spacedBy(6.dp),
    state = scalingLazyListState,
) {
    items(messageList.size) { message ->
        Card(...) {...}
    }
    item {
        Card(...) {...}
    }
}

△ ScalingLazyColumn 示例代碼

SwipeToDismissBox

這是大家十分熟悉的 Box 組件被視為界面中的一個容器,可在移動端使用旁钧,但 Wear 中有專屬版本 SwipeToDismissBox吸重,可用于您的布局,顧名思義它的功能是滑動以關閉歪今。在 Wear OS 中嚎幸,主要的手勢就是滑動,通過使用 SwipeToDismissBox 向右滑動,就相當于點擊了 "返回" 按鈕。

val state = rememberSwipeToDismissBoxState()
 
SwipeToDismissBox(
    state = state,
){ isBackground ->
    if (isBackground) {
        Box(modifier = Modifier. fillMaxSize().background(MaterialTheme.colors.secondaryVariant))
    } else {
        Column(
            modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.primary),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center,
        ){
            Text ("Swipe to dismiss", color = MaterialTheme.colors.onPrimary)
        }
    }
}

△ SwipeToDismissBox 示例代碼

在上述代碼中余掖,我們?yōu)?SwipeToDismissBox 設置了 state 屬性,這一點和移動端不同替废。通過傳遞的 state 獲取到 isBackground 回調值,它代表了此過程是否是滑動返回泊柬,您可以根據不同的狀態(tài)展示不同的內容舶担。下圖是最終的呈現效果:

△ SwipeToDismissBox 代碼效果

至此,我們介紹了一些 Wear OS 的可組合項彬呻,若您對移動端的可組合項開發(fā)有所了解衣陶,您可能會發(fā)現在 Wear OS 中開發(fā)基本是一樣的柄瑰,換句話說,您之前學習 Compose 時掌握的知識可以直接用于 Wear OS 開發(fā)剪况。

使用 Scaffold

Scaffold 可讓您實現具有基本 Material Design 布局結構的界面教沾,它可為最常見的頂層 Material 組件 (例如 TopBar、BottomBar译断、FloatingActionButton 和 Drawer) 提供槽位授翻。使用 Scaffold 時,您可以確保這些組件能夠正確放置并協同工作孙咪。而在 Wear OS 中堪唐,它也有著專屬的版本,除了同移動版相同的 content 組件之外翎蹈,額外提供了以下三個主要組件:

△ Wear Scaffold 中的三個主要組件
  1. TimeText: 可以將時間置于屏幕的頂部淮菠,我們已經介紹過它,具體請參考上文中關于 TimeText 的部分荤堪;

  2. Vignette: 可在屏幕周圍為您提供漂亮的暈影效果合陵,如上圖中所示;

  3. PositionIndicator: 也稱為滾動指示器澄阳,是屏幕右側的指示符拥知,用于根據您傳入的狀態(tài)對象類型顯示當前指示符的位置。將它放置于 Scaffold 中是由于屏幕是弧形的碎赢,因此位置指示器需要位于表盤中央 (Scaffold)低剔,而不僅僅是在視口 (viewport) 中央。否則肮塞,指示器可能會被截斷襟齿。

Scaffold 設計

△ Scaffold 設計層級

在進行 Scaffold 的設計時,請參考上圖中的層級順序進行考慮峦嗤,首先要做的是對 App 進行設置,其次是設置 MaterialTheme 來自定義一些應用的外觀和風格屋摔,緊接著是考慮如何放置 Scaffold烁设,最后才是對 Content 的定義。這個順序同在移動端是一樣的钓试,先考慮設置 Theme装黑,再到 Scaffold,接下來看一下如何編寫代碼:

// positionIndicator 在 Content 之外弓熏,因此要將 state 提升到 Scaffold 之上的級別
val scalingLazyListState: ScalingLazyListState = rememberScalingLazyListState()
 
MaterialTheme {
    Scaffold(
        modifier = Modifier.fillMaxSize(),
        timeText = {...},
        vignette = { 
            Vignette (vignettePosition = 
                VignettePosition.TopAndBottom)
        },
        positionIndicator = {
            // 通過查看 state 來判斷是否處于滾動狀態(tài)恋谭,若否,則不會進行展示
            if (scalingLazyListState.isScrollInProgress) {
                // PositionIndicator 需要用到 state挽鞠,這也是我們從 LazyColumn 提升狀態(tài)的主要原因
                PositionIndicator(scalingLazyListState =
                    scalingLazyListState)
            }
        }
    ) {
        // 設置 content
        …
    }
}

△ Scaffold 示例代碼

上述代碼中疚颊,由于 positionIndicator 位于 content 之外狈孔,因此要將 state 提升到 Scaffold 之上的級別,來避免它在屏幕中被截斷材义。而在滾動時均抽,可以通過檢查滾動狀態(tài),通過隱藏時間顯示來為屏幕留出更多的空間其掂,還可以根據狀態(tài)來關閉或打開 vignette 效果油挥。positionIndicator 支持多種滾動選項,本例中我們使用了 scalingLazyListState款熬,還可以使用很多效果炫酷的其他選項深寥,具體請參考相關文檔。而關于 modifier 和 TimeText 想必不用過多介紹了贤牛,而 vignette 的設置其實也很簡單惋鹅。

Navigation

在本文一開始就提到您需要使用 Wear Navigation 依賴項來替換 Navigation,這里再次強調一下盔夜,從技術層面來說您仍可使用 Navigation负饲,但是可能會遇到各種問題,所以還是建議您直接使用已針對 Wear 優(yōu)化的 Wear Navigation喂链。

△ Navigation 設計

關于 Navigation 的設計返十,同 Scaffold 大致相同,采用了和移動版相同的設計椭微,只是在 Scaffold 之下和 Content 之上增加了 SwipeDismissableNavHost洞坑,顧名思義該組件支持滑出操作,您可以直接使用與移動應用開發(fā)相同的知識來編寫代碼蝇率。

MaterialTheme {
    Scaffold(...){
        val navController = rememberSwipeDismissableNavController()
 
        SwipeDismissableNavHost(
            navController = navController,
            startDestination = Screen.MainScreen.route
        ) {
            composable(route = Screen.MainScreen.route){
                MyListScreen(...)
            }
            composable(route = Screen.DetailsScreen. route + "/{$ID}", ...) {
                MyDetailScreen(...)
            }
        }
    }
}

△ Navigation 示例代碼

在上述代碼中迟杂,MaterialTheme 和 Scaffold 與之前一樣,但我們創(chuàng)建了一個 navController本慕,并使用了 SwipeDismissable 版本的 rememberSwipeDismissableNavController排拷,名稱非常拗口,但是很容易理解它的功能锅尘。然后使用了 SwipeDismissableNavHost 將 startDestination 及其路徑傳遞到控制器中监氢,再設置主屏幕內容即可。您會發(fā)現代碼基本上同移動端相同藤违,非常便于理解浪腐。

總結

在 Wear OS 中,請確保使用合適的依賴項顿乒,替換 Material 并添加 Foundation 依賴议街,如果使用的是 Navigation 同樣也要進行替換。另外璧榄,所有 Compose 構建方面的知識都可以直接應用于 Wear Compose 中特漩,用移動端的開發(fā)經驗助您快速構建精美的 Wear 界面吧雹。

如需了解更多詳細信息,請參閱:

歡迎您 點擊這里 向我們提交反饋拾稳,或分享您喜歡的內容吮炕、發(fā)現的問題。您的反饋對我們非常重要访得,感謝您的支持龙亲!

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市悍抑,隨后出現的幾起案子鳄炉,更是在濱河造成了極大的恐慌,老刑警劉巖搜骡,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拂盯,死亡現場離奇詭異,居然都是意外死亡记靡,警方通過查閱死者的電腦和手機谈竿,發(fā)現死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來摸吠,“玉大人空凸,你說我怎么就攤上這事〈缌。” “怎么了呀洲?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長啼止。 經常有香客問我道逗,道長,這世上最難降的妖魔是什么献烦? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任滓窍,我火速辦了婚禮,結果婚禮上巩那,老公的妹妹穿的比我還像新娘吏夯。我一直安慰自己,他們只是感情好拢操,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布锦亦。 她就那樣靜靜地躺著舶替,像睡著了一般令境。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上顾瞪,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天舔庶,我揣著相機與錄音抛蚁,去河邊找鬼。 笑死惕橙,一個胖子當著我的面吹牛瞧甩,可吹牛的內容都是我干的。 我是一名探鬼主播弥鹦,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼肚逸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了彬坏?” 一聲冷哼從身側響起朦促,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎栓始,沒想到半個月后务冕,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡幻赚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年禀忆,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片落恼。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡箩退,死狀恐怖,靈堂內的尸體忽然破棺而出领跛,到底是詐尸還是另有隱情乏德,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布吠昭,位于F島的核電站喊括,受9級特大地震影響,放射性物質發(fā)生泄漏矢棚。R本人自食惡果不足惜郑什,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蒲肋。 院中可真熱鬧蘑拯,春花似錦、人聲如沸兜粘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽孔轴。三九已至剃法,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間路鹰,已是汗流浹背贷洲。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工收厨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人优构。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓诵叁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親钦椭。 傳聞我的和親對象是個殘疾皇子拧额,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內容