Flutter三方库适配OpenHarmony【secure_application】— 自定义锁屏界面与品牌化设计
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net默认的模糊遮罩虽然能保护内容,但看起来很"素"。对于正式的商业应用,锁屏界面应该体现品牌调性——Logo、品牌色、专业的解锁交互。secure_application 的回调给了我们完全的自由度,可以在模糊遮罩上方放置任何 Widget。这篇分享几种实用的锁屏界面设计方案。
·
前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
默认的模糊遮罩虽然能保护内容,但看起来很"素"。对于正式的商业应用,锁屏界面应该体现品牌调性——Logo、品牌色、专业的解锁交互。secure_application 的 lockedBuilder 回调给了我们完全的自由度,可以在模糊遮罩上方放置任何 Widget。
这篇分享几种实用的锁屏界面设计方案。
一、lockedBuilder 回调的灵活运用
1.1 回调签名
final Widget Function(
BuildContext context,
SecureApplicationController? secureApplicationController,
)? lockedBuilder;
1.2 基本结构
SecureGate(
blurr: 40,
opacity: 0.8,
lockedBuilder: (context, controller) {
return YourCustomLockScreen(controller: controller);
},
child: YourProtectedContent(),
)
1.3 lockedBuilder 的渲染层级
┌─────────────────────────────────┐
│ lockedBuilder(你的自定义界面) │ ← 最上层,可交互
├─────────────────────────────────┤
│ BackdropFilter(模糊遮罩) │ ← 中间层,视觉遮挡
├─────────────────────────────────┤
│ child(受保护的内容) │ ← 最下层,被遮挡
└─────────────────────────────────┘
lockedBuilder 返回的 Widget 在模糊遮罩上方,可以正常接收触摸事件。
二、自定义 PIN 码输入界面
2.1 完整实现
lockedBuilder: (context, controller) => Container(
color: Colors.transparent,
child: Center(
child: Card(
elevation: 8,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: EdgeInsets.all(32),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.lock_outline, size: 48, color: Colors.blue),
SizedBox(height: 16),
Text('请输入 PIN 码', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
SizedBox(height: 24),
PinCodeField(
length: 4,
onComplete: (pin) {
if (pin == '1234') {
controller?.authSuccess(unlock: true);
} else {
controller?.authFailed();
// 显示错误提示
}
},
),
SizedBox(height: 16),
TextButton(
onPressed: () => controller?.authLogout(),
child: Text('退出登录'),
),
],
),
),
),
),
)
2.2 PIN 码输入组件
class PinCodeField extends StatefulWidget {
final int length;
final Function(String) onComplete;
const PinCodeField({required this.length, required this.onComplete});
_PinCodeFieldState createState() => _PinCodeFieldState();
}
class _PinCodeFieldState extends State<PinCodeField> {
String _pin = '';
Widget build(BuildContext context) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(widget.length, (i) => Container(
margin: EdgeInsets.symmetric(horizontal: 8),
width: 16, height: 16,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: i < _pin.length ? Colors.blue : Colors.grey.shade300,
),
)),
),
SizedBox(height: 24),
_buildNumpad(),
],
);
}
Widget _buildNumpad() {
return Wrap(
spacing: 16, runSpacing: 16,
children: [
for (int i = 1; i <= 9; i++) _numButton('$i'),
_numButton('', enabled: false),
_numButton('0'),
_deleteButton(),
],
);
}
Widget _numButton(String num, {bool enabled = true}) {
return SizedBox(
width: 64, height: 64,
child: ElevatedButton(
onPressed: enabled ? () {
setState(() {
_pin += num;
if (_pin.length == widget.length) {
widget.onComplete(_pin);
_pin = '';
}
});
} : null,
style: ElevatedButton.styleFrom(shape: CircleBorder()),
child: Text(num, style: TextStyle(fontSize: 24)),
),
);
}
Widget _deleteButton() {
return SizedBox(
width: 64, height: 64,
child: ElevatedButton(
onPressed: () {
if (_pin.isNotEmpty) setState(() => _pin = _pin.substring(0, _pin.length - 1));
},
style: ElevatedButton.styleFrom(shape: CircleBorder()),
child: Icon(Icons.backspace_outlined),
),
);
}
}
三、生物识别解锁按钮
3.1 指纹/面容解锁
lockedBuilder: (context, controller) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 80, height: 80,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withOpacity(0.9),
),
child: IconButton(
iconSize: 48,
icon: Icon(Icons.fingerprint, color: Colors.blue),
onPressed: () async {
try {
final localAuth = LocalAuthentication();
final didAuth = await localAuth.authenticate(
localizedReason: '请验证身份',
options: AuthenticationOptions(biometricOnly: true),
);
if (didAuth) {
controller?.authSuccess(unlock: true);
} else {
controller?.authFailed();
}
} catch (e) {
controller?.authFailed();
}
},
),
),
SizedBox(height: 16),
Text(
'点击指纹解锁',
style: TextStyle(color: Colors.white, fontSize: 16),
),
SizedBox(height: 32),
TextButton(
onPressed: () => _showPinInput(context, controller),
child: Text('使用 PIN 码', style: TextStyle(color: Colors.white70)),
),
],
),
)
3.2 OpenHarmony 上的生物识别
// 平台判断
Future<void> _authenticate(SecureApplicationController? controller) async {
if (Platform.isAndroid || Platform.isIOS) {
// 使用 local_auth
final result = await LocalAuthentication().authenticate(
localizedReason: '请验证身份',
);
if (result) controller?.authSuccess(unlock: true);
} else if (Platform.isOhos) {
// OpenHarmony 需要自定义实现
// 通过 MethodChannel 调用 User Authentication Kit
final result = await _ohosAuthChannel.invokeMethod('authenticate');
if (result == true) controller?.authSuccess(unlock: true);
} else {
// 其他平台:直接解锁或使用 PIN
controller?.authSuccess(unlock: true);
}
}
📌 当前限制:local_auth 尚未适配 OpenHarmony。在 OpenHarmony 上使用生物识别需要自己桥接 User Authentication Kit。
四、品牌 Logo + 模糊背景
4.1 品牌化锁屏
lockedBuilder: (context, controller) => Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.blue.shade900.withOpacity(0.3),
Colors.blue.shade700.withOpacity(0.5),
],
),
),
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 品牌 Logo
Image.asset('assets/logo_white.png', width: 120, height: 120),
SizedBox(height: 24),
// 品牌名称
Text(
'MyBank',
style: TextStyle(
color: Colors.white,
fontSize: 28,
fontWeight: FontWeight.bold,
letterSpacing: 2,
),
),
SizedBox(height: 8),
Text(
'安全守护您的财富',
style: TextStyle(color: Colors.white70, fontSize: 14),
),
SizedBox(height: 48),
// 解锁按钮
ElevatedButton.icon(
icon: Icon(Icons.fingerprint),
label: Text('验证身份'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.blue.shade900,
padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
onPressed: () => _authenticate(controller),
),
],
),
),
)
4.2 设计要点
| 要素 | 建议 | 原因 |
|---|---|---|
| Logo | 使用白色/浅色版本 | 模糊背景通常偏暗 |
| 文字 | 白色 + 适当阴影 | 确保在模糊背景上可读 |
| 按钮 | 高对比度 | 引导用户操作 |
| 渐变 | 半透明渐变叠加 | 增强品牌感 |
| SafeArea | 必须使用 | 避免被刘海/状态栏遮挡 |
五、深色模式适配
5.1 根据主题切换
lockedBuilder: (context, controller) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: isDark
? [Colors.black54, Colors.black87]
: [Colors.white54, Colors.white70],
),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.lock_outline,
size: 64,
color: isDark ? Colors.white : Colors.black87,
),
SizedBox(height: 16),
Text(
'应用已锁定',
style: TextStyle(
fontSize: 24,
color: isDark ? Colors.white : Colors.black87,
),
),
SizedBox(height: 32),
ElevatedButton(
onPressed: () => controller?.authSuccess(unlock: true),
child: Text('解锁'),
),
],
),
),
);
}
5.2 blurr 和 opacity 的主题适配
SecureGate(
blurr: isDark ? 30 : 20,
opacity: isDark ? 0.7 : 0.6,
lockedBuilder: ...,
child: ...,
)
| 参数 | 浅色模式 | 深色模式 | 原因 |
|---|---|---|---|
| blurr | 20 | 30 | 深色内容需要更多模糊才能遮挡 |
| opacity | 0.6 | 0.7 | 深色模式下需要更高透明度 |
六、动画增强
6.1 解锁成功动画
class AnimatedLockScreen extends StatefulWidget {
final SecureApplicationController? controller;
const AnimatedLockScreen({this.controller});
_AnimatedLockScreenState createState() => _AnimatedLockScreenState();
}
class _AnimatedLockScreenState extends State<AnimatedLockScreen>
with SingleTickerProviderStateMixin {
late AnimationController _animController;
late Animation<double> _scaleAnimation;
void initState() {
super.initState();
_animController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 300),
);
_scaleAnimation = Tween<double>(begin: 0.8, end: 1.0).animate(
CurvedAnimation(parent: _animController, curve: Curves.easeOut),
);
_animController.forward();
}
void dispose() {
_animController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return ScaleTransition(
scale: _scaleAnimation,
child: FadeTransition(
opacity: _animController,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.lock, size: 64, color: Colors.white),
SizedBox(height: 16),
Text('应用已锁定', style: TextStyle(color: Colors.white, fontSize: 24)),
SizedBox(height: 32),
ElevatedButton(
onPressed: () async {
await _animController.reverse();
widget.controller?.authSuccess(unlock: true);
},
child: Text('解锁'),
),
],
),
),
),
);
}
}
6.2 使用方式
lockedBuilder: (context, controller) => AnimatedLockScreen(controller: controller),
七、不同场景的锁屏设计
7.1 场景与设计对照
| 场景 | 设计风格 | 解锁方式 | blurr | opacity |
|---|---|---|---|---|
| 银行App | 严肃专业 | 生物识别 + PIN | 60 | 0.9 |
| 社交App | 轻松活泼 | 滑动解锁 | 20 | 0.6 |
| 企业App | 简洁商务 | PIN 码 | 40 | 0.8 |
| 医疗App | 安全可信 | 生物识别 | 80 | 0.9 |
| 笔记App | 简约文艺 | 图案解锁 | 20 | 0.5 |
7.2 无认证场景
// 只需要遮挡,不需要认证
SecureGate(
blurr: 20,
opacity: 0.6,
// 不提供 lockedBuilder
// 遮罩会在用户切回时自动消失(通过 onNeedUnlock 自动解锁)
child: MyContent(),
)
配合自动解锁:
SecureApplication(
onNeedUnlock: (controller) async {
controller?.authSuccess(unlock: true);
return null;
},
child: ...,
)
总结
本文展示了多种自定义锁屏界面的设计方案:
- PIN 码输入:数字键盘 + 圆点指示器
- 生物识别:指纹/面容按钮 + 备用 PIN 码
- 品牌化设计:Logo + 渐变背景 + 品牌色
- 深色模式:根据主题动态调整颜色和参数
- 动画增强:缩放 + 淡入淡出的进入动画
下一篇我们讲敏感数据清除与安全增强——认证失败后如何保护用户数据。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
更多推荐


所有评论(0)