2020 年 6 月 22 日,蘋果召開了第一次線上的開發(fā)者大會 - WWDC20。這次發(fā)布會上宣布了ARM架構(gòu)Mac芯片(
拳打Intel)、iOS 14 ATT(腳踢Facebook),可謂是一次載入史冊(我是爸爸)的發(fā)布會了,當(dāng)然還發(fā)布了被稱為下一個頂級流量入口的Widget。踩著八月的尾巴,本次我們就來探究一下Widget。
本文會從
Widget初窺和Widget開發(fā)兩個維度和章節(jié)來探究一下Widget,
其中初窺章節(jié)會帶您簡單的了解一下Widget,適合應(yīng)用決策者閱讀;
開發(fā)章節(jié)會帶著您一步一步的完成設(shè)計開發(fā)Widget,適合程序員閱讀。
Widget初窺
一、Widget是什么
In iOS 14, we have a dramatic new Home screen experience, one that is much more dynamic and personalized, with a focus on widgets.
The content is the focus.
This is very important:widgets are not mini-apps.
Think of this as more projecting content from your app onto the Home screen rather than full mini-apps filled with tiny little buttons.
一句話來說:Widget不是迷你應(yīng)用程序。而是一種新的主屏幕體驗,能快速提供用戶關(guān)心的內(nèi)容是重點

二、Widget的特點
-
Glanceable一目了然 -
Relevant早韭晚菘 -
Personalized量體裁衣

要設(shè)計一個優(yōu)秀的Widget,就要先了解Widget的全部特點,了然于胸
針對Apple提出的Glanceable、Relevant、Personalized分別用一個成語來形容就是一目了然、早韭晚菘、量體裁衣
簡單來說下這幾個特點
1、Glanceable | 一目了然,一覽無余

一個優(yōu)秀的Widget要一目了然,一覽無余。
普通人每天進入“主屏幕”的次數(shù)超過90次,但是在主屏幕僅停留幾分鐘,就切換到其他App了。
所以Widget一定要充分利用狹小的屏幕展示最核心的信息,并且要簡潔明了。設(shè)計新穎,便于快速瀏覽,高效是一個優(yōu)秀Widget的核心。
用戶不用思考這個Widget怎么使用,不需要點擊任何按鈕就可以獲得最關(guān)心的信息。
2、Relevant | 早韭晚菘

蘋果希望Widget可以和用戶緊密結(jié)合,與用戶的行為所關(guān)聯(lián),比如早上起床,用戶希望看一下天氣;中午恰飯,用戶希望有人推薦下附近的美食;晚高峰的時候,用戶希望了解一下行車路線;晚安的時候,希望記錄下次日的行程。
為此,蘋果系統(tǒng)提供了一個叫Smart Stacks(智能疊放)的功能,Smart Stacks是一個Widgets的集合。系統(tǒng)會根據(jù)每個人的習(xí)慣,自動顯示用戶當(dāng)前時間點最需要的Widget。
<div style="align:center"><img src="https://guojunliu.github.io/images/widget/3.gif"/></div>
3、Personalized 量體裁衣
3.1 大小
Widget要能為用戶提供個性化的服務(wù),比如天氣Widget,需要能為不同的用戶提供不同細節(jié)的天氣情況。

為此Apple提供三種不同大小的小部件
systemSmallsystemMediumsystemLarge
其中systemSmall大小為2*2 Icon,systemMedium大小為4*2 Icon,systemLarge大小為4*4 Icon,具體的顯示效果如下

3.2 個性化配置
另一方面Widget需要能為不同城市的用戶提供當(dāng)?shù)氐奶鞖馇闆r。
為此Apple在創(chuàng)建Widget時為開發(fā)者提供了兩種類型:
StaticConfiguration:對于沒有用戶可配置屬性的窗口小部件,也就是用戶無需配置,展示的內(nèi)容只和用戶信息有關(guān)系。例如,顯示一般市場信息的股市窗口小部件,或顯示趨勢頭條的新聞窗口小部件。IntentConfiguration:對于具有用戶可配置屬性的窗口小部件,也就是支持用戶配置及用戶意圖的推測。您使用SiriKit自定義意圖來定義屬性。例如,需要一個城市的郵政編碼的天氣小部件,或者需要一個跟蹤號的包裹跟蹤小部件。

需要說明的是,IntentConfiguration并不需要編寫代碼,只需要簡單的配置,Xcode 會自動幫你生成對應(yīng)的代碼和類型。
3.3 黑暗模式
此外Widget還支持系統(tǒng)的黑暗模式

