用 Slice 来做 Android Live Wallpaper 的快捷设置项

 • 

好久没写技术实现了,水一篇好了。
Slice 是 Google 做的一套模版化 UI,主要用于 Assistant 相关的界面展示,但系统设置顶部偶尔也会出现相关的应用。B-Reel 和 Google 在 Pixel 4 自带的 Pixel Live Wallpaper 里用 Slice 来做了一组快捷设定项,效果如下:
PixelLWP
作为专业 Pixel Live Wallpaper 高仿者(误,做新软件 Diorama 的时候我也试了一把 Slice,效果还是可以的:
Diroama
至于原理其实很简单,在 API 29 上,wallpaper.xml 可以添加一个新字段 android:settingsSliceUri,如果提供的 Uri 是正确的,动态壁纸 Preview 的底部 panel 会新增一个 customize 的 tab。Diorama 目前的 wallpaper.xml 如下:

<?xml version="1.0" encoding="utf-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
    android:settingsSliceUri="content://com.justzht.lwp.diorama/settings"
    android:showMetadataInPreview="true"
    android:description="@string/app_wallpaper_description"
    android:author="@string/app_author"
    android:contextUri="@string/app_wallpaper_context_uri"
    android:contextDescription="@string/app_wallpaper_context_description"
    android:label="@string/app_name"
    android:settingsActivity="com.justzht.lwp.diorama.view.activity.MainActivity"
    android:thumbnail="@mipmap/wallpaper_preview"/>

Slice 的实现也很简单,首先 Android Studio 的新建菜单里 New/Other/Slice Provider 这个路径下就已经提供了一个基础的模板,只需要自己拓展 public Slice onBindSlice(Uri sliceUri) 方法返回一个自定义的 Slice 即可。

ListBuilder builder = new ListBuilder(context, sliceUri, ListBuilder.INFINITY);
if ("/settings".equals(sliceUri.getPath())) {
    // add your own implementation here
    return builder.build();
} else {
    return builder.addRow(
        new RowBuilder()
        .setTitle("ERROR: URI " + sliceUri.getPath() + " not found.")
    ).build();
}

Slice 的 Template 里有一些可以交互的 UI 元素,比如 Toggle 或者 Slider。这些交互的触发事件可以通过设定各自元素 Builder 里的 Action 来实现,比如 ListBuilder.InputRangeBuilder 就可以调用 setInputAction(PendingIntent action) 来触发滑动事件,以及 setPrimaryAction(SliceAction action) 来触发整个 row 的点击事件。若是要做一个时间拖动的 Slider:

// init your ListBuilder before this code snippet
int hour = (int) Math.floor(24 * Utils.getVal(SettingsManager.getInstance().getModel().dateTimeManualModeProgress, 0 f)); // Get current set hour from some where
Intent intent = new Intent(context, SliceBroadcastReceiver.class).setAction(context.getString(R.string.intent_change_datetime));
ListBuilder.InputRangeBuilder inputRangeBuilder = new ListBuilder.InputRangeBuilder()
    .setTitle(context.getString(R.string.text_time))
    .setSubtitle("Custom at " + String.format("%02d:00", hour))
    .setInputAction(PendingIntent.getBroadcast(
        getContext(),
        0,
        intent,
        PendingIntent.FLAG_UPDATE_CURRENT
    )) // add a PendingIntent so a BroadcastReceiver can receive the event
    .setValue(hour) // note that value is an int, meaning this slider is a *stepped* seekbar
    .setMax(24)
    .setMin(0);
builder.addInputRange(inputRangeBuilder);
// return builder.build() after this code snippet

然后在 BroadcastReceiver 里就可以获得 Slider 更新的值:

public class SliceBroadcastReceiver extends BroadcastReceiver {
    private static Uri sliceUri = Uri.parse("content://com.justzht.lwp.diorama/settings");
    @Override
    public void onReceive(Context context, Intent intent) {
        String intentAction = intent.getAction();
        if (intentAction != null) {
            if (intentAction.equals(context.getString(R.string.intent_change_datetime)) &&
                intent.getExtras() != null &&
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                int hour = intent.getExtras().getInt(EXTRA_RANGE_VALUE, 0); // acquire the updated value using EXTRA_RANGE_VALUE
                Utils.setValIfNotSame(SettingsManager.getInstance().getModel().dateTimeManualModeProgress, hour / 24 f, false); // save this value to somewhere

                context.getContentResolver().notifyChange(sliceUri, null); // notify the app to refresh slice
            }
        }
    }
}

最后记得调用 void notifyChange(Uri uri, ContentObserver observer) 刷新 Slice 即可。

2020.10.1

 • 

手头两个项目都还没做完,自己又多了几个新东西的想法,只感觉自己步调越来越不一致了。
我对项目,或者说,产品定位,一直没什么特别的想法。很多东西其实只是觉得好玩或者自己用,然而在做的过程就会考虑独立开发的盈利问题,就比如说如果不是为了可能的盈利,我其实是很不想在 Google Play 上做免费+内购软件的,原因倒也不是内购造成的用户差评或是免费造成的服务器负荷,而就只是我不想专门把时间拿来写 IAP 支付的逻辑上,对于我来说这块代码完全是没有乐趣可言的。然而总没有那么完美的事情,只能在让自己爽和让用户满意之间踮起脚跳来跳去。
昨天下午起床后就一直在写 Diorama 的 Firebase Auth + Database 逻辑,我觉得可能之前几个地理位置的软件没有增长的原因是缺少用户 UGC,毕竟里面内置的 Presents 或 Collection 都是我自己维护的,而我又是一个极度喜新厌旧的人(哪个 Android 用户又不是呢),因此自己的项目过半年就会直接放弃维护转而去研究新玩意,这一来用户也自然没有增长。但要做 UGC 成社区吧,我又觉得一个简单的小软件没必要大张旗鼓,社区规则弄不好就可能全是 SPAM 和 Bot,数据库每个月扣我的钱都会比卖软件的收入多。
写到晚上还有好多逻辑没完成,再一看调试就已经用掉了单日五万次 API 里面的两千次,只觉得做 UGC 好难啊,我还是挺发怵的,就还是先把 Diorama 做成一个没太多用户间互动的纯单机软件吧,把自己更感兴趣的动态天气和交通模拟功能给写了就准备上架了,我做 Diorama 的起源也只是觉得能随时看到一个微观世界挺开心的,Diorama 在特定时间展示出来的效果也的确挺好看,虽然我还不确定用户会不会喜欢罢了。

Banner

2020.9.23

 • 

前几天去青岛玩了,十月份会再过去常住一段时间。
独立开发者群里貌似因为 iOS 14 的缘由好多 Widgets 软件都被首页推荐了。看大家很开心的样子,我也开始想着要去更新下自己的 GitHub Contributions 了,不过貌似 WidgetsKit 还不能直接套 SceneKit,只能主 app 渲染成图再传回去,还需要时间去研究下。此外最近在研究 Metal,可能回头会做 Diffuse for Mac。
Google Play 的收入日渐下降,为了保证收入又在做几个新 LWP 了。Unity Asset Store 的 UniLWP 这个月也卖出去几份,虽然需要负责技术支持有点花时间。
感觉坑开的有点多,不过是件好事,至少有想法了。感觉多多跑动对自己也挺好的,因此还在计划月底再去北京一趟。
在路上听了 Session 042Groove Monday 08.31.20Find My Way Home。以及想起来在青岛的第一天早上收到邮件,是加申的硕士项目在约时间面试。接着晚上刚吃完饭就有视频邀请,问的问题也极尽尖锐,我于是花了一番功夫去解释为什么本科 GPA 那么差,可能说到最后我自己都有点难堪。挂掉 ZOOM 后躺在酒店的床上反而突然很放松,毕竟前段时间一直在准备 Coding 面一直没面成,反而阴差阳错在旅途中被问了那么多 Behavior Question,我也脑子缺根筋地说了很多看法和坏话,可能是我近期最坦诚相见的半小时了。

2020.9.15

 • 

Google 的招聘网站不知道出了什么问题,我点开之前投递的 21 Summer Intern 简历申请只会出现无法加载的提示,不知道是不是因为写了国内的地址所以被系统自动过滤掉了。摊手。
上周拿到了 Persona 5 Scramble 的卡带,因此花了两天打通关。P5S 的剧情算是接着 P5 来的,还是高中生的奇幻冒险故事。打完后产生了奇妙的想法,可谓逃避现实,也可以称之为想开了,即:我已经不像本科那时一样有追求真正幸福的勇气了,这两年反而在为一些现世的东西而牵挂,比如工作的薪资,简历上的头衔,还有一些他人的所谓成就,譬如谁谁拿了 Forbes 30,谁谁融了多少钱,包括前段时间一直在纠结 defer 的这段时间是在国内实习还是做独立游戏也是这类问题,抛开自己不知何时产生的世俗眼光,我当然更愿意去做完一直没做完的太空游戏,因为那才是能让我感到开心的事情,而最多只有三个月的实习,更多的是出于对自己长时间没在公司工作的焦虑而产生的想法,是我感到有义务要去做但并不真正喜欢的事情。然而回过头来看,这些我强加到自己身上的义务,更多的是大家认为的成功人士该做的事情罢了,我似乎也没有义务去为此而焦虑。
于是打通关 P5S 后,我就没在考虑找公司的事情,而是继续做独立开发了。一款游戏的剧情能让我自省,给我这么多信心,实在是近期买的最值的游戏了,特别是考虑到这剧情若是突兀地解释给他人,可能我半天也只能说出“高中生拯救世界”这种中二的剧情,和内省这种严肃的行为毫不搭边,但游戏给人的震动却是真实的。

2020091013031400-19E0B7B692434F091AC0D5181A6ED69D

2020.9.6

 • 

在家看了一会算法,原本是给 Google 的 Summer Intern 准备来着,但看目前这招聘形式估计希望不大,因此目标主要转变为月中试下国内几家的面试。虽然过了也不一定去,但好久没接触过公司了,面一面至少能让自己有点实感。
此外,最近基本就是看别人做什么自己也就做什么,看延期入学的人里面有人在国内先实习了,自己就想着要不要也找家,但最多就干三个月 HR 好不好答应不说,自己手上还有几个个人项目,也并不是很有要工作的紧迫性和觉悟。然后看到一直在追的某些开发组甚至个人工作室开发的游戏已经出 trailer 上 IGN 的 YouTube 频道了,又痛心疾首没有把时间花去做 Epoch 上。
听了 SoundOf: Sam Lowe X GuyMac,这首最近变成了跑步专用歌。还听了 So Far AwayIt's Probably MeSweet and Lovely。看了 TENET,买了票明天二刷。某个犯困的中午还看了半个小时的听见涛声,要记得这周找个时间看完。
好想去日本玩啊。

2020.8.20

 • 

东北 OOD 这门网课终于结了,大作业熬了一整夜其实还是没做完,只不过演示的效果还行。之后还是准备延期,毕竟在家上研究生对于我来说实在是太憋屈了,不如不上。
推掉了一些事情,准备安心刷题了。
听了 Never let you goLess Is More

2020.8.11

 • 

因为没在流媒体上线,所以去网站上买了份蛋堡的 家常音樂。这个网站也很好玩,设计和文案都很玩味,属于做的一板正经但又插科打诨的那种。至于专辑本身稍微有些千篇一律的感觉,但有新音乐听就足够好了。
网课快到期末了,大作业也宣布是个基于 Swing 的程序,之前没用过 Swing,对这个的印象也只有某天早上昏昏欲睡的时候看网课里老师在 NetBean 里操作一个类似于 Visual Studio MFC 一般的 Designer。
因为 21 Spring 申请的准备和网课,软件项目的进展几乎为 0,可能最近会尝试把 FastSpring 的支付宝渠道接入到 Diffuse 里。
听了 无病呻吟夏夜晚风OkinawaSwing(哎?

2020.8.2

 • 

“怎么看?指什么?”
“我该怎么办呢,往后?”
“我说什么都为时已晚吧。”我边喝冰凉冰凉的啤酒边说。
“可以的,尽管说,怎么想怎么说。”
“假如我是你,就和他各奔东西,找一个头脑更为地道的人去幸福地生活。无论怎么善意地看,和那个人相处都不能有幸福可言。自己幸福也罢,使别人幸福也罢,他并不把这个放在心上。和他在一起,神经非出问题不可。依我看,你和他交往3年之久已经是一种奇迹。诚然,我也不是不喜欢他,他这人风趣,长处很多,本事大,又坚强,我这样的角色根本望尘莫及。问题是,他考虑事物的方式和生活态度不够地道。同他交谈起来,时常觉得自己总在同一地方来回兜圈子。他以同一程序不断勇往直前,而自己却总是原地徘徊,并且空虚得很。一句话,就是人生观本身不同。我说的你明白吗?”

2020.7.28

 • 

东北的网课考期中了...惨不忍睹。平时的作业完成的还好所以加权起来暂时还能看,但期末要好好准备下了,不然研究生就和本科一样的 GPA 了。


暂且不知道 DTK 的 NDA 里是否有规定连 DTK 都不能提,但事实是我花了 3699 大洋从苹果租了台 DTK,全名叫 Universal App Quick Start Program,而且付完钱我感叹这世界上或许没有比苹果开发者更 shut up and take my money 的群体了。之前申请 DTK 一个月都没回我,后来换了个理由说我要用 SwiftUI 重写 Cetacea 啦,你们快批给我台我好重新编译到新架构上,结果一天后就批准了。掏钱之前还在犹豫有这四千块为什么不直接去弄台 AMD 的黑苹果,毕竟 taresky 的这篇 AMD YES 看得我心痒痒,而且 DTK 也只是租一年最后还要还回去。不过最后的最后,我还是在这里敲键盘,看着物流网页上我的 DTK 在从深圳发过来。
很多事情也像这样开始随性起来,我觉得是个好事,可能我想开了很多事情,比如我现在也不会给手机戴上壳了,可能是哪天下午起突然觉得买手机不就是为了裸机的手感,即使是超薄的 PTU 壳也究竟有什么意义呢,以防万一这种想法有什么意义呢,虚妄地期待坏事发生来证明存在的价值又有什么意义呢。
估计分手后重新审视事情就会这样,可能分手后女生会换个发型是一样的道理?不过即使在这种虚妄到有些犬儒的心态里,我还是希望双方不会有什么遗留下来的误会,不过可能短时间也很难解释了。


之前用很大篇幅提到的在 UIWidgets 和 Marklight 间来回切换的项目,目前还在写,基本上就是我用 UIWidgets 写到 UI 层快完工然后又用 Marklight 重新复刻一遍,因此现在对这个项目深恶痛绝,可能需要点时间才会重启了。虽然新品一直跳票,独立开发者的月收入倒没有大幅减少,不过新品上架的需求还是挺急迫的,因此最近在捣鼓 WRLD 和 Google Maps 的 Unity 地图 SDK,希望能从里面找点想法。
此外,我近期会写一个 Mac 端的 Menubar app,我觉得还是有些需求的,可能等周末吧。


买了两个域名,triotalk.appdepth.app。第一个已经在投入使用了,第二个暂时还没啥想法,只是觉得购物车里放俩月了这域名还没人买,可能是和我有缘,因此先拿了下来,有想要这个的可以联系我,因为我也不知道我现在拿 ARCore 或者 ARKit 能做什么,或许苹果眼镜出来后会有点用。


跑步的时候主要在听 Apple Music 上的 Defected Radio。最近还听了 Graffiti on a High School WallNaoki Watanabe 的 Star ChildIngrid Is A HybridAll InSudden DeathAcross 110th Street 7/11INSTANT VINTAGE RADIO 070hurt me on your own