【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
章节内容【07】
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面
开发背景
上篇我们做了自定义组件,本文继续完善注册相关页面并且实现跳转
闲话不多,开源仓库地址,可以观摩已经写好的代码:
https://gitee.com/youyacao/ff-flutter
demo下载
实战开始
我们新建 index.dart,首先写页面,插入基础内容有两种,在Flutter中,Scaffold 和 Container 是两个非常重要的小部件,它们分别有不同的用途和功能。
这两个小部件写法是一直的,但是写页面大框架的时候我们需要选择Scaffold,我们在写底部菜单栏就应该选择 Container 。
扩展知识
return Scaffold( 和return Container( 的区别是什么
在Flutter中,Scaffold
和 Container
是两个非常重要的小部件,它们分别有不同的用途和功能。
Scaffold
Scaffold
是一个用于实现Material Design布局结构的控件,通常用于创建一个完整的应用页面。它提供了一些特有的属性和功能,能够轻松实现应用常见的布局元素,例如应用栏(AppBar)、抽屉(Drawer)、浮动操作按钮(FloatingActionButton)、底部导航栏(BottomNavigationBar)等。
dart
Scaffold( appBar: AppBar( title: Text('My App'), ), body: Center( child: Text('Hello, world!'), ), floatingActionButton: FloatingActionButton( onPressed: () {}, child: Icon(Icons.add), ), )
Container
Container
是一个通用的小部件,它可以包含单个子组件并允许进行一些简单的布局配置。Container
主要用于包装和装饰子组件,比如设置边距、内边距、对齐方式、背景颜色、边框等属性。与 Scaffold
不同,Container
不提供预定义的布局结构。
dart
Container( padding: EdgeInsets.all(16.0), margin: EdgeInsets.all(16.0), alignment: Alignment.center, color: Colors.blue, child: Text( 'Hello, world!', style: TextStyle(color: Colors.white), ), )
总结
- Scaffold:
- 主要用于创建包含应用栏、抽屉、底部导航栏等常见布局元素的完整应用页面。
- 提供了许多预定义的布局结构和功能。
- Container:
- 用于包装和装饰单个子组件,可以设置边距、内边距、对齐方式、背景颜色等属性。
- 更通用但不提供预定义的布局结构。
我们插入
class IndexScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Color(0xFF1E1E1E), // 设置背景颜色为 #1E1E1E appBar: AppBar( title: Text('Index Screen'), ), body: Center( child: Text('Welcome to the Index Screen'), ), ); } }
首先我们来做首页:
我们再将页面其他内容 写入 scaffold 里面,正确融入进去
通常,我们会使用 Column 和 Row 来组织这些组件来实现,
当写顶部左边文字后,写右边下载图标和按钮,我们发现 iconlogo 不对,
import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; class IndexScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Color(0xFF1E1E1E), // 设置背景颜色为 #1E1E1E appBar: AppBar( title: Text('Index Screen'), ), body: Column( children: [ Padding( padding: const EdgeInsets.only(left: 16, top: 16, right: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Free Friend", style: TextStyle( color: Color(0xfff1f1f1), fontSize: 32, fontFamily: "SansSerif", fontWeight: FontWeight.w700, ), ), DownloadButton(), ], ), ), // 其他内容可以继续添加在这里 Expanded( child: Center( child: Text('Welcome to the Index Screen'), ), ), ], ), ); } } class DownloadButton extends StatelessWidget { @override Widget build(BuildContext context) { return ElevatedButton.icon( onPressed: () { // 处理下载逻辑 }, icon: FlutterLogo(size: 30), label: Text( "Download", style: TextStyle( color: Color(0xfff1f1f1), fontSize: 26, fontFamily: "PingFang SC", fontWeight: FontWeight.w800, ), ), style: ElevatedButton.styleFrom( primary: Color(0xffe7568c), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(27), ), padding: EdgeInsets.symmetric(horizontal: 17, vertical: 9), minimumSize: Size(195, 54), ), ); } }
我们替换图标为
icon: Icon(Icons.system_update_alt, size: 30),
扩展知识-关于flutter图标库的
Flutter 提供了一整套 Material Design 图标库,包含了数百个常用图标。你可以使用这些图标来实现多种设计需求。Material Icons 是一个非常丰富的图标库,每个图标都有一个唯一的名称和代码点,可以在代码中直接引用。
使用示例
要在Flutter中使用这些图标,你需要导入 flutter/material.dart
包,然后使用 Icon
小部件和 Icons
类来引用图标。
dart
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: Text('Flutter Icons 示例'), ), body: Center( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Icon(Icons.home, size: 50.0), // Home 图标 SizedBox(width: 20), Icon(Icons.favorite, size: 50.0, color: Colors.red), // Favorite 图标 SizedBox(width: 20), Icon(Icons.settings, size: 50.0), // Settings 图标 ], ), ), ), ); } }
常用图标列表
以下是一些常用的图标和它们的名称:
图标 |
名称 |
用法 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
这些只是 Flutter Material Icons 库中的一小部分。要查看完整的图标列表和它们的名称,你可以访问 Material Icons 库,并在代码中相应地使用 Icons.<icon_name>
来引用图标。
以下是我们的代码 但是我们发现问题:
The named parameter ‘primary’ isn’t defined.
Try correcting the name to an existing named parameter’s name, or defining a named parameter with the name ‘primary’.
提示primary未定义
在 Flutter 中,ElevatedButton.styleFrom 方法并没有 primary 这个命名参数。相反,你应该使用 primaryColor 或 backgroundColor 来设置按钮的背景颜色。
import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; class IndexScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Color(0xFF1E1E1E), // 设置背景颜色为 #1E1E1E appBar: AppBar( title: Text('Index Screen'), ), body: Column( children: [ Padding( padding: const EdgeInsets.only(left: 16, top: 16, right: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Free Friend", style: TextStyle( color: Color(0xfff1f1f1), fontSize: 32, fontFamily: "SansSerif", fontWeight: FontWeight.w700, ), ), DownloadButton(), ], ), ), // 其他内容可以继续添加在这里 Expanded( child: Center( child: Text('Welcome to the Index Screen'), ), ), ], ), ); } } class DownloadButton extends StatelessWidget { @override Widget build(BuildContext context) { return ElevatedButton.icon( onPressed: () { // 处理下载逻辑 }, icon: Icon(Icons.system_update_alt, size: 30), label: Text( "Download", style: TextStyle( color: Color(0xfff1f1f1), fontSize: 26, fontFamily: "PingFang SC", fontWeight: FontWeight.w800, ), ), style: ElevatedButton.styleFrom( primary: Color(0xffe7568c), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(27), ), padding: EdgeInsets.symmetric(horizontal: 17, vertical: 9), minimumSize: Size(195, 54), ), ); } }
扩展知识
在Flutter中,primary
和 backgroundColor
都是用于设置颜色的属性,但它们用于不同的场景和目的。
primary
primary
颜色通常用于应用的主要颜色。这是Material Design中的一个核心概念,用于突出显示应用程序的品牌颜色和主要UI元素。它在应用的许多地方都会被用到,例如应用栏、浮动操作按钮(FAB)等。
dart
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( primaryColor: Colors.blue, // 设置应用的主要颜色 ), home: Scaffold( appBar: AppBar( title: Text('Primary Color 示例'), ), body: Center( child: ElevatedButton( onPressed: () {}, child: Text('按钮'), ), ), ), ); } }
backgroundColor
backgroundColor
用于设置组件或容器的背景颜色。它可以用于多种小部件,例如 Container
、Scaffold
和 AppBar
等。使用 backgroundColor
属性可以更具体地控制某个小部件的背景颜色。
dart
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( backgroundColor: Colors.grey[200], // 设置 Scaffold 的背景颜色 appBar: AppBar( title: Text('BackgroundColor 示例'), backgroundColor: Colors.blue, // 设置 AppBar 的背景颜色 ), body: Center( child: Container( width: 200, height: 200, color: Colors.white, // 设置 Container 的背景颜色 child: Center(child: Text('Hello, world!')), ), ), ), ); } }
总结
primary
:
- 用于设置应用的主要颜色。
- 通常在主题中配置,影响整个应用的主要颜色元素。
- 例如:
ThemeData(primaryColor: Colors.blue)
。
backgroundColor
:
- 用于设置特定小部件或容器的背景颜色。
- 可以单独配置,不影响其他小部件。
- 例如:
Container(color: Colors.white)
,Scaffold(backgroundColor: Colors.grey[200])
。
大白话 就是,小部件用backgroundColor,整个应用主题颜色采用primary,(关于创建切换theme主题才用的到)
我们社交app就一个模板,所以用不上,接下来我们放入 右侧的图标按钮以及下方的图标和文字,以下是代码:
import 'package:flutter/material.dart'; class IndexScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Color(0xFF1E1E1E), // 设置背景颜色为 #1E1E1E // appBar: AppBar( // title: Text('Index Screen'), // ), body: Column( children: [ Padding( padding: const EdgeInsets.only(left: 16, top: 16, right: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Free Friend", style: TextStyle( color: Color(0xfff1f1f1), fontSize: 32, fontFamily: "SansSerif", fontWeight: FontWeight.w700, ), ), Row( children: [ DownloadButton(), SizedBox(width: 16), // 添加间距 CustomIconButton(), ], ), ], ), ), SizedBox(height: 16), // 添加间距 Padding( padding: const EdgeInsets.only(left: 16), child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 36, height: 36, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), color: Color(0xffe7568c), // 设置背景颜色为 0xffe7568c ), child: Icon( Icons.location_on, size: 36, color: Color(0xfff1f1f1), // 设置图标颜色为 0xfff1f1f1 ), ), SizedBox(width: 10), Text( "America", style: TextStyle( color: Color(0xfff1f1f1), fontSize: 32, fontFamily: "SansSerif", fontWeight: FontWeight.w700, ), ), ], ), ), // 其他内容可以继续添加在这里 Expanded( child: Center( child: Text('Welcome to the Index Screen'), ), ), ], ), ); } } class DownloadButton extends StatelessWidget { @override Widget build(BuildContext context) { return ElevatedButton.icon( onPressed: () { // 处理下载逻辑 }, icon: Icon( Icons.system_update_alt, size: 30, color: Color(0xfff1f1f1), // 设置图标颜色为 0xfff1f1f1 ), label: Text( "Download", style: TextStyle( color: Color(0xfff1f1f1), fontSize: 26, fontFamily: "PingFang SC", fontWeight: FontWeight.w800, ), ), style: ElevatedButton.styleFrom( backgroundColor: Color(0xffe7568c), // 使用 backgroundColor 替代 primary shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(27), ), padding: EdgeInsets.symmetric(horizontal: 17, vertical: 9), minimumSize: Size(195, 54), ), ); } } class CustomIconButton extends StatelessWidget { @override Widget build(BuildContext context) { return Container( width: 54, height: 54, child: Stack( children: [ Container( width: 54, height: 54, decoration: BoxDecoration( shape: BoxShape.circle, color: Color(0xff151313), ), ), Positioned.fill( child: Align( alignment: Alignment.center, child: Icon( Icons.notifications, size: 36, color: Color(0xfff1f1f1), // 设置图标颜色为 0xfff1f1f1 ), ), ), ], ), ); } }
接着我们做下面的定位按钮以及文字,
如何将一个容器完全放置于左侧,使用 Padding 组件为 Row 添加左侧内边距 const EdgeInsets.only(left: 16),以确保整个容器放置在左侧,
以下是代码文件:
以下代码"America" 及其前面的图标 以及前面的图标 已经设置了容器在左侧,为什么还是显示在屏幕的中间 import 'package:flutter/material.dart'; class IndexScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Color(0xFF1E1E1E), // 设置背景颜色为 #1E1E1E body: Column( children: [ Padding( padding: const EdgeInsets.only(left: 16, top: 16, right: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Free Friend", style: TextStyle( color: Color(0xfff1f1f1), fontSize: 32, fontFamily: "SansSerif", fontWeight: FontWeight.w700, ), ), Row( children: [ DownloadButton(), SizedBox(width: 16), // 添加间距 CustomIconButton(), ], ), ], ), ), SizedBox(height: 16), // 添加间距 Padding( padding: const EdgeInsets.only(left: 16), child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 36, height: 36, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), color: Color(0xffe7568c), // 设置背景颜色为 0xffe7568c ), child: Icon( Icons.location_on, size: 36, color: Color(0xfff1f1f1), // 设置图标颜色为 0xfff1f1f1 ), ), SizedBox(width: 10), Text( "America", style: TextStyle( color: Color(0xfff1f1f1), fontSize: 32, fontFamily: "SansSerif", fontWeight: FontWeight.w700, ), ), ], ), ), Expanded( child: Center( child: Text('Welcome to the Index Screen'), ), ), ], ), ); } } class DownloadButton extends StatelessWidget { @override Widget build(BuildContext context) { return ElevatedButton.icon( onPressed: () { // 处理下载逻辑 }, icon: Icon( Icons.system_update_alt, size: 30, color: Color(0xfff1f1f1), // 设置图标颜色为 0xfff1f1f1 ), label: Text( "Download", style: TextStyle( color: Color(0xfff1f1f1), fontSize: 26, fontFamily: "PingFang SC", fontWeight: FontWeight.w800, ), ), style: ElevatedButton.styleFrom( backgroundColor: Color(0xffe7568c), // 使用 backgroundColor 替代 primary shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(27), ), padding: EdgeInsets.symmetric(horizontal: 17, vertical: 9), minimumSize: Size(195, 54), ), ); } } class CustomIconButton extends StatelessWidget { @override Widget build(BuildContext context) { return Container( width: 54, height: 54, child: Stack( children: [ Container( width: 54, height: 54, decoration: BoxDecoration( shape: BoxShape.circle, color: Color(0xff151313), ), ), Positioned.fill( child: Align( alignment: Alignment.center, child: Icon( Icons.notifications, size: 36, color: Color(0xfff1f1f1), // 设置图标颜色为 0xfff1f1f1 ), ), ), ], ), ); } }
但是实际并没有到左侧,因为需要进行如下处理,可以将 CrossAxisAlignment.start 添加到 Column 和 Row 的交叉轴对齐属性中,在 Column 中添加了 crossAxisAlignment: CrossAxisAlignment.start 属性,以确保所有子元素在交叉轴上对齐到左侧。这应该能使 “America” 及其前面的图标对齐到左侧,而不是显示在屏幕的中间。
import 'package:flutter/material.dart'; class IndexScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFF1E1E1E), // 设置背景颜色为 #1E1E1E body: Column( crossAxisAlignment: CrossAxisAlignment.start, // 添加这个属性 children: [ Padding( padding: const EdgeInsets.only(left: 16, top: 16, right: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( "Free Friend", style: TextStyle( color: Color(0xfff1f1f1), fontSize: 32, fontFamily: "SansSerif", fontWeight: FontWeight.w700, ), ), Row( children: [ DownloadButton(), const SizedBox(width: 16), // 添加间距 CustomIconButton(), ], ), ], ), ), const SizedBox(height: 16), // 添加间距 Padding( padding: const EdgeInsets.only(left: 16), child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 36, height: 36, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), color: const Color(0xffe7568c), // 设置背景颜色为 0xffe7568c ), child: const Icon( Icons.location_on, size: 36, color: Color(0xfff1f1f1), // 设置图标颜色为 0xfff1f1f1 ), ), const SizedBox(width: 10), const Text( "America", style: TextStyle( color: Color(0xfff1f1f1), fontSize: 32, fontFamily: "SansSerif", fontWeight: FontWeight.w700, ), ), ], ), ), Expanded( child: Center( child: const Text('Welcome to the Index Screen'), ), ), ], ), ); } } 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), ), padding: const EdgeInsets.symmetric(horizontal: 17, vertical: 9), minimumSize: const Size(195, 54), ), ); } } class CustomIconButton extends StatelessWidget { @override Widget build(BuildContext context) { return Container( width: 54, height: 54, child: Stack( children: [ Container( width: 54, height: 54, 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 ), ), ), ], ), ); } }
显示效果
—— 晚点更新