三、Widget的本質(zhì)
Widget的本質(zhì)是一系列靜態(tài)視圖堆疊而成的集合,不同的時間點展示不同的視圖
這里要引入Widget的核心Timeline
顧名思義,Timeline就是一條時間線,在對應(yīng)的時間點發(fā)生對應(yīng)的事件
許多Widget具有可預(yù)測的時間點,在這些時間點更新其內(nèi)容是有意義的。例如,顯示天氣信息的小部件可能會在一整天內(nèi)每小時更新一次溫度。股市窗口小部件可以在公開市場時間頻繁更新其內(nèi)容,但周末則不用完全更新。通過提前計劃這些時間,生成不同的視圖放入時間線中,WidgetKit會在適當(dāng)?shù)臅r間到來時自動刷新您的窗口小部件。
這也決定了Widget基本上不能實時更新
另外值得一提的是,WidgetKit會把 Timelines 所定義的Views 結(jié)構(gòu)信息緩存到磁盤,然后在刷新的時候才通過 JIT 的方式來渲染。這使得系統(tǒng)可以在極低電量開銷下為眾多 Widgets 處理 Timelines 信息。

四、用戶交互
不好意思,沒有交互!??!
為了實現(xiàn)以上的特點,Apple也移除限制了Widget的一些功能
- 不能交互
- 不能播放動畫
- 不能播放視頻
- 不支持滾動
- 不支持主動刷新視圖
唯一支持的只有用戶點擊Widget喚起主App

其中點擊喚起主App有兩種方案,分別是:
- widgetURL
- Link
widgetURL喚起App的點擊區(qū)域是Widget的所有區(qū)域,這種方案適合簡單元素,單一邏輯的小部件

對于systemSmall類型的小部件,只支持widgetURL喚起方式
針對systemMedium和systemLarge還可以使用更細分的Link喚起方式,這種喚起方式能讓小部件通過不同元素的點擊喚起App的不同頁面,讓開發(fā)者有更多的施展空間

舉個簡單的例子,widgetURL可應(yīng)用于天氣小部件,博客小部件,點擊直達App;
Link可用于備忘錄和日歷小部件,點擊不同的備忘錄和日期直接跳轉(zhuǎn)到對應(yīng)的備忘錄詳情和待辦詳情頁面

Widget初窺總結(jié)
Widget的出現(xiàn)猶如在一潭死水的iOS桌面上泛起了一片漣漪,一定會有很多App來爭奪這塊肥肉一般的流量入口。
但是仔細研究一下會發(fā)現(xiàn),Apple這次推出的Widget非常克制,并沒有非常激進,
俗話說:喜歡是放肆,但愛就是克制。這里不得不再次引用Apple在Widget介紹中出現(xiàn)頻率最高的話widgets are not mini-apps,因為Widget在設(shè)計之初就是為了能使用最少的成本,向用戶提供最核心的信息。為了盡可能的減少用戶成本(電量,網(wǎng)絡(luò)等)和提高用戶體驗,Apple在技術(shù)層面上做了很多限制,限制了非常多的功能,大大削弱了Widget的地位和重要程度,也降低了開發(fā)者實現(xiàn)的熱情和積極性
其實每年Apple更新的新技術(shù)只有很少的一部分能應(yīng)用到App上,希望這次的Widget能有動力讓大家結(jié)合自己的App,給自己的App帶來更多的流量,也能給用戶帶來更好的體驗。
Widget開發(fā)
重頭戲來啦,接下來讓我們一步一步設(shè)計編寫出優(yōu)秀的小部件吧
開始之前,首先我們要介紹下Widget的開發(fā)語言,Apple特別指定了小部件只能使用SwiftUI來開發(fā)
一、SwiftUI

