【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
章节内容【08】
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心
开发背景
背景说明要提一点:我们所有的开发耗尽2个月的时间,目前只是整合与记录并且呈现过程,大家不要想的太简单,自己试试就知道了哈,而不是你们以为的很快很简单,这点请必须要知道。
闲话不多,开源仓库地址,可以观摩已经写好的代码:
https://gitee.com/youyacao/ff-flutter
demo下载
https://www.youyacao.cn/freefirend
更新代码文件和日志文件-gitee可见
·完善了首页的列表
·增加了用户中心
·直播间房间的页面内容铺垫
·调试变成真机调试采用android studio进行调试
本次gitpull 忘记 记录文件变化了
实战开始
先启用真机调试,进入android studio,左侧打开本项目的android目录,右侧选择device manage
ok 选择一个适配的设配,
点击加号,选择第一个创建虚拟设备。
选择pixel 9 比较适合,我们考虑的机器就是 竖屏手机,没有考虑平板
下一步提示镜像选择,继续next,
创建虚拟机的名字,默认即可
点击run 也就是播放按钮,就开始运行
在左侧的build框我们可以看见运行的过程日志
右侧我们点击 run device 可以看到运行的设备, 上面点击 run 播放按钮 可以看到 直接启动了本项目
我们看到右侧已经显示了整个模拟器情况,我们看到
首页底部的 附近的用户,以及直播列表 已经增加
其次个人中心的选项与设置均已增加,
往下可以滑动,这是整体结果情况,我们来看看具体修改的代码部分,在比较复杂的部分,这里会做解释,
项目的开发我们是用的flutter ,dart语言,因此我们继续回到vscode,android studio 只适合安卓开发很局限。
这是我们增加的页面,以及组件,本次增加大概有13个页面,
最先来看路由
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/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()); }), ), ]; }
先来看首页组件home_screen.dart:
import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:ff_flutter/screens/home/widgets/popular_live_room.dart'; import 'package:ff_flutter/screens/home/widgets/download_button.dart'; import 'package:ff_flutter/screens/home/widgets/notification_button.dart'; import 'package:ff_flutter/screens/home/widgets/big_popular_live_room.dart'; import 'package:ff_flutter/screens/home/widgets/tab_bar/index.dart'; class HomeScreenWidget extends StatelessWidget { @override Widget build(BuildContext context) { return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 顶部状态栏留空 SizedBox(height: MediaQuery.of(context).padding.top), // Free Friend 和下载按钮 Padding( padding: EdgeInsets.symmetric(horizontal: 32.w), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Free Friend", style: TextStyle( color: Colors.white, fontSize: 48.sp, fontWeight: FontWeight.bold, ), ), Row( children: [ DownloadButton(), SizedBox(width: 24.w), NotificationButton(), ], ), ], ), ), SizedBox(height: 32.h), // 地区选择 Padding( padding: EdgeInsets.symmetric(horizontal: 32.w), child: Row( children: [ Container( width: 64.w, height: 64.h, decoration: BoxDecoration( color: const Color(0xFFE7568C), borderRadius: BorderRadius.circular(16.r), ), child: Icon( Icons.location_on, color: Colors.white, size: 36.sp, ), ), SizedBox(width: 16.w), Text( "America", style: TextStyle( color: Colors.white, fontSize: 48.sp, fontWeight: FontWeight.bold, ), ), ], ), ), SizedBox(height: 32.h), // Popular Live Room 组件 PopularLiveRoom(), BigPopularLiveRoom(), HomeTabBar(), ], ), ); } }
再继续看下我们的下载按钮单独做了组件,应该下载要单独download_button.dart
import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; class DownloadButton extends StatelessWidget { @override Widget build(BuildContext context) { return Container( height: 72.h, decoration: BoxDecoration( color: const Color(0xFFE7568C), borderRadius: BorderRadius.circular(36.r), ), child: TextButton.icon( onPressed: () {}, icon: Icon( Icons.download, color: Colors.white, size: 32.sp, ), label: Text( 'Download', style: TextStyle( color: Colors.white, fontSize: 32.sp, fontWeight: FontWeight.bold, ), ), ), ); } }
首页中的滑动组件,big_popular_live_room.dart
import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; class BigPopularLiveRoom extends StatelessWidget { @override Widget build(BuildContext context) { return Column( children: [ SizedBox(height: 50.h), SizedBox( height: 399.h, child: ListView.builder( scrollDirection: Axis.horizontal, padding: EdgeInsets.symmetric(horizontal: 32.w), itemCount: 8, itemBuilder: (context, index) { return Container( width: 300.w, margin: EdgeInsets.only(right: 24.w), child: Column( children: [ Container( width: 300.w, height: 300.w, decoration: BoxDecoration( borderRadius: BorderRadius.circular(16.r), ), child: Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(16.r), child: const Image( image: AssetImage( 'assets/images/big_popular_live_girl.png'), width: double.infinity, height: double.infinity, fit: BoxFit.cover, alignment: Alignment.center, ), ), Positioned( right: 16.w, bottom: 16.h, child: Container( padding: EdgeInsets.symmetric( horizontal: 16.w, vertical: 4.h, ), decoration: BoxDecoration( color: Colors.black.withOpacity(0.5), borderRadius: BorderRadius.circular(30.r), ), child: Row( children: [ Icon( Icons.favorite, color: Colors.white, size: 24.sp, ), SizedBox(width: 4.w), Text( '${index + 1}.2M', style: TextStyle( color: Colors.white, fontSize: 24.sp, ), ), ], ), ), ), ], ), ), SizedBox(height: 16.h), Container( width: 300.w, padding: EdgeInsets.symmetric(horizontal: 16.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( [ 'Bryce Adams', 'Emma Stone', 'Lucy Liu', 'Anna May' ][index % 4], style: TextStyle( color: Colors.white, fontSize: 32.sp, fontWeight: FontWeight.bold, ), ), // SizedBox(height: 8.h), Text( '@${[ 'fitiajidjisd', 'emmastone', 'lucyliu', 'annamay' ][index % 4]}', style: TextStyle( color: Colors.grey, fontSize: 24.sp, ), ), ], ), ), ], ), ); }, ), ), ], ); } }
通知按钮notification_button.dart
import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; class NotificationButton extends StatelessWidget { @override Widget build(BuildContext context) { return Container( width: 72.w, height: 72.h, decoration: const BoxDecoration( color: Color(0xFF2A2A2A), shape: BoxShape.circle, ), child: Icon( Icons.notifications_none, color: Colors.white, size: 36.sp, ), ); } }
再来看看 首页 index.dart
import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:ff_flutter/screens/home/index.dart'; import 'package:ff_flutter/screens/short_video_screen.dart'; import 'package:ff_flutter/screens/message_screen.dart'; import 'package:ff_flutter/screens/account/index.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 ), ), ), ], ); } }
个人中心,来看头像部分
import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; class AccountHeader extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( padding: EdgeInsets.symmetric(horizontal: 32.w), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( width: 72.w, height: 72.h, decoration: const BoxDecoration( color: Color(0xFF2A2A2A), shape: BoxShape.circle, ), child: Image.asset( 'assets/images/account_notes.png', width: 36.w, height: 36.h, ), ), Container( height: 72.h, padding: EdgeInsets.symmetric(horizontal: 32.w), decoration: BoxDecoration( color: Colors.transparent, borderRadius: BorderRadius.circular(36.r), border: Border.all( color: const Color(0xFFE7568C), width: 2.w, ), ), child: Row( children: [ Icon( Icons.download, color: const Color(0xFFE7568C), size: 32.sp, ), SizedBox(width: 8.w), Text( 'Download', style: TextStyle( color: const Color(0xFFE7568C), fontSize: 32.sp, fontWeight: FontWeight.bold, ), ), ], ), ), ], ), ); } }
下方列表
import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; class MenuItem { final String icon; final String title; MenuItem(this.icon, this.title); } class MenuList extends StatelessWidget { final List<MenuItem> menuItems = [ MenuItem('assets/images/account_icon1.png', 'Bill'), MenuItem('assets/images/account_icon2.png', 'Product Management'), MenuItem('assets/images/account_icon3.png', 'Order History'), MenuItem('assets/images/account_icon4.png', 'Historical'), MenuItem('assets/images/account_icon5.png', 'Invite Friends'), MenuItem( 'assets/images/account_icon6.png', 'Apply to become a broadcaster'), ]; final List<MenuItem> systemMenuItems = [ MenuItem('assets/images/account_icon7.png', 'Online Service'), MenuItem('assets/images/account_icon8.png', 'Security Center'), MenuItem('assets/images/account_icon9.png', 'Help Center'), ]; @override Widget build(BuildContext context) { return Padding( padding: EdgeInsets.symmetric(horizontal: 30.w), child: Column( children: [ ...menuItems.map((item) => _buildMenuItem(item)).toList(), Container( height: 1.h, color: const Color(0xFF393939), ), SizedBox(height: 70.h), ...systemMenuItems.map((item) => _buildMenuItem(item)).toList(), ], ), ); } Widget _buildMenuItem(MenuItem item) { return Column( children: [ SizedBox( height: 46.h, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Image.asset( item.icon, width: 48.w, height: 48.h, ), SizedBox(width: 24.w), Text( item.title, style: TextStyle( color: Colors.white, fontSize: 32.sp, fontWeight: FontWeight.bold, ), ), ], ), Icon( Icons.chevron_right, color: Colors.white, size: 48.sp, ), ], ), ), SizedBox(height: 70.h), ], ); } }
vip部分
import 'package:flutter/material.dart'; import 'package:flutter\_screenutil/flutter\_screenutil.dart'; class OpenVip extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( padding: EdgeInsets.symmetric(horizontal: 30.w), child: Container( height: 110.h, decoration: BoxDecoration( color: const Color(0xFF393939), borderRadius: BorderRadius.circular(30.r), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: \[ Row( children: \[ SizedBox(width: 32.w), Image.asset( 'assets/images/account\_vip.png', width: 48.w, height: 48.h, ), SizedBox(width: 24.w), Text( 'Open VIP', style: TextStyle( color: Colors.white, fontSize: 32.sp, fontWeight: FontWeight.bold, ), ), \], ), Padding( padding: EdgeInsets.only(right: 32.w), child: Icon( Icons.chevron\_right, color: Colors.white, size: 48.sp, ), ), \], ), ), ); } }
用户信息部分、
import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; class UserInfo extends StatelessWidget { @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ // 头像和相机按钮 Center( child: SizedBox( width: 319.w, height: 319.h, child: Stack( children: [ Container( width: 319.w, height: 319.h, decoration: const BoxDecoration( shape: BoxShape.circle, ), child: ClipOval( child: Image.asset( 'assets/images/girl.png', fit: BoxFit.cover, ), ), ), Positioned( right: 0, bottom: 0, child: Container( width: 72.w, height: 72.h, decoration: const BoxDecoration( color: Color(0xFFE7568C), shape: BoxShape.circle, ), child: Icon( Icons.camera_alt, color: Colors.white, size: 36.sp, ), ), ), ], ), ), ), SizedBox(height: 24.h), // 用户名 Text( 'ANNA_122', style: TextStyle( color: Colors.white, fontSize: 48.sp, fontWeight: FontWeight.bold, ), ), SizedBox(height: 8.h), // 用户ID Text( 'ID:012345548787', style: TextStyle( color: Colors.grey, fontSize: 32.sp, ), ), SizedBox(height: 8.h), // 职业 Text( 'a teacher', style: TextStyle( color: Colors.white.withOpacity(0.9), fontSize: 32.sp, ), ), ], ); } }
用户状态部分,
import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; class UserStats extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( padding: EdgeInsets.only(top: 31.h), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _buildStatItem('8.6K', 'Gold Coin'), SizedBox(width: 120.w), _buildStatItem('12', 'Shopping Cart'), ], ), ); } Widget _buildStatItem(String value, String label) { return Column( children: [ Text( value, style: TextStyle( color: Colors.white, fontSize: 42.sp, fontWeight: FontWeight.w900, ), ), SizedBox(height: 4.h), Text( label, style: TextStyle( color: Colors.grey, fontSize: 26.sp, ), ), ], ); } }
整体代码中,在头像部分 还需要单独处理
这段代码定义了一个名为 AccountHeader
的无状态小部件,用于显示账户头部信息。它包含一个圆形图片和一个下载按钮。具体功能如下:
- 左侧是一个72x72的圆形容器,内含36x36的图片。
- 右侧是一个带边框的容器,内含下载图标和文本“Download”,点击效果未实现。
控制流图
mermaid
flowchart TD A[开始] --> B[创建Padding] B --> C[创建Row布局] C --> D[创建左侧圆形容器] D --> E[添加图片] C --> F[创建右侧带边框容器] F --> G[添加下载图标] G --> H[添加下载文本] H --> I[结束]
下载按钮部分也是比较值得注意的,
代码解释 这段代码定义了一个名为 DownloadButton 的无状态小部件,用于创建一个下载按钮。按钮的高度为72逻辑像素,背景颜色为粉色(0xFFE7568C),圆角半径为36逻辑像素。按钮包含一个图标和文本“Download”,点击按钮时触发空操作。 控制流图 mermaid flowchart TD A[开始] --> B[创建容器] B --> C[设置容器高度和装饰] C --> D[创建TextButton.icon] D --> E[设置按钮点击事件为空操作] E --> F[设置图标和文本样式] F --> G[结束]
本次记录和学习已经完成,优雅草卓伊凡。