【maaath】 Flutter for OpenHarmony 安全中心应用实战开发
随着 OpenHarmony 生态的日益成熟,越来越多的开发者开始关注如何在鸿蒙设备上构建高质量的应用。Flutter for OpenHarmony 作为跨平台框架的重要一员,让开发者能够使用一套 Dart 代码同时覆盖 Android、iOS 和 OpenHarmony 三大平台,大幅降低了多端适配的成本。本文将带领大家从零开始,使用 Flutter for OpenHarmony 构建一款功
Flutter for OpenHarmony 安全中心应用实战开发
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
作者:maaath
一、前言
随着 OpenHarmony 生态的日益成熟,越来越多的开发者开始关注如何在鸿蒙设备上构建高质量的应用。Flutter for OpenHarmony 作为跨平台框架的重要一员,让开发者能够使用一套 Dart 代码同时覆盖 Android、iOS 和 OpenHarmony 三大平台,大幅降低了多端适配的成本。
本文将带领大家从零开始,使用 Flutter for OpenHarmony 构建一款功能完备的安全中心应用,涵盖手机体检评分、病毒扫描、垃圾清理、应用锁、隐私保护报告、支付环境检测、流量防盗监控和安全加速优化八大核心功能模块。通过本文,你将掌握 Flutter 在鸿蒙设备上的 UI 布局、状态管理、动画交互以及系统 API 调用等实用技巧。
本文完整源码已托管在 AtomGit:https://atomgit.com,欢迎 Star 和 Fork。
二、项目架构设计
在开始编码之前,我们先对项目进行整体架构设计。安全中心应用采用模块化分层架构:
lib/
├── main.dart # 应用入口
├── pages/
│ ├── home_page.dart # 首页仪表盘
│ ├── phone_check_page.dart # 手机体检评分
│ ├── virus_scan_page.dart # 病毒扫描
│ ├── junk_clean_page.dart # 垃圾清理
│ ├── app_lock_page.dart # 应用锁
│ ├── privacy_report_page.dart # 隐私保护报告
│ ├── payment_check_page.dart # 支付环境检测
│ ├── data_monitor_page.dart # 流量防盗监控
│ └── security_optimize_page.dart # 安全加速优化
├── widgets/
│ ├── score_ring.dart # 评分环形组件
│ ├── feature_card.dart # 功能卡片组件
│ └── scan_progress.dart # 扫描进度组件
├── models/
│ ├── check_item.dart # 检测项数据模型
│ └── junk_item.dart # 垃圾项数据模型
└── utils/
├── format_utils.dart # 格式化工具
└── mock_data.dart # 模拟数据
三、首页仪表盘实现
首页是整个应用的入口,我们设计了一个包含安全评分环形图、快捷操作按钮和功能网格的仪表盘布局。
import 'package:flutter/material.dart';
import '../widgets/score_ring.dart';
import '../widgets/feature_card.dart';
import 'phone_check_page.dart';
import 'virus_scan_page.dart';
import 'junk_clean_page.dart';
import 'app_lock_page.dart';
import 'privacy_report_page.dart';
import 'payment_check_page.dart';
import 'data_monitor_page.dart';
import 'security_optimize_page.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _securityScore = 92;
String _statusText = '您的设备处于安全状态';
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
body: SafeArea(
child: CustomScrollView(
slivers: [
_buildScoreHeader(),
_buildQuickActions(),
_buildFeatureGrid(),
],
),
),
);
}
Widget _buildScoreHeader() {
return SliverToBoxAdapter(
child: Container(
margin: const EdgeInsets.only(bottom: 16),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
),
boxShadow: [
BoxShadow(
color: Color(0x10000000),
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Column(
children: [
const SizedBox(height: 30),
const Text(
'安全评分',
style: TextStyle(fontSize: 16, color: Color(0xFF8E8E93)),
),
const SizedBox(height: 20),
ScoreRing(
score: _securityScore,
size: 160,
strokeWidth: 10,
),
const SizedBox(height: 8),
Text(
_statusText,
style: const TextStyle(fontSize: 12, color: Color(0xFF8E8E93)),
),
const SizedBox(height: 20),
],
),
),
);
}
Widget _buildQuickActions() {
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const VirusScanPage()),
),
icon: const Icon(Icons.shield, size: 18),
label: const Text('立即扫描'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF007AFF),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.symmetric(vertical: 12),
),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const SecurityOptimizePage(),
),
),
icon: const Icon(Icons.bolt, size: 18),
label: const Text('立即优化'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF34C759),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.symmetric(vertical: 12),
),
),
),
],
),
),
);
}
Widget _buildFeatureGrid() {
final features = [
FeatureData('手机体检', Icons.search, const Color(0xFF007AFF),
() => _navigateTo(const PhoneCheckPage())),
FeatureData('病毒扫描', Icons.bug_report, const Color(0xFFFF3B30),
() => _navigateTo(const VirusScanPage())),
FeatureData('垃圾清理', Icons.cleaning_services, const Color(0xFFFF9500),
() => _navigateTo(const JunkCleanPage())),
FeatureData('应用锁', Icons.lock, const Color(0xFF5856D6),
() => _navigateTo(const AppLockPage())),
FeatureData('隐私报告', Icons.privacy_tip, const Color(0xFF34C759),
() => _navigateTo(const PrivacyReportPage())),
FeatureData('支付安全', Icons.credit_card, const Color(0xFFFF2D55),
() => _navigateTo(const PaymentCheckPage())),
FeatureData('流量监控', Icons.signal_cellular_alt, const Color(0xFF5AC8FA),
() => _navigateTo(const DataMonitorPage())),
FeatureData('安全加速', Icons.rocket_launch, const Color(0xFFFF9500),
() => _navigateTo(const SecurityOptimizePage())),
];
return SliverPadding(
padding: const EdgeInsets.all(16),
sliver: SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 1.1,
),
delegate: SliverChildBuilderDelegate(
(context, index) => FeatureCard(data: features[index]),
childCount: features.length,
),
),
);
}
void _navigateTo(Widget page) {
Navigator.push(context, MaterialPageRoute(builder: (_) => page));
}
}
评分环形组件
评分环形组件是整个仪表盘的核心视觉元素,我们使用 CustomPainter 来绘制:
import 'dart:math';
import 'package:flutter/material.dart';
class ScoreRing extends StatelessWidget {
final int score;
final double size;
final double strokeWidth;
const ScoreRing({
super.key,
required this.score,
this.size = 160,
this.strokeWidth = 10,
});
Color get _scoreColor {
if (score >= 80) return const Color(0xFF34C759);
if (score >= 60) return const Color(0xFFFF9500);
return const Color(0xFFFF3B30);
}
Widget build(BuildContext context) {
return SizedBox(
width: size,
height: size,
child: Stack(
alignment: Alignment.center,
children: [
CustomPaint(
size: Size(size, size),
painter: _RingPainter(
progress: score / 100.0,
color: _scoreColor,
strokeWidth: strokeWidth,
),
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'$score',
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
color: _scoreColor,
),
),
const Text(
'安全评分',
style: TextStyle(fontSize: 12, color: Color(0xFF8E8E93)),
),
],
),
],
),
);
}
}
class _RingPainter extends CustomPainter {
final double progress;
final Color color;
final double strokeWidth;
_RingPainter({
required this.progress,
required this.color,
required this.strokeWidth,
});
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final radius = (size.width - strokeWidth) / 2;
final bgPaint = Paint()
..color = const Color(0xFFE5E5EA)
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round;
canvas.drawCircle(center, radius, bgPaint);
final fgPaint = Paint()
..color = color
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round;
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
-pi / 2,
2 * pi * progress,
false,
fgPaint,
);
}
bool shouldRepaint(covariant _RingPainter oldDelegate) {
return oldDelegate.progress != progress || oldDelegate.color != color;
}
}
四、手机体检评分模块
手机体检模块模拟了对设备六大维度的安全检测,包括系统安全、应用权限、存储空间、电池健康、网络环境和运行性能。每个检测项逐项扫描并给出评分,最终汇总为综合评分。
import 'package:flutter/material.dart';
import 'dart:async';
class PhoneCheckPage extends StatefulWidget {
const PhoneCheckPage({super.key});
State<PhoneCheckPage> createState() => _PhoneCheckPageState();
}
class _PhoneCheckPageState extends State<PhoneCheckPage> {
bool _isChecking = false;
double _checkProgress = 0;
bool _checkComplete = false;
int _totalScore = 0;
final List<Map<String, dynamic>> _checkItems = [
{'name': '系统安全检测', 'icon': Icons.shield, 'score': 0, 'status': 'waiting', 'desc': '检查系统漏洞与安全补丁'},
{'name': '应用权限检测', 'icon': Icons.security, 'score': 0, 'status': 'waiting', 'desc': '检查应用敏感权限使用情况'},
{'name': '存储空间检测', 'icon': Icons.storage, 'score': 0, 'status': 'waiting', 'desc': '检查存储空间使用状态'},
{'name': '电池健康检测', 'icon': Icons.battery_std, 'score': 0, 'status': 'waiting', 'desc': '检查电池健康状况'},
{'name': '网络环境检测', 'icon': Icons.wifi, 'score': 0, 'status': 'waiting', 'desc': '检查WiFi与网络安全'},
{'name': '运行性能检测', 'icon': Icons.speed, 'score': 0, 'status': 'waiting', 'desc': '检查CPU与内存使用情况'},
];
void _startCheck() {
setState(() {
_isChecking = true;
_checkProgress = 0;
_checkComplete = false;
_totalScore = 0;
for (var item in _checkItems) {
item['score'] = 0;
item['status'] = 'waiting';
}
});
_runCheckSequence(0);
}
void _runCheckSequence(int index) {
if (index >= _checkItems.length) {
setState(() {
_checkProgress = 100;
_isChecking = false;
_checkComplete = true;
});
return;
}
setState(() {
_checkItems[index]['status'] = 'checking';
_checkProgress = (index / _checkItems.length) * 100;
});
Timer(const Duration(milliseconds: 800), () {
final score = 70 + (DateTime.now().millisecond % 30);
setState(() {
_checkItems[index]['score'] = score;
_checkItems[index]['status'] = 'done';
_totalScore = (_checkItems.fold<int>(
0,
(sum, item) => sum + (item['score'] as int),
) /
_checkItems.length)
.round();
_checkProgress = ((index + 1) / _checkItems.length) * 100;
});
_runCheckSequence(index + 1);
});
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
appBar: AppBar(
title: const Text('手机体检'),
backgroundColor: Colors.white,
foregroundColor: const Color(0xFF1A1A1A),
elevation: 0,
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildScoreDisplay(),
if (!_checkComplete && !_isChecking) _buildStartButton(),
if (_isChecking) _buildProgressBar(),
if (_checkComplete) _buildResultSummary(),
const SizedBox(height: 16),
_buildCheckItemList(),
],
),
);
}
Widget _buildScoreDisplay() {
return Center(
child: Column(
children: [
const SizedBox(height: 24),
Container(
width: 140,
height: 140,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: _checkComplete
? (_totalScore >= 80
? const Color(0xFF34C759)
: const Color(0xFFFF9500))
: const Color(0xFFE5E5EA),
width: 8,
),
),
child: Center(
child: Text(
_checkComplete ? '$_totalScore' : '--',
style: TextStyle(
fontSize: 42,
fontWeight: FontWeight.bold,
color: _checkComplete
? (_totalScore >= 80
? const Color(0xFF34C759)
: const Color(0xFFFF9500))
: const Color(0xFF8E8E93),
),
),
),
),
const SizedBox(height: 8),
Text(
_checkComplete ? '综合评分' : '点击下方开始体检',
style: const TextStyle(fontSize: 12, color: Color(0xFF8E8E93)),
),
],
),
);
}
Widget _buildStartButton() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Center(
child: ElevatedButton(
onPressed: _startCheck,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF007AFF),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.symmetric(horizontal: 48, vertical: 12),
),
child: const Text('开始体检', style: TextStyle(fontSize: 16)),
),
),
);
}
Widget _buildProgressBar() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: _checkProgress / 100,
backgroundColor: const Color(0xFFE5E5EA),
valueColor:
const AlwaysStoppedAnimation<Color>(Color(0xFF007AFF)),
minHeight: 6,
),
),
const SizedBox(height: 8),
Text(
'正在体检中... ${_checkProgress.toInt()}%',
style: const TextStyle(fontSize: 13, color: Color(0xFF8E8E93)),
),
],
),
);
}
Widget _buildResultSummary() {
final excellentCount =
_checkItems.where((item) => (item['score'] as int) >= 80).length;
final warningCount = _checkItems
.where((item) =>
(item['score'] as int) > 0 && (item['score'] as int) < 80)
.length;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text('✅ 优秀项: ${excellentCount}项',
style: const TextStyle(color: Color(0xFF34C759), fontSize: 13)),
Text('⚠️ 需优化: ${warningCount}项',
style: const TextStyle(color: Color(0xFFFF9500), fontSize: 13)),
],
),
);
}
Widget _buildCheckItemList() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('检测项目',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFF1A1A1A))),
const SizedBox(height: 8),
..._checkItems.map((item) => _buildCheckItemTile(item)),
],
);
}
Widget _buildCheckItemTile(Map<String, dynamic> item) {
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(item['icon'] as IconData, size: 24, color: const Color(0xFF007AFF)),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item['name'] as String,
style: const TextStyle(
fontSize: 14, fontWeight: FontWeight.w500)),
Text(item['desc'] as String,
style: const TextStyle(
fontSize: 11, color: Color(0xFF8E8E93))),
],
),
),
_buildStatusWidget(item['status'] as String, item['score'] as int),
],
),
);
}
Widget _buildStatusWidget(String status, int score) {
switch (status) {
case 'waiting':
return const Text('待检测',
style: TextStyle(fontSize: 12, color: Color(0xFF8E8E93)));
case 'checking':
return const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
);
case 'done':
return Text(
score >= 80 ? '✅ $score' : '⚠️ $score',
style: TextStyle(
fontSize: 13,
color: score >= 80 ? const Color(0xFF34C759) : const Color(0xFFFF9500),
),
);
default:
return const SizedBox();
}
}
}
五、病毒扫描模块
病毒扫描模块支持快速扫描和全盘扫描两种模式,对已安装应用、系统文件、存储文件、下载文件和缓存文件五个维度进行逐一排查。
class VirusScanPage extends StatefulWidget {
const VirusScanPage({super.key});
State<VirusScanPage> createState() => _VirusScanPageState();
}
class _VirusScanPageState extends State<VirusScanPage> {
bool _isScanning = false;
double _scanProgress = 0;
bool _scanComplete = false;
int _scannedFiles = 0;
int _threatsFound = 0;
String _scanMode = 'quick';
final List<Map<String, dynamic>> _scanItems = [
{'name': '已安装应用', 'icon': Icons.phone_android, 'count': 0, 'status': 'waiting'},
{'name': '系统文件', 'icon': Icons.settings, 'count': 0, 'status': 'waiting'},
{'name': '存储文件', 'icon': Icons.folder, 'count': 0, 'status': 'waiting'},
{'name': '下载文件', 'icon': Icons.download, 'count': 0, 'status': 'waiting'},
{'name': '缓存文件', 'icon': Icons.cached, 'count': 0, 'status': 'waiting'},
];
void _startScan() {
setState(() {
_isScanning = true;
_scanProgress = 0;
_scanComplete = false;
_scannedFiles = 0;
_threatsFound = 0;
for (var item in _scanItems) {
item['count'] = 0;
item['status'] = 'waiting';
}
});
_runScanSequence(0);
}
void _runScanSequence(int index) {
if (index >= _scanItems.length) {
setState(() {
_scanProgress = 100;
_isScanning = false;
_scanComplete = true;
_threatsFound = DateTime.now().millisecond % 3;
});
return;
}
setState(() => _scanItems[index]['status'] = 'scanning');
Timer(const Duration(milliseconds: 600), () {
final count = 50 + (DateTime.now().millisecond % 100);
setState(() {
_scanItems[index]['count'] = count;
_scanItems[index]['status'] = 'done';
_scannedFiles += count;
_scanProgress = ((index + 1) / _scanItems.length) * 100;
});
_runScanSequence(index + 1);
});
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
appBar: AppBar(
title: const Text('病毒扫描'),
backgroundColor: Colors.white,
foregroundColor: const Color(0xFF1A1A1A),
elevation: 0,
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildStatusHeader(),
if (!_scanComplete && !_isScanning) ...[
_buildModeSelector(),
_buildStartButton(),
],
if (_isScanning) _buildProgressBar(),
if (_scanComplete) _buildResultCards(),
const SizedBox(height: 16),
_buildScanDetailList(),
],
),
);
}
Widget _buildModeSelector() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => setState(() => _scanMode = 'quick'),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: _scanMode == 'quick'
? const Color(0xFFE8F0FE)
: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: _scanMode == 'quick'
? const Color(0xFF007AFF)
: Colors.transparent,
width: 1.5,
),
),
child: const Column(
children: [
Text('快速扫描',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
Text('扫描关键位置',
style: TextStyle(fontSize: 10, color: Color(0xFF8E8E93))),
],
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: GestureDetector(
onTap: () => setState(() => _scanMode = 'full'),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: _scanMode == 'full'
? const Color(0xFFE8F0FE)
: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: _scanMode == 'full'
? const Color(0xFF007AFF)
: Colors.transparent,
width: 1.5,
),
),
child: const Column(
children: [
Text('全盘扫描',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
Text('深度扫描所有文件',
style: TextStyle(fontSize: 10, color: Color(0xFF8E8E93))),
],
),
),
),
),
],
),
);
}
Widget _buildStartButton() {
return Center(
child: ElevatedButton(
onPressed: _startScan,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFF3B30),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
padding: const EdgeInsets.symmetric(horizontal: 48, vertical: 12),
),
child: const Text('开始扫描', style: TextStyle(fontSize: 16)),
),
);
}
Widget _buildProgressBar() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: _scanProgress / 100,
backgroundColor: const Color(0xFFE5E5EA),
valueColor:
const AlwaysStoppedAnimation<Color>(Color(0xFFFF3B30)),
minHeight: 6,
),
),
const SizedBox(height: 8),
Text('已扫描 $_scannedFiles 个文件',
style: const TextStyle(fontSize: 13, color: Color(0xFF8E8E93))),
],
),
);
}
Widget _buildResultCards() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
_buildStatCard('$_scannedFiles', '扫描文件', const Color(0xFF007AFF)),
_buildStatCard('$_threatsFound', '发现威胁',
_threatsFound > 0 ? const Color(0xFFFF3B30) : const Color(0xFF34C759)),
_buildStatCard(
_scanMode == 'quick' ? '快速' : '全盘', '扫描模式', const Color(0xFF1A1A1A)),
],
),
);
}
Widget _buildStatCard(String value, String label, Color color) {
return Expanded(
child: Column(
children: [
Text(value,
style: TextStyle(
fontSize: 22, fontWeight: FontWeight.bold, color: color)),
Text(label,
style:
const TextStyle(fontSize: 11, color: Color(0xFF8E8E93))),
],
),
);
}
Widget _buildStatusHeader() {
return Center(
child: Column(
children: [
const SizedBox(height: 20),
Icon(
_scanComplete
? (_threatsFound == 0 ? Icons.shield : Icons.warning_amber)
: Icons.search,
size: 64,
color: _scanComplete
? (_threatsFound == 0
? const Color(0xFF34C759)
: const Color(0xFFFF3B30))
: const Color(0xFF007AFF),
),
const SizedBox(height: 8),
Text(
_isScanning
? '正在扫描...'
: (_scanComplete
? (_threatsFound == 0 ? '设备安全' : '发现威胁')
: '准备扫描'),
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
),
],
),
);
}
Widget _buildScanDetailList() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('扫描详情',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFF1A1A1A))),
const SizedBox(height: 8),
..._scanItems.map((item) => Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(item['icon'] as IconData, size: 24),
const SizedBox(width: 12),
Expanded(
child: Text(item['name'] as String,
style: const TextStyle(fontSize: 14)),
),
item['status'] == 'done'
? Text('已扫描 ${item['count']} 项',
style: const TextStyle(
fontSize: 12, color: Color(0xFF34C759)))
: (item['status'] == 'scanning'
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('等待中',
style: TextStyle(
fontSize: 12, color: Color(0xFF8E8E93)))),
],
),
)),
],
);
}
}
六、垃圾清理模块
垃圾清理模块先扫描设备中的各类垃圾文件,然后让用户选择性清理。支持系统缓存、应用缓存、临时文件、残留文件、日志文件、下载缓存、缩略图缓存和空文件夹八类垃圾的识别与清理。
class JunkCleanPage extends StatefulWidget {
const JunkCleanPage({super.key});
State<JunkCleanPage> createState() => _JunkCleanPageState();
}
class _JunkCleanPageState extends State<JunkCleanPage> {
bool _isScanning = false;
double _scanProgress = 0;
bool _scanComplete = false;
double _totalJunk = 0;
bool _isCleaning = false;
double _cleanProgress = 0;
bool _cleanComplete = false;
final List<Map<String, dynamic>> _junkItems = [
{'name': '系统缓存', 'icon': Icons.settings, 'size': 0.0, 'checked': true},
{'name': '应用缓存', 'icon': Icons.phone_android, 'size': 0.0, 'checked': true},
{'name': '临时文件', 'icon': Icons.description, 'size': 0.0, 'checked': true},
{'name': '残留文件', 'icon': Icons.delete_outline, 'size': 0.0, 'checked': true},
{'name': '日志文件', 'icon': Icons.article, 'size': 0.0, 'checked': true},
{'name': '下载缓存', 'icon': Icons.download, 'size': 0.0, 'checked': false},
{'name': '缩略图缓存', 'icon': Icons.image, 'size': 0.0, 'checked': true},
{'name': '空文件夹', 'icon': Icons.folder_open, 'size': 0.0, 'checked': true},
];
String _formatSize(double mb) {
if (mb >= 1024) return '${(mb / 1024).toStringAsFixed(1)} GB';
return '${mb.toInt()} MB';
}
double get _checkedSize =>
_junkItems.where((i) => i['checked']).fold(0.0, (s, i) => s + (i['size'] as double));
void _startScan() {
setState(() {
_isScanning = true;
_scanProgress = 0;
_scanComplete = false;
_totalJunk = 0;
for (var item in _junkItems) {
item['size'] = 0.0;
}
});
_runScanSequence(0);
}
void _runScanSequence(int index) {
if (index >= _junkItems.length) {
setState(() {
_scanProgress = 100;
_isScanning = false;
_scanComplete = true;
});
return;
}
Timer(const Duration(milliseconds: 400), () {
final size = 10.0 + (DateTime.now().millisecond % 200).toDouble();
setState(() {
_junkItems[index]['size'] = size;
_totalJunk += size;
_scanProgress = ((index + 1) / _junkItems.length) * 100;
});
_runScanSequence(index + 1);
});
}
void _startClean() {
setState(() {
_isCleaning = true;
_cleanProgress = 0;
_cleanComplete = false;
});
Timer.periodic(const Duration(milliseconds: 200), (timer) {
setState(() => _cleanProgress += 10);
if (_cleanProgress >= 100) {
timer.cancel();
setState(() {
_cleanProgress = 100;
_isCleaning = false;
_cleanComplete = true;
});
}
});
}
void _resetAll() {
setState(() {
_isScanning = false;
_scanProgress = 0;
_scanComplete = false;
_totalJunk = 0;
_isCleaning = false;
_cleanProgress = 0;
_cleanComplete = false;
for (var item in _junkItems) {
item['size'] = 0.0;
item['checked'] = true;
}
});
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
appBar: AppBar(
title: const Text('垃圾清理'),
backgroundColor: Colors.white,
foregroundColor: const Color(0xFF1A1A1A),
elevation: 0,
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildStatusHeader(),
if (!_scanComplete && !_isScanning) _buildStartButton(),
if (_isScanning) _buildProgressBar(),
if (_scanComplete && !_cleanComplete) ...[
_buildJunkSummary(),
_buildJunkItemList(),
_buildCleanButton(),
],
if (_isCleaning) _buildCleaningProgress(),
if (_cleanComplete) _buildCleanResult(),
],
),
);
}
Widget _buildStatusHeader() {
return Center(
child: Column(
children: [
const SizedBox(height: 24),
Icon(
_scanComplete ? Icons.cleaning_services : Icons.search,
size: 56,
color: const Color(0xFFFF9500),
),
const SizedBox(height: 8),
Text(
_scanComplete ? '发现可清理垃圾' : '扫描设备垃圾',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
),
if (_scanComplete)
Text(
_formatSize(_totalJunk),
style: const TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Color(0xFFFF9500)),
),
],
),
);
}
Widget _buildStartButton() {
return Center(
child: ElevatedButton(
onPressed: _startScan,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFF9500),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
padding: const EdgeInsets.symmetric(horizontal: 48, vertical: 12),
),
child: const Text('开始扫描', style: TextStyle(fontSize: 16)),
),
);
}
Widget _buildProgressBar() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: _scanProgress / 100,
backgroundColor: const Color(0xFFE5E5EA),
valueColor:
const AlwaysStoppedAnimation<Color>(Color(0xFFFF9500)),
minHeight: 6,
),
),
const SizedBox(height: 8),
Text('正在扫描垃圾文件... ${_scanProgress.toInt()}%',
style: const TextStyle(fontSize: 13, color: Color(0xFF8E8E93))),
],
),
);
}
Widget _buildJunkSummary() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
_buildStatCard(_formatSize(_totalJunk), '垃圾总量', const Color(0xFFFF9500)),
_buildStatCard(_formatSize(_checkedSize), '已选清理', const Color(0xFF007AFF)),
_buildStatCard(
'${_junkItems.where((i) => i['checked']).length}/${_junkItems.length}',
'选中项',
const Color(0xFF1A1A1A)),
],
),
);
}
Widget _buildStatCard(String value, String label, Color color) {
return Expanded(
child: Column(
children: [
Text(value,
style: TextStyle(
fontSize: 20, fontWeight: FontWeight.bold, color: color)),
Text(label,
style:
const TextStyle(fontSize: 11, color: Color(0xFF8E8E93))),
],
),
);
}
Widget _buildJunkItemList() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('清理项目',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFF1A1A1A))),
const SizedBox(height: 8),
..._junkItems.asMap().entries.map((entry) {
final index = entry.key;
final item = entry.value;
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(item['icon'] as IconData, size: 24),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item['name'] as String,
style: const TextStyle(
fontSize: 14, fontWeight: FontWeight.w500)),
Text(_formatSize(item['size'] as double),
style: const TextStyle(
fontSize: 11, color: Color(0xFF8E8E93))),
],
),
),
Checkbox(
value: item['checked'] as bool,
activeColor: const Color(0xFFFF9500),
onChanged: (v) =>
setState(() => _junkItems[index]['checked'] = v),
),
],
),
);
}),
],
);
}
Widget _buildCleanButton() {
return Center(
child: ElevatedButton(
onPressed: _startClean,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFF9500),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
padding: const EdgeInsets.symmetric(horizontal: 48, vertical: 12),
),
child: Text('立即清理 (${_formatSize(_checkedSize)})',
style: const TextStyle(fontSize: 16)),
),
);
}
Widget _buildCleaningProgress() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: _cleanProgress / 100,
backgroundColor: const Color(0xFFE5E5EA),
valueColor:
const AlwaysStoppedAnimation<Color>(Color(0xFF34C759)),
minHeight: 6,
),
),
const SizedBox(height: 8),
Text('正在清理中... ${_cleanProgress.toInt()}%',
style: const TextStyle(fontSize: 13, color: Color(0xFF8E8E93))),
],
),
);
}
Widget _buildCleanResult() {
return Center(
child: Column(
children: [
const SizedBox(height: 24),
const Icon(Icons.check_circle, size: 56, color: Color(0xFF34C759)),
const SizedBox(height: 8),
const Text('清理完成',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF34C759))),
const SizedBox(height: 8),
Text('已释放 ${_formatSize(_checkedSize)} 存储空间',
style: const TextStyle(fontSize: 14, color: Color(0xFF8E8E93))),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _resetAll,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF007AFF),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
),
child: const Text('重新扫描'),
),
],
),
);
}
}
七、支付环境检测模块
支付环境检测是安全中心的核心功能之一,它对系统环境、网络环境、应用完整性、恶意软件、剪贴板安全、屏幕录制、VPN代理和证书安全八个维度进行全面检测,确保用户在安全的支付环境中进行交易。
class PaymentCheckPage extends StatefulWidget {
const PaymentCheckPage({super.key});
State<PaymentCheckPage> createState() => _PaymentCheckPageState();
}
class _PaymentCheckPageState extends State<PaymentCheckPage> {
bool _isChecking = false;
double _checkProgress = 0;
bool _checkComplete = false;
bool _isEnvironmentSafe = true;
final List<Map<String, dynamic>> _checkItems = [
{'name': '系统环境检测', 'icon': Icons.computer, 'status': 'waiting', 'result': '', 'desc': '检测系统是否被Root/越狱'},
{'name': '网络环境检测', 'icon': Icons.wifi, 'status': 'waiting', 'result': '', 'desc': '检测WiFi安全性及DNS劫持'},
{'name': '应用完整性检测', 'icon': Icons.verified, 'status': 'waiting', 'result': '', 'desc': '检测支付应用是否为正版'},
{'name': '恶意软件检测', 'icon': Icons.bug_report, 'status': 'waiting', 'result': '', 'desc': '检测是否存在键盘记录器等恶意软件'},
{'name': '剪贴板安全检测', 'icon': Icons.content_paste, 'status': 'waiting', 'result': '', 'desc': '检测剪贴板是否被监控'},
{'name': '屏幕录制检测', 'icon': Icons.videocam, 'status': 'waiting', 'result': '', 'desc': '检测是否有应用在录屏'},
{'name': 'VPN代理检测', 'icon': Icons.vpn_lock, 'status': 'waiting', 'result': '', 'desc': '检测是否存在可疑代理或VPN'},
{'name': '证书安全检测', 'icon': Icons.verified_user, 'status': 'waiting', 'result': '', 'desc': '检测是否存在可疑安全证书'},
];
void _startCheck() {
setState(() {
_isChecking = true;
_checkProgress = 0;
_checkComplete = false;
_isEnvironmentSafe = true;
for (var item in _checkItems) {
item['status'] = 'waiting';
item['result'] = '';
}
});
_runCheckSequence(0);
}
void _runCheckSequence(int index) {
if (index >= _checkItems.length) {
setState(() {
_checkProgress = 100;
_isChecking = false;
_checkComplete = true;
});
return;
}
setState(() => _checkItems[index]['status'] = 'checking');
Timer(const Duration(milliseconds: 500), () {
final isSafe = DateTime.now().millisecond % 10 > 1;
setState(() {
_checkItems[index]['result'] = isSafe ? '安全' : '风险';
_checkItems[index]['status'] = 'done';
if (!isSafe) _isEnvironmentSafe = false;
_checkProgress = ((index + 1) / _checkItems.length) * 100;
});
_runCheckSequence(index + 1);
});
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
appBar: AppBar(
title: const Text('支付环境检测'),
backgroundColor: Colors.white,
foregroundColor: const Color(0xFF1A1A1A),
elevation: 0,
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildStatusHeader(),
if (!_checkComplete && !_isChecking) _buildStartButton(),
if (_isChecking) _buildProgressBar(),
if (_checkComplete) _buildResultSummary(),
const SizedBox(height: 16),
_buildCheckItemList(),
if (_checkComplete) _buildSafetyTips(),
],
),
);
}
Widget _buildStatusHeader() {
return Center(
child: Column(
children: [
const SizedBox(height: 24),
Icon(
_checkComplete
? (_isEnvironmentSafe ? Icons.shield : Icons.warning_amber)
: Icons.credit_card,
size: 56,
color: _checkComplete
? (_isEnvironmentSafe
? const Color(0xFF34C759)
: const Color(0xFFFF3B30))
: const Color(0xFF007AFF),
),
const SizedBox(height: 8),
Text(
_checkComplete
? (_isEnvironmentSafe ? '支付环境安全' : '检测到风险')
: '支付环境检测',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: _checkComplete
? (_isEnvironmentSafe
? const Color(0xFF34C759)
: const Color(0xFFFF3B30))
: const Color(0xFF1A1A1A),
),
),
],
),
);
}
Widget _buildStartButton() {
return Center(
child: ElevatedButton(
onPressed: _startCheck,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF007AFF),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
padding: const EdgeInsets.symmetric(horizontal: 48, vertical: 12),
),
child: const Text('开始检测', style: TextStyle(fontSize: 16)),
),
);
}
Widget _buildProgressBar() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: _checkProgress / 100,
backgroundColor: const Color(0xFFE5E5EA),
valueColor:
const AlwaysStoppedAnimation<Color>(Color(0xFF007AFF)),
minHeight: 6,
),
),
const SizedBox(height: 8),
Text('正在检测支付环境... ${_checkProgress.toInt()}%',
style: const TextStyle(fontSize: 13, color: Color(0xFF8E8E93))),
],
),
);
}
Widget _buildResultSummary() {
final passed = _checkItems.where((i) => i['result'] == '安全').length;
final danger = _checkItems.where((i) => i['result'] == '风险').length;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
_buildStatCard('$passed', '通过', const Color(0xFF34C759)),
_buildStatCard('0', '警告', const Color(0xFFFF9500)),
_buildStatCard('$danger', '风险', const Color(0xFFFF3B30)),
],
),
);
}
Widget _buildStatCard(String value, String label, Color color) {
return Expanded(
child: Column(
children: [
Text(value,
style: TextStyle(
fontSize: 22, fontWeight: FontWeight.bold, color: color)),
Text(label,
style:
const TextStyle(fontSize: 11, color: Color(0xFF8E8E93))),
],
),
);
}
Widget _buildCheckItemList() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('检测项目',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFF1A1A1A))),
const SizedBox(height: 8),
..._checkItems.map((item) => Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(item['icon'] as IconData, size: 24),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item['name'] as String,
style: const TextStyle(
fontSize: 14, fontWeight: FontWeight.w500)),
Text(item['desc'] as String,
style: const TextStyle(
fontSize: 11, color: Color(0xFF8E8E93))),
],
),
),
_buildStatusWidget(item['status'] as String, item['result'] as String),
],
),
)),
],
);
}
Widget _buildStatusWidget(String status, String result) {
switch (status) {
case 'waiting':
return const Text('等待中',
style: TextStyle(fontSize: 12, color: Color(0xFF8E8E93)));
case 'checking':
return const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
);
case 'done':
return Text(
result,
style: TextStyle(
fontSize: 12,
color: result == '安全' ? const Color(0xFF34C759) : const Color(0xFFFF3B30),
),
);
default:
return const SizedBox();
}
}
Widget _buildSafetyTips() {
return Padding(
padding: const EdgeInsets.only(top: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('💡 支付安全提示',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Color(0xFF1A1A1A))),
const SizedBox(height: 8),
...const [
'• 不要在公共WiFi下进行支付操作',
'• 定期检查支付应用是否为最新版本',
'• 开启支付应用的双重认证功能',
'• 支付前建议进行一次环境安全检测',
].map((tip) => Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Text(tip,
style: const TextStyle(
fontSize: 12, color: Color(0xFF8E8E93))),
)),
],
),
);
}
}
八、运行截图
以下为安全中心应用在鸿蒙设备上的实际运行效果截图:
首页仪表盘
手机体检
病毒扫描
垃圾清理
应用锁
隐私保护报告
支付环境检测
流量防盗监控
安全加速优化
九、关键技术点总结
1. 状态管理
本应用采用 Flutter 原生的 StatefulWidget + setState 进行状态管理。对于安全中心这类中等复杂度的应用,这种方式足够清晰且易于维护。每个功能页面独立管理自己的扫描/检测状态,页面间通过 Navigator.push 传递数据。
2. 自定义绘制
评分环形图使用 CustomPainter 实现,通过 Canvas.drawArc 绘制进度弧线。这种方式比使用第三方图表库更加轻量,且能完全控制绘制细节,适配鸿蒙设备的渲染管线。
3. 异步扫描模拟
各模块的逐项扫描效果通过 Timer 配合递归调用实现。在实际生产环境中,这部分应替换为真实的系统 API 调用。Flutter for OpenHarmony 提供了 MethodChannel 用于与鸿蒙原生侧通信,可以调用 OpenHarmony 的系统能力 API。
4. 跨平台适配
得益于 Flutter 的跨平台特性,本文中的所有 Dart 代码无需修改即可在 Android、iOS 和 OpenHarmony 三个平台上运行。开发者只需关注业务逻辑,UI 渲染由 Flutter 引擎统一处理。
十、结语
本文从零开始,使用 Flutter for OpenHarmony 构建了一款功能完备的安全中心应用,涵盖了从 UI 布局、自定义绘制、状态管理到异步流程控制的完整开发流程。通过本文的实践,读者可以掌握 Flutter 在鸿蒙设备上的核心开发技巧,并将其应用到自己的项目中。
Flutter for OpenHarmony 正在快速发展,越来越多的 Flutter 生态包正在完成鸿蒙化适配。希望本文能为正在探索鸿蒙跨平台开发的你提供一些实用的参考。
完整项目源码已托管在 AtomGit:https://atomgit.com,欢迎交流讨论。
更多推荐













所有评论(0)