現(xiàn)在iOS主流的開發(fā)語言還是Objective-C,那Apple為什么要選擇2019 WWDC發(fā)布迄今為止只有一年的SwiftUI呢?
首先,從一開始就將小部件實現(xiàn)多平臺化是Apple的一個目標(biāo),SwiftUI在跨設(shè)備展示的能力上是一把大殺器;
其次SwiftUI還使自動布局和暗模式等功能變得非常容易,降低了適配等開發(fā)成本,對不需要太多元素的小部件來說,SwiftUI重點關(guān)注布局的特點無疑是最合適的;
從另一方面來講,只有使用 SwiftUI 才能達到我們上邊說的對于 Widget 的限制。如果可以使用 Objective-C UIKit 的話,我們強大的開發(fā)者可能會想出無數(shù)的黑科技來忽略Apple真的小部件的限制。比如開發(fā)無法使用 UIViewRepresentable 來橋接 UIKit;
最后Apple也夾帶了自己的私心,Apple今年已經(jīng)將 Swift 語言和 SwiftUI 的重要程度提升到了一個新的高度,Swift已經(jīng)可以獨立于Foundtion框架,那么對應(yīng)的SwiftUI也應(yīng)該不依賴于UIKit框架了,強行使用SwiftUI可以使開發(fā)人員盡可能容易地將其學(xué)習(xí)其內(nèi)容并應(yīng)用于iOS,iPadOS和macOS,
畢竟5月份卡位第20位Objective-C在6月份已經(jīng)跌出了前20

這里要重點說明一下,Widget只要使用任何 UIKit 的元素就會直接 Crash
二、將小部件目標(biāo)添加到您的應(yīng)用
窗口小部件擴展模板提供了創(chuàng)建窗口小部件的起點。單個小部件擴展可以包含多種小部件。例如,一個體育應(yīng)用程序可能有一個顯示團隊信息的小部件,另一個顯示游戲時間表的小部件。一個小部件擴展可以包含兩個小部件。盡管建議將所有窗口小部件包含在一個窗口小部件擴展中,但如有必要,可以添加多個擴展。
在Xcode中打開您的應(yīng)用程序項目,然后選擇“文件”>“新建”>“目標(biāo)”。
從“應(yīng)用程序擴展”組中,選擇“窗口小部件擴展”,然后單擊“下一步”。
輸入您的包名。
如果窗口小部件提供了用戶可配置的屬性,請選中
Include Configuration Intent復(fù)選框。
單擊完成。

重點:小部件不僅支持Swift項目,同樣也支持Objective-C項目,OC小伙伴不用擔(dān)心啦
創(chuàng)建完小部件之后,我們會多出一個SmileEverydayWidget.swift文件,這已經(jīng)是一個可以run起來的小部件了,因為我們接下來要逐個方法來分析,所以先將文件全文展示如下
//
// SmileEverydayWidget.swift
// SmileEverydayWidget
//
// Created by steve on 2020/8/28.
//
import WidgetKit
import SwiftUI
struct Provider: TimelineProvider {
public typealias Entry = SimpleEntry
public func snapshot(with context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date())
completion(entry)
}
public func timeline(with context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
public let date: Date
}
struct PlaceholderView : View {
var body: some View {
Text("Placeholder View")
}
}
struct SmileEverydayWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
Text(entry.date, style: .time)
}
}
@main
struct SmileEverydayWidget: Widget {
private let kind: String = "SmileEverydayWidget"
public var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider(), placeholder: PlaceholderView()) { entry in
SmileEverydayWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
struct SmileEverydayWidget_Previews: PreviewProvider {
static var previews: some View {
/*@START_MENU_TOKEN@*/Text("Hello, World!")/*@END_MENU_TOKEN@*/
}
}
這里的概念和代碼比較多,接下來我們一個一個來解釋
三、Widget API
首先我們從帶有main字段的方法來說起,
@main
struct SmileEverydayWidget: Widget {
private let kind: String = "com.steve.liu.smileEverydayWidget"
public var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider(), placeholder: PlaceholderView()) { entry in
SmileEverydayWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
}
大家都知道帶有main標(biāo)識的方法都是程序的入口
這段代碼使用SwiftUI聲明了一個名為SmileEverydayWidget的小部件,其中StaticConfiguration是小部件的初始化方法,它有幾個參數(shù):
kindproviderplaceholder

其中
kind是標(biāo)識小部件的字符串,并且應(yīng)描述小部件所代表的內(nèi)容。即小部件的包名
provider為時間線提供者
PlaceholderView為占位視圖
同時也提供了一些方法,例如
-
configurationDisplayName()設(shè)置小部件顯示的名稱 -
description()設(shè)置小部件的描述 -
supportedFamilies()設(shè)置小部件支持的尺寸
這里有一個重點,為了使某個應(yīng)用程序的窗口小部件出現(xiàn)在窗口小部件庫中,用戶必須在安裝該應(yīng)用程序后至少啟動一次包含該窗口小部件的應(yīng)用程序。
四、WidgetEntryView
WidgetEntryView就是使用SwiftUI布局的小部件視圖
struct SmileEverydayWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
Text(entry.date, style: .time)
}
}
例如這個小部件視圖就簡單的展示了當(dāng)前的時間
接下來我們可以將默認的布局更改為我們自己想要的布局,例如我在設(shè)置了顯示文本的字體和小部件的背景圖
struct SmileEverydaWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
return Text(entry.message)
.background(Image(entry.backgroundImageStr))
.font(.callout)
}
}
五、PlaceholderView
每種小部件都需要提供占位符UI。
占位符UI是窗口小部件的默認內(nèi)容。
它應(yīng)該代表您的小部件類型,但僅此而已。
此用戶界面中不應(yīng)有任何用戶數(shù)據(jù)。

