Loading... ## 写在前面 ### 成品展示  ### 相关依赖 * get * local_auth * shared_preferences ## 组件代码 这里使用 `getx` 作为状态管理,`shared_preferences` 持久化数据,`local_auth` 库用于调用系统的生物识别功能,比如 `Android` 平台的指纹识别,`IOS` 平台的 Face ID 等。如果没有需求可以不依赖。 <div class="tab-container post_tab box-shadow-wrap-lg"> <ul class="nav no-padder b-b scroll-hide" role="tablist"> <li class='nav-item active' role="presentation"><a class='nav-link active' style="" data-toggle="tab" aria-controls='tabs-26a886da55905960e3db2c6c6f988ebf30' role="tab" data-target='#tabs-26a886da55905960e3db2c6c6f988ebf30'>View</a></li><li class='nav-item ' role="presentation"><a class='nav-link ' style="" data-toggle="tab" aria-controls='tabs-fa386fa25abdb2142cc9576ec91a8398371' role="tab" data-target='#tabs-fa386fa25abdb2142cc9576ec91a8398371'>State</a></li><li class='nav-item ' role="presentation"><a class='nav-link ' style="" data-toggle="tab" aria-controls='tabs-9d317f3cf9bbda9ad3f0268a0174d9c2202' role="tab" data-target='#tabs-9d317f3cf9bbda9ad3f0268a0174d9c2202'>Logic</a></li> </ul> <div class="tab-content no-border"> <div role="tabpanel" id='tabs-26a886da55905960e3db2c6c6f988ebf30' class="tab-pane fade active in"> ```dart import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:mood_diary/utils/utils.dart'; import 'lock_logic.dart'; class LockPage extends StatelessWidget { const LockPage({super.key}); @override Widget build(BuildContext context) { final logic = Bind.find<LockLogic>(); final state = Bind.find<LockLogic>().state; final colorScheme = Theme.of(context).colorScheme; final textStyle = Theme.of(context).textTheme; final buttonSize = (textStyle.displayLarge!.fontSize! * textStyle.displayLarge!.height!); Widget buildNumButton(String num) { return Ink( decoration: BoxDecoration(color: colorScheme.surfaceContainerHighest, shape: BoxShape.circle), child: InkWell( borderRadius: BorderRadius.all(Radius.circular(buttonSize / 2)), onTap: () async { await logic.updatePassword(num); }, child: Center( child: Text( num, style: textStyle.displaySmall, )), ), ); } Widget buildDeleteButton() { return Ink( decoration: const BoxDecoration(shape: BoxShape.circle), child: InkWell( borderRadius: BorderRadius.all(Radius.circular(buttonSize / 2)), onTap: () { logic.deletePassword(); }, child: const Icon( Icons.backspace, ), ), ); } Widget buildBiometricsButton() { return Ink( decoration: const BoxDecoration(shape: BoxShape.circle), child: InkWell( borderRadius: BorderRadius.all(Radius.circular(buttonSize / 2)), onTap: () async { if (await Utils().authUtil.check()) { logic.checked(); } }, child: const Icon( Icons.fingerprint, ), ), ); } List<Widget> buildPasswordIndicator() { return List.generate(4, (index) { return Obx(() { return Icon( Icons.circle, size: 16, color: Color.lerp( state.password.value.length > index ? colorScheme.onSurface : colorScheme.surfaceContainerHighest, Colors.red, logic.animation.value), ); }); }); } return GetBuilder<LockLogic>( init: logic, assignId: true, builder: (logic) { return PopScope( canPop: false, child: Scaffold( appBar: AppBar( automaticallyImplyLeading: false, ), body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.lock), const SizedBox( height: 16.0, ), Text( '请输入密码', style: textStyle.titleMedium, ), const SizedBox( height: 16.0, ), AnimatedBuilder( animation: logic.animation, builder: (context, child) { return Transform.translate( offset: Offset(logic.interpolate(logic.animation.value), 0), child: Wrap( spacing: 16.0, children: buildPasswordIndicator(), ), ); }, ), const SizedBox( height: 32.0, ), SizedBox( width: buttonSize * 3 + 20, height: buttonSize * 4 + 30, child: GridView.count( crossAxisCount: 3, childAspectRatio: 1.0, crossAxisSpacing: 10, mainAxisSpacing: 10, children: [ buildNumButton('1'), buildNumButton('2'), buildNumButton('3'), buildNumButton('4'), buildNumButton('5'), buildNumButton('6'), buildNumButton('7'), buildNumButton('8'), buildNumButton('9'), buildBiometricsButton(), buildNumButton('0'), buildDeleteButton() ], ), ), ], ), ), ), ); }, ); } } ``` </div><div role="tabpanel" id='tabs-fa386fa25abdb2142cc9576ec91a8398371' class="tab-pane fade "> ```dart import 'package:get/get.dart'; import 'package:mood_diary/utils/utils.dart'; class LockState { late RxString password; late RxString realPassword; //锁定类型,是立即锁定导致,还是启动锁定导致 late String? lockType; LockState() { password = ''.obs; realPassword = Utils().prefUtil.getValue<String>('password')!.obs; lockType = Get.arguments; ///Initialize variables } } ``` </div><div role="tabpanel" id='tabs-9d317f3cf9bbda9ad3f0268a0174d9c2202' class="tab-pane fade "> ```dart import 'package:flutter/animation.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:mood_diary/router/app_routes.dart'; import 'lock_state.dart'; class LockLogic extends GetxController with GetSingleTickerProviderStateMixin { final LockState state = LockState(); late AnimationController animationController = AnimationController(vsync: this, duration: const Duration(milliseconds: 200)); late Animation<double> animation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: animationController, curve: Curves.easeInOut)); @override void onReady() { // TODO: implement onReady super.onReady(); } @override void onClose() { // TODO: implement onClose animationController.dispose(); super.onClose(); } double interpolate(double x) { var step = 10.0; if (x <= 0.25) { return 4 * step * x; } else if (x <= 0.75) { return step - 4 * step * (x - 0.25); } else { return -step + 4 * step * (x - 0.75); } } void deletePassword() { if (state.password.value.isNotEmpty) { state.password.value = state.password.value.substring(0, state.password.value.length - 1); HapticFeedback.selectionClick(); } } Future<void> updatePassword(String value) async { if (state.password.value.length < 4) { state.password.value += value; HapticFeedback.selectionClick(); } Future.delayed(const Duration(milliseconds: 100), () async { if (state.password.value.length == 4) { //密码正确 if (state.password.value == state.realPassword.value) { checked(); } else { animationController.forward(); await HapticFeedback.mediumImpact(); Future.delayed(const Duration(milliseconds: 200), () { animationController.reverse(); state.password.value = ''; }); } } }); } void checked() { //如果是启动时的锁定,就跳转到homepage if (state.lockType == null) { Get.offAllNamed(AppRoutes.homePage); } //如果是开启立即锁定后生命周期变化导致的锁定 if (state.lockType == 'pause') { Get.backLegacy(); } } } ``` </div> </div> </div> ## 其他 如果要实现软件离开后自动锁定的功能,可以在一个不会被销毁的页面比如主页,引入 `AppLifecycleListener` 来监听生命周期变化,当生命周期变化时自动跳转到锁屏页面,这样就可以实现自动锁屏的功能,需要注意的是,由于使用过程中导致的锁屏需要保存路由状态,所以要防止用户通过返回的防止返回上一个路由,可以使用 `PopScope` 包裹锁屏页面,拦截返回操作。 如果引入 `local_auth` 包使用生物识别,可能需要修改弹窗内容,因为调用的是系统底层的弹窗而不是来自于 Flutter,如果不修改弹窗内容,可能会是英文的界面。 以安卓平台为例,修改弹窗内容。 ```dart //生物识别 Future<bool> check() async { return await _authentication.authenticate( authMessages: [ const AndroidAuthMessages( biometricHint: "", biometricNotRecognized: "验证失败", biometricSuccess: "验证成功", cancelButton: "取消", goToSettingsButton: "设置", goToSettingsDescription: "请先在系统中开启指纹", signInTitle: "扫描您的指纹以继续", ) ], localizedReason: '安全验证', options: const AuthenticationOptions( useErrorDialogs: true, stickyAuth: true, sensitiveTransaction: true, biometricOnly: true, ), ); } ``` © 允许规范转载 打赏 赞赏作者 微信 赞