【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
章节内容【08】
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-卓伊凡换人优雅草Alex
开发背景
由于卓伊凡工作实在太多,工作繁忙且卓伊凡每天晚上还要直播,因此本项目已前端部分转交优雅草Alex继续并且更新-为了保证每日更新
优雅草Alex 【高级全栈开发工程师-任职世界500强企业-月薪30k+】接下来就看这位大佬的表演吧
经典案例-在优雅草手撸加速器-win-mac-安卓+苹果 4端【仅用了一个月时间】
上篇我们做了自定义组件,本文继续完善注册相关页面并且实现跳转
闲话不多,开源仓库地址,可以观摩已经写好的代码:
https://gitee.com/youyacao/ff-flutter
demo下载
https://www.youyacao.cn/freefirend
更新代码文件和日志文件-gitee可见
·更新了getx路由
·增加了屏幕适配
·基础导航栏开发处理
·重建了Android
·布局规划了包含注册,直播,其他等页面框架
·整体处理
remote: Enumerating objects: 98, done. remote: Counting objects: 100% (94/94), done. remote: Compressing objects: 100% (50/50), done. remote: Total 62 (delta 19), reused 13 (delta 0), pack-reused 0 Unpacking objects: 100% (62/62), 18.63 KiB | 141.00 KiB/s, done. From https://gitee.com/youyacao/ff-flutter 2d36d20..71239bb master -> origin/master Updating 2d36d20..71239bb Fast-forward .cursorrules | 130 +++++++++++ .fvmrc | 3 + .gitignore | 3 + .metadata | 30 +-- .vscode/launch.json | 6 +- .vscode/settings.json | 3 + android/.gitignore | 2 +- android/app/build.gradle | 53 +++-- android/app/src/main/AndroidManifest.xml | 5 +- .../{freefirend => ff_flutter}/MainActivity.kt | 2 +- .../app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 1508805 -> 544 bytes .../app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 1508805 -> 442 bytes .../app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 1508805 -> 721 bytes .../app/src/main/res/mipmap-xxhdpi/ic_launcher.png | Bin 1508805 -> 1031 bytes .../src/main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 1508805 -> 1443 bytes android/build.gradle | 4 +- android/gradle.properties | 2 +- android/gradle/wrapper/gradle-wrapper.properties | 2 +- android/settings.gradle | 9 +- lib/controllers/index_controller.dart | 5 + lib/controllers/register_controller.dart | 14 ++ lib/controllers/sms_login_controller.dart | 5 + lib/main.dart | 34 +-- lib/routes/app_pages.dart | 38 +++ lib/routes/app_routes.dart | 5 + lib/screens/account_screen.dart | 16 ++ lib/screens/home_screen.dart | 254 ++++++++++++++++++++ lib/screens/index.dart | 259 ++++++++------------- lib/screens/message_screen.dart | 16 ++ lib/screens/register.dart | 109 +++++---- lib/screens/short_video_screen.dart | 16 ++ linux/main.cc | 6 + linux/my_application.cc | 124 ++++++++++ linux/my_application.h | 18 ++ pubspec.lock | 66 ++++-- pubspec.yaml | 2 + test/widget_test.dart | 30 +++ 37 files changed, 972 insertions(+), 299 deletions(-) create mode 100644 .cursorrules create mode 100644 .fvmrc create mode 100644 .vscode/settings.json rename android/app/src/main/kotlin/com/example/{freefirend => ff_flutter}/MainActivity.kt (74%) create mode 100644 lib/controllers/index_controller.dart create mode 100644 lib/controllers/register_controller.dart create mode 100644 lib/controllers/sms_login_controller.dart create mode 100644 lib/routes/app_pages.dart create mode 100644 lib/routes/app_routes.dart create mode 100644 lib/screens/account_screen.dart create mode 100644 lib/screens/home_screen.dart create mode 100644 lib/screens/message_screen.dart create mode 100644 lib/screens/short_video_screen.dart create mode 100644 linux/main.cc create mode 100644 linux/my_application.cc create mode 100644 linux/my_application.h create mode 100644 test/widget_test.dart
实战开始
先打包个,flutter build apk 运行, 报错
先运行下看看
可以成功运行,那么已经可以确定肯定是gradle 打包问题了,解决打包问题前我们已经看看更新的内容吧,
import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:ff_flutter/routes/app_pages.dart'; import 'package:ff_flutter/routes/app_routes.dart'; void main() { // 初始化日志记录器 Logger.root.level = Level.ALL; Logger.root.onRecord.listen((record) { debugPrint('${record.level.name}: ${record.time}: ${record.message}'); }); runApp(const MainApp()); } class MainApp extends StatelessWidget { const MainApp({super.key}); @override Widget build(BuildContext context) { return ScreenUtilInit( designSize: const Size(750, 1624), // 设置设计稿尺寸 minTextAdapt: true, splitScreenMode: true, builder: (context, child) { return GetMaterialApp( debugShowCheckedModeBanner: false, title: 'freefirend', theme: ThemeData( primarySwatch: Colors.blue, ), initialRoute: AppRoutes.INDEX, getPages: AppPages.pages, ); }); } }
从入口文件看到 更新了 路由app_routes.dart,更新了要么框架 app_pages.dart
页面框架代码:
import 'package:get/get.dart'; import 'package:ff_flutter/screens/index.dart'; import 'package:ff_flutter/screens/register.dart'; import 'package:ff_flutter/screens/smslogin.dart' as sms; import 'package:ff_flutter/screens/home_screen.dart'; import 'package:ff_flutter/screens/short_video_screen.dart'; import 'package:ff_flutter/screens/message_screen.dart'; import 'package:ff_flutter/screens/account_screen.dart'; import 'package:ff_flutter/routes/app_routes.dart'; import 'package:ff_flutter/controllers/index_controller.dart'; import 'package:ff_flutter/controllers/register_controller.dart'; import 'package:ff_flutter/controllers/sms_login_controller.dart'; class AppPages { static final pages = [ GetPage( name: AppRoutes.INDEX, page: () => IndexScreen(), binding: BindingsBuilder(() { Get.lazyPut(() => IndexController()); }), ), GetPage( name: AppRoutes.REGISTER, page: () => const RegisterScreen(), binding: BindingsBuilder(() { Get.lazyPut(() => RegisterController()); }), ), GetPage( name: AppRoutes.SMS_LOGIN, page: () => const sms.SmsLoginScreen(), binding: BindingsBuilder(() { Get.lazyPut(() => SmsLoginController()); }), ), ]; }
路由文件代码,
abstract class AppRoutes { static const INDEX = '/'; static const REGISTER = '/register'; static const SMS_LOGIN = '/sms-login'; }
创建了 control 文件,里面对页面进行了方法的文件创建和默认的内容,诸如注册的调用
import 'package:get/get.dart'; class RegisterController extends GetxController { final RxBool agreedToTerms = false.obs; final RxString selectedCountryCode = '+1'.obs; void updateAgreedToTerms(bool value) { agreedToTerms.value = value; } void updateSelectedCountryCode(String value) { selectedCountryCode.value = value; } }
这段代码定义了一个名为 RegisterController
的类,继承自 GetxController
。它包含两个可观察变量:agreedToTerms
表示用户是否同意条款,默认为 false
;selectedCountryCode
表示选择的国家代码,默认为 +1
。还提供了两个方法用于更新这些变量的值。
控制流图
mermaid
flowchart TD A[初始化 RegisterController] --> B{更新 agreedToTerms} B -->|调用 updateAgreedToTerms| C[设置 agreedToTerms 值] A --> D{更新 selectedCountryCode} D -->|调用 updateSelectedCountryCode| E[设置 selectedCountryCode 值]
整体主要是对框架进行了适配,此刻我发现同事把我打包图标默认的默认被换了,因此我建立APP打包的图标自定义
找到原图标,下载并保存
修改pubspec.yaml, 加入自定义图标代码
flutter_launcher_icons: image_path: "assets/icon/logo.png" # 指定用于生成图标的源图像路径 android: true # 表示要为 Android 平台生成图标 ios: true # 表示要为 iOS 平台生成图标 remove_alpha_ios: true # 移除 iOS 图标中的透明通道
在资源目录加入assets\icons中logo.png文件
再来看看index.dart中对导航栏的书写,
import 'package:flutter/material.dart'; import 'package:ff_flutter/screens/register.dart'; // 导入 register.dart 文件 import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:ff_flutter/routes/app_routes.dart'; import 'package:ff_flutter/screens/home_screen.dart'; import 'package:ff_flutter/screens/short_video_screen.dart'; import 'package:ff_flutter/screens/message_screen.dart'; import 'package:ff_flutter/screens/account_screen.dart'; class IndexScreen extends StatefulWidget { // 改为 StatefulWidget @override State<IndexScreen> createState() => _IndexScreenState(); } class _IndexScreenState extends State<IndexScreen> { int _selectedIndex = 0; final List<Widget> _pages = [ HomeScreen(), ShortVideoScreen(), MessageScreen(), AccountScreen(), ]; void _onItemTapped(int index) { setState(() { _selectedIndex = index; }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFF1E1E1E), body: IndexedStack( index: _selectedIndex, children: _pages, ), bottomNavigationBar: Container( height: 168.h, decoration: const BoxDecoration( color: Color(0xFF1E1E1E), ), child: Theme( data: ThemeData( splashColor: Colors.transparent, highlightColor: Colors.transparent, ), child: BottomNavigationBar( items: const <BottomNavigationBarItem>[ BottomNavigationBarItem( icon: Icon(Icons.home), label: 'Home', activeIcon: Icon(Icons.home, color: Color(0xFFE7568C)), ), BottomNavigationBarItem( icon: Icon(Icons.play_circle_outline), label: 'Short Video', activeIcon: Icon(Icons.play_circle_outline, color: Color(0xFFE7568C)), ), BottomNavigationBarItem( icon: Icon(Icons.notifications_none), label: 'Message', activeIcon: Icon(Icons.notifications_none, color: Color(0xFFE7568C)), ), BottomNavigationBarItem( icon: Icon(Icons.person_outline), label: 'Account', activeIcon: Icon(Icons.person_outline, color: Color(0xFFE7568C)), ), ], currentIndex: _selectedIndex, selectedItemColor: const Color(0xFFE7568C), unselectedItemColor: Colors.grey, backgroundColor: const Color(0xFF1E1E1E), type: BottomNavigationBarType.fixed, selectedFontSize: 24.sp, unselectedFontSize: 24.sp, iconSize: 48.sp, elevation: 0, onTap: _onItemTapped, ), ), ), ); } } class DownloadButton extends StatelessWidget { @override Widget build(BuildContext context) { return ElevatedButton.icon( onPressed: () { // 处理下载逻辑 }, icon: const Icon( Icons.system_update_alt, size: 30, color: Color(0xfff1f1f1), // 设置图标颜色为 0xfff1f1f1 ), label: const Text( "Download", style: TextStyle( color: Color(0xfff1f1f1), fontSize: 26, fontFamily: "PingFang SC", fontWeight: FontWeight.w800, ), ), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xffe7568c), // 使用 backgroundColor 替代 primary shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(27.r), ), padding: EdgeInsets.symmetric(horizontal: 17.w, vertical: 9.h), minimumSize: Size(195.w, 54.h), ), ); } } class CustomIconButton extends StatelessWidget { @override Widget build(BuildContext context) { return Container( width: 54.w, height: 54.h, child: Stack( children: [ Container( width: 54.w, height: 54.h, decoration: const BoxDecoration( shape: BoxShape.circle, color: Color(0xff151313), ), ), Positioned.fill( child: Align( alignment: Alignment.center, child: const Icon( Icons.notifications, size: 36, color: Color(0xfff1f1f1), // 设置图标颜色为 0xfff1f1f1 ), ), ), ], ), ); } } class MoreButton extends StatelessWidget { @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ const Text( "more", style: TextStyle( color: Color(0xff929292), fontSize: 26, ), ), SizedBox(width: 10.w), Transform.rotate( angle: 3.14, child: Container( width: 26.w, height: 26.h, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8.r), ), child: const Icon( Icons.arrow_back, // 替换为合适的图标 size: 26, color: Color(0xfff1f1f1), // 设置图标颜色为 0xfff1f1f1 ), ), ), ], ); } }
结束,最后我们再来看看打包问题,解决之前的报错 只需要对Gradle处理,以下是常用处理方式,gradlew clean解决了我们的问题
,java.net.SocketException: Unexpected end of file from server
错误通常发生在 Gradle 尝试从服务器下载依赖项时遇到连接问题。以下是一些可能的解决方案:
- 检查网络连接:
- 确保你的网络连接稳定。
- 如果使用代理,请确保代理配置正确。
- 清理 Gradle 缓存:
- 进入项目根目录,运行命令
gradlew clean
清理构建缓存。 - 删除
.gradle
文件夹(位于用户主目录下)以清除全局 Gradle 缓存。
- 更新 Gradle 和插件:
- 检查并更新项目的 Gradle 版本和 Android 插件版本到最新稳定版。
- 修改
build.gradle
文件中的distributionUrl
到最新的 Gradle 版本。
- 检查依赖项:
- 确认
pubspec.yaml
中的所有依赖项都能正常访问。 - 使用
flutter pub get
命令来获取 Dart/Flutter 依赖项。
- 尝试离线模式(如果网络不稳定):
- 在
gradle.properties
文件中添加org.gradle.offline=true
来启用 Gradle 离线模式。
- 防火墙或杀毒软件干扰:
- 暂时禁用防火墙或杀毒软件,测试是否是它们阻止了 Gradle 的网络请求。
- 重试构建:
- 有时候简单的重试可以解决问题,因为可能是临时的网络波动导致的。
但是又遇到了新的问题,
问题出在 Gradle 版本与 Java 版本不兼容。具体来说,Gradle 不支持当前使用的 Java 版本(Java 11 或更高版本)。以下是解决此问题的步骤
- 检查当前 Java 和 Gradle 版本
- 运行
flutter doctor --verbose
检查当前使用的 Java 和 Gradle 版本。
- 更新 Gradle 版本
- 打开项目中的
android/gradle/wrapper/gradle-wrapper.properties
文件。 - 修改
distributionUrl
以使用一个与当前 Java 版本兼容的 Gradle 版本。例如:
properties
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
- 更新 Android Studio 的 JDK
- 如果你使用的是 Android Studio,确保它使用的是与 Gradle 兼容的 JDK 版本。可以在 Android Studio 的设置中更改 JDK 版本:
- 打开
File -> Project Structure -> SDK Location
。 - 更改
JDK location
到一个兼容的 JDK 版本。
- 清理和重建项目
- 清理 Gradle 缓存:
bash
cd android ./gradlew clean
- 重新构建项目:
bash
flutter clean flutter pub get flutter build apk --release
最后我们成功解决问题,已经提交打包,发现,还是不兼容,这时候怎么办,我们需要用到 FVM (Flutter Version Manager)
由于错误发生在 flutter_tools
中,你还需要确保 Flutter 工具本身使用的 Gradle 版本是最新的。你可以通过以下步骤来更新 Flutter 工具的 Gradle 版本:
- 打开 Flutter SDK 目录下的
packages/flutter_tools/gradle/gradle-wrapper.properties
文件。 - 修改
distributionUrl
为最新的 Gradle 版本,例如:
properties
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
使用 FVM (Flutter Version Manager)
如果你使用多个 Flutter 版本,建议使用 FVM 来管理不同项目的 Flutter 版本,确保每个项目使用兼容的 Flutter 和 Gradle 版本。
通过以上步骤,你应该能够解决 Gradle 版本与 Java 版本不兼容的问题,并成功构建你的 Flutter 项目。如果问题仍然存在,请提供更多的日志输出(如 --stacktrace
或 --info
),以便进一步诊断。
使用 FVM(Flutter Version Manager)可以帮助你管理不同项目的 Flutter 版本,确保每个项目使用兼容的 Flutter 和 Gradle 版本。以下是详细的步骤来安装和配置 FVM:
安装 FVM
- 安装 FVM: 你可以通过以下命令全局安装 FVM:
bash
dart pub global activate fvm
- 将 FVM 添加到 PATH: 确保 FVM 的二进制文件路径已添加到你的系统 PATH 中。通常路径为
~/.pub-cache/bin
。你可以通过以下命令将其添加到当前终端会话中:
bash
export PATH="$PATH":"$HOME/.pub-cache/bin"
如果你想永久添加,请将上述命令添加到你的 shell 配置文件中(例如 .bashrc
、.zshrc
或 .bash_profile
)。
使用 FVM 初始化项目
- 初始化 FVM: 在你的项目根目录下运行以下命令以初始化 FVM:
bash
fvm install
- 选择 Flutter 版本: FVM 会自动从
fvm.yaml
文件中读取指定的 Flutter 版本。如果没有该文件,FVM 会提示你选择一个版本并创建fvm.yaml
文件。你也可以手动编辑fvm.yaml
文件来指定版本:
yaml
flutter_sdk: version: "3.7.12" # 替换为你需要的版本号
- 使用指定的 Flutter 版本: 运行以下命令以使用 FVM 管理的 Flutter 版本:
bash
fvm flutter --version
更新项目中的依赖项
- 获取 Dart/Flutter 依赖项: 使用 FVM 管理的 Flutter 版本来获取依赖项:
bash
fvm flutter pub get
- 清理和重建项目: 清理 Gradle 缓存并重新构建项目:
bash
cd android ../.fvm/flutter_sdk/bin/flutter clean ../.fvm/flutter_sdk/bin/flutter pub get ../.fvm/flutter_sdk/bin/flutter build apk --release
更新 Gradle 和 Java 版本
- 更新 Gradle 版本: 打开项目中的
android/gradle/wrapper/gradle-wrapper.properties
文件,修改distributionUrl
以使用一个与当前 Java 版本兼容的 Gradle 版本。例如:
properties
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
- 更新 Android Studio 的 JDK: 如果你使用的是 Android Studio,确保它使用的是与 Gradle 兼容的 JDK 版本。可以在 Android Studio 的设置中更改 JDK 版本:
- 打开
File -> Project Structure -> SDK Location
。 - 更改
JDK location
到一个兼容的 JDK 版本。
检查环境变量配置
确保你的环境变量配置正确,特别是 JAVA_HOME
和 PATH
变量,指向正确的 JDK 和 Gradle 安装路径。
示例流程
假设你已经安装了 FVM 并将其添加到 PATH 中,以下是完整的示例流程:
- 初始化 FVM 并选择 Flutter 版本:
bash
cd g:\clone\ff-flutter fvm install
- 使用 FVM 管理的 Flutter 版本检查当前版本:
bash
fvm flutter --version
- 获取 Dart/Flutter 依赖项:
bash
fvm flutter pub get
- 清理和重建项目:
bash
cd android ../.fvm/flutter_sdk/bin/flutter clean ../.fvm/flutter_sdk/bin/flutter pub get ../.fvm/flutter_sdk/bin/flutter build apk --release