想象一下
如果在用戶的主屏幕上出現(xiàn)如下的場景,那么你的小部件離被移除可能已經(jīng)不遠了

六、TimelineEntry
我們知道小部件是按照時間線來展示的,TimelineEntry時間線上的一個個條目
struct SimpleEntry: TimelineEntry {
public let date: Date
}
TimelineEntry有一個必須有的屬性就是date,也就是這個條目在時間線上的具體時間
另外開發(fā)者可以在TimelineEntry里自定義各種屬性,用來給小部件視圖提供數(shù)據(jù)
例如我在TimelineEntry里自定義了message和backgroundImageStr屬性,用來顯示小部件上的文字和背景圖片
struct SimpleEntry: TimelineEntry {
public let date: Date
public let message: String
public let backgroundImageStr : String
}
七、TimelineProvider
TimelineProvider 是一個提供了上述我們所說的TimelineEntry集合的對象

我們來看下具體的代碼:
struct Provider: TimelineProvider {
public typealias Entry = SimpleEntry
public func snapshot(with context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date())
completion(entry)
}
public func timeline(with context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
其中包含兩個方法
- snapshot()
- timeline()
1、snapshot 快照
為了在小部件庫中顯示小部件,WidgetKit 要求提供者提供預(yù)覽快照, 即snapshot(),這個方法里主要提供了一些示例數(shù)據(jù),最好是真實數(shù)據(jù),用于時間線不能展示的時候展示給用戶
需要說明的是,快照是系統(tǒng)需要快速顯示單個條目的位置。
因此,您的擴展程序必須盡快返回視圖,因為這樣做時,用戶會在iOS上漂亮的Widget Gallery中看到真正的Widget。
這不是我們在設(shè)計時必須提供的屏幕截圖或圖像。這是用戶在iOS,iPadOS和macOS上真正的小部件體驗。
在大多數(shù)情況下,時間軸的第一個條目和快照可以作為同一條目返回,因此,在“小工具庫”中看到的就是用戶將其添加到設(shè)備中時得到的內(nèi)容。

例如我們在Widget Gallery中添加電池小部件時,小部件此時在Widget Gallery中展示的就是當(dāng)前設(shè)備電池信息的實時數(shù)據(jù)的快照,而不是一些虛假的數(shù)據(jù),這個時候小部件的數(shù)據(jù)是什么樣子,用戶添加到主屏幕上之后小部件的數(shù)據(jù)就是什么樣子,從而提高用戶的體驗。
對比
小部件里有兩個比較類似的概念,PlaceholderView和snapshot,都是一種占位解決方案,不同的是PlaceholderView是在主屏幕上無法快速獲取數(shù)據(jù)時的一種占位視圖,不至于顯示loading或者白屏給用戶看;而snapshot主要用于Widget Gallery中,用來提高用戶體驗的,一般來說,snapshot就是時間線的第一幀
2、timeline
在請求初始快照后,WidgetKit調(diào)用timeline以請求提供者的常規(guī)時間軸。時間軸由一個或多個時間軸條目TimelineEntry以及一個重載策略ReloadPolicy組成,該重載策略通知WidgetKit何時請求后續(xù)時間軸。
關(guān)于重載策略,提供了以下幾種策略
-
atEnd: 是指 Timeline 執(zhí)行到最后一個時間片的時候再刷新。 -
atAfter: 是指在某個時間以后有規(guī)律的刷新 -
never:是指以后不需要刷新了。什么時候需要重新刷新需要 App 重新告知 Widget
根據(jù)上邊的分析,我們可以將TimelineProvider改造如下
struct Provider: TimelineProvider {
public typealias Entry = SimpleEntry
public func snapshot(with context: Context, completion: @escaping (SimpleEntry) -> ()) {
let date = Date()
let message = "蒹葭蒼蒼,白露為霜。所謂伊人,在水一方。"
let backgroundImageStr = "bg7"
let entry = SimpleEntry(date: date, message: message, backgroundImageStr: backgroundImageStr)
completion(entry)
}
public func timeline(with context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
var currentDate = Date()
var nextUpdateDate = Calendar.current.date(byAdding: .second, value: 3, to: currentDate)!
let message = "蒹葭蒼蒼,白露為霜。所謂伊人,在水一方。\n溯洄從之,道阻且長;溯游從之,宛在水中央。\n蒹葭凄凄,白露未晞。所謂伊人,在水之湄。"
let backgroundImageStr = "bg"
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
var currentDateStr = ""
var nextUpdateDateStr = ""
let longUuid = UUID().uuidString
let range: Range = longUuid.range(of: "-")!
let location: Int = longUuid.distance(from: longUuid.startIndex, to: range.lowerBound)
let uuid = longUuid.prefix(location)
for i in 1 ..< 10 {
var msg = ""
currentDate = nextUpdateDate;
nextUpdateDate = Calendar.current.date(byAdding: .second, value: 3, to: currentDate)!
currentDateStr = formatter.string(from: currentDate)
nextUpdateDateStr = formatter.string(from: nextUpdateDate)
msg.append(message)
msg.append("\n時間軸ID " + uuid);
msg.append("\n時間軸第" + String(i+1) + "個視圖")
msg.append("\n本次視圖開始時間 " + currentDateStr)
msg.append("\n下次視圖開始時間 " + nextUpdateDateStr)
let entry = SimpleEntry(date: currentDate, message: msg, backgroundImageStr: backgroundImageStr+String(i))
entries.append(entry)
print(String(i))
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
這段代碼里我們提供了一個古詩詞的快照
并生成了一個時間線,其中時間線里包含了10個entry,且每個entry 間隔10s
entry文本拼接顯示了
- 展示文本(詩經(jīng))
- 時間軸ID
- 本次視圖開始時間
- 下次視圖開始時間
下邊我們來看一下真實run出來的效果

到此為止我們就有了一個可以在主屏幕上展示的小部件了
并且能根據(jù)時間線展示不同的視圖了。
七、Reload Timeline 使小部件保持最新
為了使我們的小部件能隨時提供最新的,而不是過期的信息,我們需要不時的對小部件進行更新。
我們已經(jīng)知道了小部件的本質(zhì)是一系列的視圖堆疊,那么更新小部件就是更新這些視圖。

比如一個有三個視圖的小部件,預(yù)測了現(xiàn)在和未來3小時的天氣預(yù)報,這個小部件顯示步驟如下:

但是有一個很重要的問題就是時間線是我們預(yù)測出來的,是預(yù)測就會有偏差。比如天氣預(yù)報,預(yù)報2小時后有雨,但是隨著天氣的變化,2小時后變成晴天了,這個時候我們?nèi)绻桓滦〔考系臅r間線,就會在2小時后給用戶提供錯誤的信息。
為此我們需要在有信息變化的時候重新顯示新的視圖,如下圖:

為了是我們的小部件信息準(zhǔn)確無誤,首先我們需要了解下小部件是如何刷新的
很不幸,Widget 的刷新完全由 WidgetCenter控制。開發(fā)者無法通過任何 API 去主動刷新 Widget 的頁面,只能告知 WidgetCenter,Timeline 需要刷新了
所以我們不能直接刷新小部件的視圖,而是要通過生成一個新的時間線來替換舊的時間線,Reload Timeline 并不是直接刷新 Widget,而是 WidgetCenter 重新向 Widget 請求下一階段的數(shù)據(jù)。
其中Reload Timeline分為兩種方式
- System Reloads
- App Reloads
1、System Reloads
這個行為由系統(tǒng)主動發(fā)起,會調(diào)用一次 Reload Timeline 向 Widget 請求下一階段刷新的數(shù)據(jù)。系統(tǒng)除了會按時發(fā)起 System Reloads 之外,還會動態(tài)決策每個不同的 TimeLine 的 System Reloads 的頻次。比如被點擊次數(shù)很大程度上直接決定了 System Reloads 的頻率,點擊率越高,更新頻次越快,當(dāng)然還有一些由于設(shè)備環(huán)境變化觸發(fā)的行為也會觸發(fā) System Reloads,比如設(shè)備時間進行了變更。

很顯然這種方案不能很好的解決我們上邊的問題
2、App Reloads
這種行為指的是App主動通知小部件,你需要更新信息了。這里邊根據(jù)App的當(dāng)前的前后臺狀態(tài)又分為兩種方式
- 應(yīng)用在前臺運行
- 應(yīng)用在后臺運行
當(dāng)應(yīng)用在前臺運行的時候,App 可以直接使用WidgetCenter的 API 來 Reload Timeline;而當(dāng)應(yīng)用處于后臺時,可以使用后臺推送(Background Notification)來 Reload Timeline。

除了這些,給Timeline設(shè)定合適的刷新策略也是很重要的手段
合理的組合使用這些刷新機制,能夠極大的提高Widget信息的準(zhǔn)確性

八、交互
前邊我們說過,widget和app交互有兩種方式SwiftUI widgetURL API和SwiftUI Link API
這兩種方式的本質(zhì)都是URL Schemes,只要監(jiān)聽SceneDelegate的scene:openURLContexts:就可以了
由于Schemes大家都太熟悉了,關(guān)于如何高效快速準(zhǔn)確的傳遞參數(shù),這里就不展開講了。

九、設(shè)計漂亮的小部件
如果你已經(jīng)看到了這里,并且已經(jīng)理解了上述的講解,你已經(jīng)具備了開發(fā)小部件的能力。
那么有哪些關(guān)鍵點能給自己的小部件錦上添花呢?
去除額外的App信息:系統(tǒng)會在小部件下方自動顯示你的應(yīng)用名稱,因此你無需在內(nèi)容中重復(fù)App的名稱,Icon,而是要通過顏色,布局和圖像來聯(lián)系您的App
簡潔的描述。小部件庫中顯示的描述可以幫助人們理解每個小部件的功能。從動作動詞開始描述通常效果很好;例如,“查看當(dāng)前天氣狀況和位置預(yù)測”或“跟蹤即將舉行的活動和會議”。避免包含不必要的短語來引用窗口小部件本身,例如“此窗口小部件顯示...”,“使用此窗口小部件...”或“添加此窗口小部件”。
舒適的信息密度:一覽無余。當(dāng)內(nèi)容顯得稀疏時,小部件可能看起來是多余的;當(dāng)內(nèi)容太密集時,小部件將無法瀏覽。如果要包含很多信息,請避免讓小部件成為難以解析的項的拼貼。尋求整理內(nèi)容的方法,以便人們可以立即掌握關(guān)鍵部分,并以更長的時間查看相關(guān)細節(jié)。您可能還考慮創(chuàng)建一個較大的小部件,并尋找可以用圖形替換文本而又不會失去清晰度的位置。
明智地使用顏色:豐富,美麗的色彩吸引眼球,但它們絕不能阻止人們一眼就吸收小部件的信息。使用顏色可以增強小部件的外觀,而不會與小部件的內(nèi)容競爭。
使用系統(tǒng)字體,支持系統(tǒng)功能:例如 支持黑暗模式;使用SF Pro和使用系統(tǒng)字體;文本可縮放。

設(shè)計一個真實的預(yù)覽以顯示在小部件庫中:突出顯示小部件的外觀和功能可幫助人們做出明智的決定,并鼓勵他們添加小部件。您可以在小部件預(yù)覽中顯示真實數(shù)據(jù),但是如果數(shù)據(jù)生成或加載所需的時間太長,請顯示真實的模擬數(shù)據(jù)。
設(shè)計占位符內(nèi)容,以幫助人們識別您的小部件。小部件在加載數(shù)據(jù)時顯示占位符內(nèi)容。通過將UI的靜態(tài)部分與代表實際內(nèi)容的半透明形狀結(jié)合起來,可以創(chuàng)建有效的預(yù)覽。例如,您可以使用不同寬度的矩形來建議文本行,并使用圓環(huán)或正方形代替字形和圖像。

圖片適配屏幕尺寸:確保圖片在大部件和小部件下都不會壓縮

十圍之木,始生如蘗
簡單的總結(jié)一下
一個優(yōu)秀的小部件是完全可以提高用戶體驗,成為很好的流量入口,給App帶來巨大的商業(yè)價值。
但是要設(shè)計一個優(yōu)秀的小部件也并非易事。
本文拋磚引玉,希望大家能設(shè)計出更多優(yōu)秀的小部件。
本次的Widget指北到這里就結(jié)束了,萬字不易,多多傳播。
喜歡我你就關(guān)注我,
有話說你就評論我,
都不干你就點個贊

Demo
參考