Flutter桌面小工具 -- 靈動島【Windows+Android版本】
theme: vuepress
通過此篇文章,你將瞭解到: 1. Flutter動畫實現靈動島; 2. Flutter如何開發一個置頂可自由拖拽的小工具; 3. 分享一些關於靈動島的想法。
⚠️本文為稀土掘金技術社群首發簽約文章,14天內禁止轉載,14天后未獲授權禁止轉載,侵權必究!
前言
Flutter開發Windows應用已經見怪不怪了,我覺得可以嘗試做一些小工具。恰逢近期最近蘋果iphone 14
系列推出“靈動島”,這個酷炫的元件瞬間引起很多關注;而且看到一些前端部落格用css實現靈動島的效果;作為Flutter的忠實擁護者,前端能寫的Flutter必須能寫!
靈動島效果實現
- 小藥丸放大
小藥丸放大的效果可以拆分為兩步:橫向放大+慣性縮放回彈。需要兩個動畫和控制器,當放大動畫執行完畢的時候,執行縮放動畫,從1.04到1.0。
``` dart
// 初始化變數
late Animation
late Animation dart
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_animation = Tween
_scaleAnimationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_scaleAnimation = Tween
// 放大動畫執行完畢,開始縮放動畫,從1.04到1.0
animationController.addStatusListener((status) {
this.status = status;
if (status == AnimationStatus.completed) {
_scaleAnimation = Tween佈局上使用`AnimatedBuilder`監聽animate值的變化,設定小藥丸的寬高以達到放大和縮放效果。
dart
AnimatedBuilder(
animation: _scaleAnimation,
builder: (context, - **i形分離效果**
在小藥丸後面要分離出一個小圓圈,從而實現i形的效果;這裡也需要一個動畫控制器,在佈局上我們選擇`Stack`和`Positioned`。分離過程就是小圓圈的右邊距一直往負的方向放大,實現向右移出和向左縮回。
dart
late Animationdart
_ballAnimationController = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
);
_ballAnimation = Tween
// 當藥丸縮回的過程中,執行分離動畫
animationController.addListener(() {
if (count == 2 &&
status == AnimationStatus.reverse &&
_animationController.value > 0.25 &&
_animationController.value < 0.3) {
_ballAnimationController.forward(from: 0);
}
});
上面是動畫的過程,我們再看下佈局的程式碼:
AnimatedBuilder(
animation: _ballAnimation,
builder: (context, ) => Stack(clipBehavior: Clip.none, children: [
AnimatedBuilder(
// .... 小藥丸 ....
),
Positioned(
top: 0,
right: _ballAnimation.value,
child: Container(
width: EnvConfig.relHeight,
height: EnvConfig.relHeight,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.black,
),
),
),
]),
),
```
動畫其實非常簡單,也沒啥好講的,重點在分享如何作為一個小工具。具體原始碼見文末倉庫。
將應用配置為小工具【Windows端】
這裡的前提是基於上一篇文章:做好螢幕的適配。
在windows上,小工具就是一個普通應用【這跟Android window_manager的機制是不一樣的】。不過我們需要把寬高、位置設定好;同時還需要保證小工具置頂、沒有狀態列圖示。
這裡我們依然用到了window_manager的外掛,每個步驟都有對應註釋。
``` dart
static late double relHeight;
static initWindow(List
將應用配置為小工具【Android端】
Android小元件與Windows可是大有不同。由於Google基於安全的限制,Android應用必須是全屏且不允許穿透點選,因此Android的小元件一般都是依附於懸浮窗來開發的
,即windows_manager
。
Flutter只是一個UI框架,自然也不能脫離Android本身的機制,因此我們需要在原生層建立一個懸浮窗,然後建立一個Flutter engine來吸附Flutter的UI。
- 建立後臺服務
``` manifest
- 建立一個懸浮窗,實現步驟注意看其中的註釋
kotlin
package com.karl.open.desktop_app
import android.annotation.SuppressLint import android.app.Service import android.content.Intent import android.graphics.PixelFormat import android.os.IBinder import android.util.DisplayMetrics import android.view.LayoutInflater import android.view.MotionEvent import android.view.ViewGroup import android.view.WindowManager import android.widget.FrameLayout import com.karl.open.desktop_app.utils.Utils import io.flutter.embedding.android.FlutterSurfaceView import io.flutter.embedding.android.FlutterView import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngineGroup import io.flutter.embedding.engine.dart.DartExecutor import io.flutter.view.FlutterMain.findAppBundlePath
class WindowsService : Service() { // Flutter引擎組,可以自動管理引擎的生命週期 private lateinit var engineGroup: FlutterEngineGroup
private lateinit var engine: FlutterEngine
private lateinit var flutterView: FlutterView
private lateinit var windowManager: WindowManager
private val metrics = DisplayMetrics()
private lateinit var inflater: LayoutInflater
@SuppressLint("InflateParams")
private lateinit var rootView: ViewGroup
private lateinit var layoutParams: WindowManager.LayoutParams
override fun onCreate() {
super.onCreate()
layoutParams = WindowManager.LayoutParams(
Utils.dip2px(this, 168.toFloat()),
Utils.dip2px(this, 30.toFloat()),
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
)
// 初始化變數
windowManager = this.getSystemService(Service.WINDOW_SERVICE) as WindowManager
inflater =
this.getSystemService(Service.LAYOUT_INFLATER_SERVICE) as LayoutInflater
rootView = inflater.inflate(R.layout.floating, null, false) as ViewGroup
engineGroup = FlutterEngineGroup(this)
// 建立Flutter Engine
val dartEntrypoint = DartExecutor.DartEntrypoint(findAppBundlePath(), "main")
val option =
FlutterEngineGroup.Options(this).setDartEntrypoint(dartEntrypoint)
engine = engineGroup.createAndRunEngine(option)
// 設定懸浮窗的位置
@Suppress("Deprecation")
windowManager.defaultDisplay.getMetrics(metrics)
setPosition()
@Suppress("ClickableViewAccessibility")
rootView.setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
layoutParams.flags =
layoutParams.flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
windowManager.updateViewLayout(rootView, layoutParams)
true
}
else -> false
}
}
engine.lifecycleChannel.appIsResumed()
// 為懸浮窗加入佈局
rootView.findViewById<FrameLayout>(R.id.floating_window)
.addView(
flutterView,
ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
)
windowManager.updateViewLayout(rootView, layoutParams)
}
private fun setPosition() {
// 設定位置
val screenWidth = metrics.widthPixels
val screenHeight = metrics.heightPixels
layoutParams.x = (screenWidth - layoutParams.width) / 2
layoutParams.y = (screenHeight - layoutParams.height) / 2
windowManager.addView(rootView, layoutParams)
flutterView = FlutterView(inflater.context, FlutterSurfaceView(inflater.context, true))
flutterView.attachToFlutterEngine(engine)
}
}
```
- 喚起懸浮窗元件
直接通過adb指令喚起即可
adb shell am start-foreground-service -n com.karl.open.desktop_app/com.karl.open.desktop_app.WindowsService - 注意 1. 通過服務喚起懸浮窗,Android要求必須是系統應用,因此大家在使用的時候還需要配置下系統簽名; 2. Flutter engine必須使用FlutterEngineGroup進行託管,否則靜置一段時間後,engine就會被系統回收!!!
關於靈動島的一些思考
windows版本的靈動島元件,實現起來其實是比較簡單的。但是我在思考,假設我作為一個OS開發者,我該怎麼看待iPhone的這個軟體創新?
1. iPhone這個靈動島的問世,其實把使用者對狀態列的認知顛覆了:原來平時用來看時間電量的地方還能這麼玩;這個創新能否帶動整個移動端、甚至桌面端狀態列等工具的改革
?
2. 雖說創新,但目前從各種測評來看,這個工具很少有應用接入,連iOS自己的軟體都很多沒有接入。著實是有點雞肋的,而且使用者還要去學習如何使用這個靈動島,當應用更多的接入進來,使用者的教育成本會變得更高,降低使用體驗。所以iPhone為啥敢開拓創新
做這個至少目前很雞肋的工具呢?
3. iPhone官方如何去推廣靈動島,讓更多使用者接受
?
上面這幾個問題,也是我一直在思考的。但其實是環環相扣的,首先能否引領新的互動改革,這個取決於市場的接受度。而市場的接受度,除了果粉引以為傲的“別人沒有而我有”,還要做到真正的實用:iOS自身更多的軟體接入,讓靈動島功能更完善。
使用者習慣了用這個工具,大量軟體就必須為了使用者而作。
同時按照iPhone的營銷手段,會大量利用iPhone的使用者心理,不斷放大這個靈動島的格調,很多軟體為了俘獲使用者,甚至會專門為靈動島做一些擴充的功能,從而吸引很多使用者。【目前已有一些軟體在做這個事情了】
而假設我是OS開發者,如果我要去做這個工具,首先我的使用者基數要足夠大,同時讓工具提供簡單且實用的功能,真正把投入產出比做好,而且真正得服務於使用者。酷炫與否交給營銷去推廣,真正對使用者有用的東西,才是底子所在!
寫在最後
靈動島元件的實現,分windows和android系統。
專案原始碼倉庫:http://github.com/WxqKb/open_flutter_desktop.git
- Flutter桌面開發 - windows外掛開發
- Flutter桌面開發-專案工程化框架搭建
- Flutter桌面小工具 -- 靈動島【Windows Android版本】
- Flutter資源下載實現斷點續傳
- Flutter桌面應用如何進行多解析度適配
- Flutter - 桌面應用視窗化實戰
- 你真的敢落地Flutter桌面端嗎?
- Flutter 桌面端實踐之識別外接媒體裝置
- Flutter【移動端】如何進行多渠道打包釋出
- Flutter實現新手引導蒙層的兩種方式
- 移動端音視訊需求實現方案探索
- Flutter實現酷狗流暢Tabbar效果
- Flutter實現動態化更新-技術預研
- Flutter動畫-實現閃爍星星
- 我該如何給Flutter webview新增透明背景?
- Flutter輸入框獲取剪下板-合規問題踩坑