From 2e438aa089284881cf1ab63de4fa76cf3762351a Mon Sep 17 00:00:00 2001 From: lxh2875931338 Date: Mon, 20 Jul 2026 23:57:01 +0800 Subject: [PATCH] =?UTF-8?q?=E9=92=88=E5=AF=B9readme=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=81=9A=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96=EF=BC=8C=E7=9B=AE?= =?UTF-8?q?=E6=A0=87=E5=B8=A7=E7=8E=87120=E8=BE=BE=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/lxh/battery_health/MainActivity.kt | 48 ++++- lib/pages/changelog_page.dart | 164 ++++++++++++++---- 2 files changed, 181 insertions(+), 31 deletions(-) diff --git a/android/app/src/main/kotlin/com/lxh/battery_health/MainActivity.kt b/android/app/src/main/kotlin/com/lxh/battery_health/MainActivity.kt index 2005305..c677d9e 100644 --- a/android/app/src/main/kotlin/com/lxh/battery_health/MainActivity.kt +++ b/android/app/src/main/kotlin/com/lxh/battery_health/MainActivity.kt @@ -11,6 +11,7 @@ import android.os.Build import android.os.Bundle import android.provider.DocumentsContract import android.provider.OpenableColumns +import android.view.Window // 🟢 必须包含 import androidx.activity.ComponentActivity import androidx.annotation.NonNull import io.flutter.embedding.android.FlutterActivity @@ -32,6 +33,9 @@ class MainActivity : FlutterActivity() { private val PREF_NAME = "battery_health_prefs" private val KEY_SAF_URI = "saf_uri" + // 🟢 刷新率控制通道 + private val FRAME_RATE_CHANNEL = "com.lxh.battery_health/frame_rate" + private val chargeReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { intent ?: return @@ -74,7 +78,6 @@ class MainActivity : FlutterActivity() { try { safUri = Uri.parse(uriString) android.util.Log.d("BatteryHealth", "通过 MethodChannel 恢复 URI: $safUri") - // 同时保存到 SharedPreferences val prefs: SharedPreferences = getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) prefs.edit().putString(KEY_SAF_URI, uriString).apply() } catch (e: Exception) { @@ -222,9 +225,34 @@ class MainActivity : FlutterActivity() { return fileUri?.let { readFile(it) } } +// 🟢 刷新率控制方法 +// 🟢 刷新率控制方法(使用 preferredRefreshRate,兼容性更好) +private fun setHighRefreshRate() { + try { + val params = window.attributes + params.preferredRefreshRate = 120f + window.attributes = params + android.util.Log.d("BatteryHealth", "setHighRefreshRate: 设置 120Hz") + } catch (e: Exception) { + android.util.Log.e("BatteryHealth", "setHighRefreshRate 失败: ${e.message}") + } +} + +private fun clearRefreshRate() { + try { + val params = window.attributes + params.preferredRefreshRate = 0f // 0 表示让系统自动决定 + window.attributes = params + android.util.Log.d("BatteryHealth", "clearRefreshRate: 恢复默认") + } catch (e: Exception) { + android.util.Log.e("BatteryHealth", "clearRefreshRate 失败: ${e.message}") + } +} + override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) + // 充电状态通道 MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHARGE_CHANNEL) .setMethodCallHandler { call, result -> if (call.method == "getChargeState") { @@ -247,6 +275,7 @@ class MainActivity : FlutterActivity() { } } + // SAF 通道 MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SAF_CHANNEL) .setMethodCallHandler { call, result -> when (call.method) { @@ -283,6 +312,23 @@ class MainActivity : FlutterActivity() { } } + // 🟢 刷新率控制通道 + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, FRAME_RATE_CHANNEL) + .setMethodCallHandler { call, result -> + when (call.method) { + "setHighRefreshRate" -> { + setHighRefreshRate() + result.success(true) + } + "clearRefreshRate" -> { + clearRefreshRate() + result.success(true) + } + else -> result.notImplemented() + } + } + + // 事件通道 EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL) .setStreamHandler( object : EventChannel.StreamHandler { diff --git a/lib/pages/changelog_page.dart b/lib/pages/changelog_page.dart index 6866470..1ed2fdf 100644 --- a/lib/pages/changelog_page.dart +++ b/lib/pages/changelog_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; // 🟢 添加这一行 import 'package:flutter/services.dart' show rootBundle; import 'package:flutter_markdown/flutter_markdown.dart'; @@ -9,15 +10,85 @@ class ChangelogPage extends StatefulWidget { State createState() => _ChangelogPageState(); } -class _ChangelogPageState extends State { +class _ChangelogPageState extends State + with SingleTickerProviderStateMixin { + // ---------- 数据 ---------- String _markdownContent = ''; bool _isLoading = true; String? _error; + Widget? _cachedMarkdown; + // ---------- 滚动 ---------- + final ScrollController _scrollController = ScrollController(); + double _scrollProgress = 0.0; + bool _isScrolling = false; + + // ---------- 呼吸动画(维持高刷感知) ---------- + late AnimationController _breathController; + late Animation _breathAnimation; + + // ---------- 原生刷新率控制 ---------- + // 🟢 改用 static final(不是 const) + static final _frameRateChannel = MethodChannel( + 'com.lxh.battery_health/frame_rate', + ); + + // ---------- 生命周期 ---------- @override void initState() { super.initState(); _loadChangelog(); + + // 进入页面时申请高刷(原生层) + _frameRateChannel.invokeMethod('setHighRefreshRate'); + + // 呼吸动画:让进度条在滚动时轻微抖动,维持高刷感知 + _breathController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 500), + )..repeat(reverse: true); + + _breathAnimation = Tween(begin: 0.0, end: 0.03).animate( + CurvedAnimation(parent: _breathController, curve: Curves.easeInOut), + ); + + _scrollController.addListener(() { + _updateProgress(); + final isScrolling = _scrollController.position.isScrollingNotifier.value; + if (isScrolling != _isScrolling) { + setState(() { + _isScrolling = isScrolling; + }); + if (isScrolling) { + _breathController.repeat(reverse: true); + } else { + _breathController.stop(); + _breathController.reset(); + } + } + }); + } + + @override + void dispose() { + // 离开页面时恢复默认刷新率 + _frameRateChannel.invokeMethod('clearRefreshRate'); + + _scrollController.removeListener(_updateProgress); + _scrollController.dispose(); + _breathController.dispose(); + super.dispose(); + } + + // ---------- 方法 ---------- + void _updateProgress() { + if (!_scrollController.hasClients) return; + final maxScroll = _scrollController.position.maxScrollExtent; + final currentScroll = _scrollController.position.pixels; + final progress = maxScroll > 0 ? currentScroll / maxScroll : 0.0; + setState(() { + _scrollProgress = progress; + }); } Future _loadChangelog() async { @@ -26,6 +97,7 @@ class _ChangelogPageState extends State { setState(() { _markdownContent = content; _isLoading = false; + _cachedMarkdown = _buildMarkdown(content); }); } catch (e) { setState(() { @@ -35,8 +107,56 @@ class _ChangelogPageState extends State { } } + Widget _buildMarkdown(String content) { + return Markdown( + data: content, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + styleSheet: MarkdownStyleSheet( + h1: const TextStyle( + fontFamily: 'HarmonyOS_Sans_SC', + fontSize: 24, + fontWeight: FontWeight.w700, + color: Colors.black, + ), + h2: const TextStyle( + fontFamily: 'HarmonyOS_Sans_SC', + fontSize: 20, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + h3: const TextStyle( + fontFamily: 'HarmonyOS_Sans_SC', + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + p: const TextStyle( + fontFamily: 'HarmonyOS_Sans_SC', + fontSize: 16, + color: Colors.black87, + ), + listBullet: const TextStyle( + fontFamily: 'HarmonyOS_Sans_SC', + fontSize: 16, + color: Colors.black87, + ), + code: const TextStyle( + fontFamily: 'HarmonyOS_Sans_SC', + fontSize: 14, + color: Colors.black87, + backgroundColor: Color(0xFFF5F5F5), + ), + ), + ); + } + + // ---------- Build ---------- @override Widget build(BuildContext context) { + final breathOffset = _isScrolling ? _breathAnimation.value : 0.0; + final displayProgress = (_scrollProgress + breathOffset).clamp(0.0, 1.0); + return Scaffold( backgroundColor: Colors.white, appBar: AppBar( @@ -51,6 +171,17 @@ class _ChangelogPageState extends State { elevation: 0, foregroundColor: Colors.black, iconTheme: const IconThemeData(color: Colors.black), + bottom: PreferredSize( + preferredSize: const Size.fromHeight(2.0), + child: LinearProgressIndicator( + value: displayProgress, + backgroundColor: Colors.grey.shade200, + valueColor: const AlwaysStoppedAnimation( + Color.fromRGBO(157, 212, 237, 1.0), + ), + minHeight: 2.0, + ), + ), ), body: _isLoading ? const Center(child: CircularProgressIndicator()) @@ -65,36 +196,9 @@ class _ChangelogPageState extends State { ), ) : SingleChildScrollView( + controller: _scrollController, padding: const EdgeInsets.all(16.0), - child: Markdown( - data: _markdownContent, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - styleSheet: MarkdownStyleSheet( - h1: const TextStyle( - fontFamily: 'HarmonyOS_Sans_SC', - fontSize: 24, - fontWeight: FontWeight.w700, - color: Colors.black, - ), - h2: const TextStyle( - fontFamily: 'HarmonyOS_Sans_SC', - fontSize: 20, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), - p: const TextStyle( - fontFamily: 'HarmonyOS_Sans_SC', - fontSize: 16, - color: Colors.black87, - ), - listBullet: const TextStyle( - fontFamily: 'HarmonyOS_Sans_SC', - fontSize: 16, - color: Colors.black87, - ), - ), - ), + child: _cachedMarkdown ?? const SizedBox.shrink(), ), ); }