虽然这几天也在不断的学习flutter相关的知识,但是并没有输出,学的有点凌乱不知道从何下手。今天加班不怎么忙,理了下思路:增加Gitcode 口袋功能,然后详解相关组件。

一、页面框架搭建

1、Scaffold

Scaffold 是 Flutter Material 组件库提供的一个路由页的骨架,很容易实现一个页面完整的页面,

Scaffold 的 bottomNavigationBar属性来设置底部导航

如上述截图代码:

  NavigationBar buildNormalNavigationBar() {
    return NavigationBar(
      selectedIndex: currentIndex,
      onDestinationSelected: (int index) {
        setState(() {
          currentIndex = index;
        });
      },
      destinations: const [
        NavigationDestination(
          icon: Icon(Icons.home_outlined),
          selectedIcon: Icon(Icons.home),
          label: '首页',
        ),
        NavigationDestination(
          icon: Icon(Icons.search_outlined),
          selectedIcon: Icon(Icons.search),
          label: '搜索',
        ),
        NavigationDestination(
          icon: Icon(Icons.person_outline),
          selectedIcon: Icon(Icons.person),
          label: '我的',
        ),
      ],
    );
  }

2、Scaffold 的 AppBar 是一个 Material 风格的导航栏

      appBar: AppBar(
        title: Text('GitCode 口袋工具'),
        leading: Builder(builder: (context){
          return IconButton(
            icon: Icon(Icons.settings,color: Colors.black54,size: 32,),
            onPressed:(){
              Scaffold.of(context).openDrawer();
            },
          );
        }),
      ),

3、Scaffold 的 Drawer 实现抽屉样式

 drawer: SettingDrawer(),


class SettingDrawer extends StatelessWidget{
  const SettingDrawer({super.key});

  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: MediaQuery.removePadding(
        context: context,
        //移除抽屉菜单顶部默认留白
        removeTop: true,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(top: 38.0),
              child: Row(
                children: <Widget>[
                  Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 16.0),
                    child: ClipOval(
                      child: Image.asset(
                        "images/avatar.jpg",
                        width: 80,
                      ),
                    ),
                  ),
                  Text(
                    "夏小鱼",
                    style: TextStyle(fontWeight: FontWeight.bold),
                  )
                ],
              ),
            ),
            Expanded(
              child: ListView(
                children: <Widget>[
                  ListTile(
                    leading: const Icon(Icons.dark_mode),
                    title: const Text('深色模式'),
                  ),
                  ListTile(
                    leading: const Icon(Icons.clear),
                    title: const Text('清楚缓存'),
                  ),
                  ListTile(
                    leading: const Icon(Icons.info),
                    title: const Text('版本信息'),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

4、Scaffold 的 FloatingActionButton可以实现导航栏底部挖空的效果

      bottomNavigationBar: BottomAppBar(
        color: Colors.white,
        shape: CircularNotchedRectangle(), // 底部导航栏打一个圆形的洞
        notchMargin: 8,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround, //均分底部导航栏横向空间
          children: [
            IconButton(icon: Icon(Icons.home), onPressed: () {}),
            SizedBox(), //中间位置空出
            IconButton(icon: Icon(Icons.person), onPressed: () {}),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        shape: CircleBorder(),
        child: CircleAvatar(
          radius: 40,
          backgroundColor: Colors.green[300],
          child: Icon(Icons.add, size: 40, color: Colors.white),
        ),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,

二、首页

这个页面主要是信息展示,涉及到的是Text相关属性

import 'package:flutter/material.dart';

// 首页
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final mutedColor = theme.colorScheme.onSurfaceVariant;

    return Scaffold(
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 标题和图标
            Center(
              child: Column(
                children: [
                  const SizedBox(height: 32),
                  Text(
                    'GitCode 口袋工具',
                    style: theme.textTheme.headlineMedium?.copyWith(
                      fontWeight: FontWeight.bold,
                      color: theme.colorScheme.primary,
                    ),
                  ),
                  const SizedBox(height: 8),
                  Text(
                    '方便快捷的 GitCode 助手',
                    style: theme.textTheme.bodyLarge?.copyWith(
                      color: mutedColor,
                    ),
                  ),
                ],
              ),
            ),
            const SizedBox(height: 40),

            // 项目介绍
            _buildSection(
              context,
              title: '项目介绍',
              icon: Icons.info_outline,
              child: Text(
                'GitCode 口袋工具 - 面向 OpenHarmony 生态的 Flutter 跨平台实践',
                style: theme.textTheme.bodyMedium?.copyWith(height: 1.6),
              ),
            ),
            const SizedBox(height: 24),


            // 技术栈
            _buildSection(
              context,
              title: '技术栈',
              icon: Icons.build_outlined,
              child: Wrap(
                spacing: 8,
                runSpacing: 8,
                children: [
                  _buildTechChip(context, 'Flutter'),
                  _buildTechChip(context, 'Dio'),
                  _buildTechChip(context, 'Axios'),
                  _buildTechChip(context, 'Material Design 3'),
                ],
              ),
            ),
            const SizedBox(height: 24),

            // 使用提示
            _buildSection(
              context,
              title: '使用提示',
              icon: Icons.lightbulb_outline,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  _buildTipItem(context, '1. 在"搜索"页面输入关键字和 Access Token'),
                  const SizedBox(height: 8),
                  _buildTipItem(context, '2. 选择搜索模式:用户或仓库'),
                  const SizedBox(height: 8),
                  _buildTipItem(context, '3. 点击"查询"按钮开始搜索'),
                  const SizedBox(height: 8),
                  _buildTipItem(context, '4. 点击"查看全部"可进入分页列表页面'),
                ],
              ),
            ),
            const SizedBox(height: 40),

            // 版本信息
            Center(
              child: Text(
                '版本 1.0.0',
                style: theme.textTheme.bodySmall?.copyWith(
                  color: mutedColor,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSection(
      BuildContext context, {
        required String title,
        required IconData icon,
        required Widget child,
      }) {
    final theme = Theme.of(context);

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          children: [
            Icon(icon, color: theme.colorScheme.primary, size: 24),
            const SizedBox(width: 8),
            Text(
              title,
              style: theme.textTheme.titleLarge?.copyWith(
                fontWeight: FontWeight.bold,
              ),
            ),
          ],
        ),
        const SizedBox(height: 16),
        child,
      ],
    );
  }


  Widget _buildTechChip(BuildContext context, String label) {
    return Chip(
      label: Text(label),
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
    );
  }

  Widget _buildTipItem(BuildContext context, String text) {
    final theme = Theme.of(context);
    final muted = theme.colorScheme.onSurfaceVariant;

    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const SizedBox(width: 8),
        Expanded(
          child: Text(
            text,
            style: theme.textTheme.bodyMedium?.copyWith(
              color: muted,
              height: 1.5,
            ),
          ),
        ),
      ],
    );
  }
}

1、位置与对齐组件:Center

是对齐与相对定位(Align)其中的一种

Align({
  Key key,
  this.alignment = Alignment.center,
  this.widthFactor,
  this.heightFactor,
  Widget child,
})

Align组件可以随意调整子组件的位置

alignment : 需要一个AlignmentGeometry类型的值,表示子组件在父组件中的起始位置。AlignmentGeometry 是一个抽象类,它有两个常用的子类:Alignment和 FractionalOffset,我们将在下面的示例中详细介绍。

widthFactorheightFactor是用于确定Align 组件本身宽高的属性;它们是两个缩放因子,会分别乘以子元素的宽、高,最终的结果就是Align 组件的宽高。如果值为null,则组件的宽高将会占用尽可能多的空间。

    Align(
      widthFactor: 2,
      heightFactor: 2,
      alignment: Alignment.topRight,
      child: Icon(Icons.rocket_launch,size: 96,color: Colors.green,),
    );

Icon 的大小是96,widthFactor,heightFactor都是2,Align组件的大小就是 96 x 2

Icon 位于 Align 的右上角,但是 Alignment.topRight 是什么呢

static const Alignment topRight = Alignment(1.0, -1.0);

Alignment继承自AlignmentGeometry,表示矩形内的一个点,他有两个属性xy,分别表示在水平和垂直方向的偏移

接下来就是 Center

class Center extends Align {
  /// Creates a widget that centers its child.
  const Center({super.key, super.widthFactor, super.heightFactor, super.child});
}

Center 继承自 Align,比 Align 只少了一个 alignment 参数;由于Align的构造函数中alignment 值为Alignment.center,因此可以认为 Center 组件其实是对齐方式确定(Alignment.center)了的Align

2、布局组件: Column

与 Row 一样都是线性布局(沿水平或垂直方向排列子组件)

对于线性布局,有主轴和纵轴之分,如果布局是沿水平方向,那么主轴就是指水平方向,而纵轴即垂直方向;如果布局沿垂直方向,那么主轴就是指垂直方向,而纵轴就是水平方向

在线性布局中,有两个定义对齐方式的枚举类MainAxisAlignmentCrossAxisAlignment,分别代表主轴对齐和纵轴对齐。

3、固定尺寸组件:SizedBox

是 Flutter 中一个非常常用的布局组件,用于控制子组件的尺寸或创建固定大小的空白空间

1) 固定尺寸的 SizedBox
SizedBox(
  width: 100,    // 宽度 100
  height: 50,    // 高度 50
  child: Text('固定尺寸'), // 子组件
)
2)仅设置宽度或高度
SizedBox(
  width: 100,    // 宽度 100
  height: 50,    // 高度 50
  child: Text('固定尺寸'), // 子组件
)
3)其它构造函数
SizedBox.shrink() // 创建一个尺寸为 0×0 的盒子

SizedBox.expand(
  child: Text('填满父容器'), // 宽度和高度都充满父容器
)

SizedBox.fromSize(
  size: Size(150, 80), // 使用 Size 对象指定尺寸
  child: Text('通过 Size 设置'),
)

const SizedBox(width: 20, height: 20) // 使用 const 优化性能


4)实际场景
// 1、创建间距
Column(
  children: [
    Text('第一行'),
    SizedBox(height: 20), // 创建 20px 的垂直间距
    Text('第二行'),
    SizedBox(height: 30), // 创建 30px 的垂直间距
    Text('第三行'),
  ],
)

// 2、水平间距
Row(
  children: [
    Text('左边'),
    SizedBox(width: 15), // 创建 15px 的水平间距
    Text('右边'),
  ],
)

// 3、控制组件大小
// 限制按钮大小
SizedBox(
  width: 200,
  height: 60,
  child: ElevatedButton(
    onPressed: () {},
    child: Text('固定大小的按钮'),
  ),
)

// 限制图片大小
SizedBox(
  width: 120,
  height: 120,
  child: Image.asset('assets/logo.png'),
)

4、文本组件:Text

Text('基于 Flutter 的跨平台 GitCode 口袋工具,全面兼容 OpenHarmony 系统',
            textAlign: TextAlign.left, // 文本的对齐方式;可以选择左对齐、右对齐还是居中
            maxLines: 2,
            overflow: TextOverflow.ellipsis,
            textScaler: TextScaler.linear(1.5), // 可缩放1.5倍
            style: TextStyle( //TextStyle用于指定文本显示的样式如颜色、字体、粗细、背景等
                fontSize: 16,
                color: Colors.green,
                fontWeight: FontWeight.bold,
                fontFamily: "Courier", // 字体
                background: Paint()..color = Colors.yellow,
                decoration: TextDecoration.underline,
                decorationStyle: TextDecorationStyle.dashed)),

1)textAlign
文本的对齐方式;可以选择左对齐、右对齐还是居中
2)maxLinesoverflow

指定文本显示的最大行数,默认情况下,文本是自动折行的,如果指定此参数,则文本最多不会超过指定的行。如果有多余的文本,可以通过overflow来指定截断方式,默认是直接截断,本例中指定的截断方式TextOverflow.ellipsis,它会将多余文本截断后以省略符“...”表示

3)textScaleFactor

代表文本相对于当前字体大小的缩放因子,相对于去设置文本的样式style属性的fontSize,它是调整字体大小的一个快捷方式。该属性的默认值可以通过MediaQueryData.textScaleFactor获得,如果没有MediaQuery,那么会默认值将为1.0。

4)TextStyle

用于指定文本显示的样式如颜色、字体、粗细、背景等

5、Expanded

Expanded 是 Flutter 中用于弹性布局的重要组件,它让子组件在 RowColumn 或 Flex 中占据剩余空间。下面详细解析它的用法:

1)、什么是 Expanded?
// Expanded 让子组件填充可用空间
Row(
  children: [
    Container(width: 50, color: Colors.red),
    Expanded( // 这个 Container 会占据剩余的所有宽度
      child: Container(color: Colors.blue),
    ),
  ],
)
2)、级别语法
Expanded(
  flex: 1, // 弹性系数(可选,默认为1)
  child: YourWidget(), // 要扩展的子组件
)
3)、在Row中等分空间
Row(
  children: [
    Expanded( // 每个 Expanded 占据相同空间
      child: Container(color: Colors.red, height: 50),
    ),
    Expanded(
      child: Container(color: Colors.green, height: 50),
    ),
    Expanded(
      child: Container(color: Colors.blue, height: 50),
    ),
  ],
)
4)、不同比例的空间分配
Row(
  children: [
    Expanded(
      flex: 1, // 占据 1/6 的空间
      child: Container(color: Colors.red, height: 50),
    ),
    Expanded(
      flex: 2, // 占据 2/6 的空间
      child: Container(color: Colors.green, height: 50),
    ),
    Expanded(
      flex: 3, // 占据 3/6 的空间
      child: Container(color: Colors.blue, height: 50),
    ),
  ],
)
5)、固定尺寸与弹性尺寸结合
Row(
  children: [
    Container(
      width: 80, // 固定宽度
      color: Colors.red,
      height: 50,
    ),
    Expanded( // 占据剩余宽度
      child: Container(color: Colors.blue, height: 50),
    ),
    Container(
      width: 60, // 固定宽度
      color: Colors.green,
      height: 50,
    ),
  ],
)
6)、在Column中垂直等分
Column(
  children: [
    Expanded(
      child: Container(color: Colors.red),
    ),
    Expanded(
      child: Container(color: Colors.green),
    ),
    Expanded(
      child: Container(color: Colors.blue),
    ),
  ],
)
7)、复杂垂直布局
Column(
  children: [
    Container(
      height: 60, // 固定高度 - AppBar
      color: Colors.grey,
      child: Center(child: Text('标题栏')),
    ),
    Expanded( // 占据剩余空间 - 主要内容
      flex: 3,
      child: Container(color: Colors.blue),
    ),
    Expanded( // 占据剩余空间 - 次要内容
      flex: 1,
      child: Container(color: Colors.green),
    ),
    Container(
      height: 50, // 固定高度 - 底部栏
      color: Colors.orange,
      child: Center(child: Text('底部栏')),
    ),
  ],
)
8)、flex的计算规则
Row(
  children: [
    Expanded(
      flex: 1, // 占据 1/(1+2+3) = 1/6 的空间
      child: Container(color: Colors.red),
    ),
    Expanded(
      flex: 2, // 占据 2/(1+2+3) = 2/6 的空间
      child: Container(color: Colors.green),
    ),
    Expanded(
      flex: 3, // 占据 3/(1+2+3) = 3/6 的空间
      child: Container(color: Colors.blue),
    ),
  ],
)
9)、混合使用不同的flex值
Row(
  children: [
    Container(width: 50, color: Colors.grey), // 固定 50px
    Expanded(
      flex: 1, // 占据剩余空间的 1/4
      child: Container(color: Colors.red),
    ),
    Expanded(
      flex: 3, // 占据剩余空间的 3/4
      child: Container(color: Colors.blue),
    ),
  ],
)

6、Wrap 

Wrap 是 Flutter 中一个灵活的布局组件,当子组件在主轴方向上空间不足时,它会自动换行到交叉轴方向。

1)、什么是Wrap
Wrap(
  children: [
    Chip(label: Text('标签1')),
    Chip(label: Text('标签2')),
    Chip(label: Text('很长的标签3')),
    Chip(label: Text('标签4')),
    Chip(label: Text('标签5')),
    // 当一行放不下时自动换行
  ],
)
2)、基本语法
Wrap(
  direction: Axis.horizontal, // 排列方向
  alignment: WrapAlignment.start, // 主轴对齐方式
  spacing: 8.0, // 主轴间距
  runAlignment: WrapAlignment.start, // 交叉轴对齐方式
  runSpacing: 8.0, // 交叉轴间距
  children: [/* 子组件 */],
)
3)、排列方向
// 水平排列(默认)
Wrap(
  direction: Axis.horizontal,
  children: List.generate(10, (index) => Chip(label: Text('标签$index'))),
)

// 垂直排列
Wrap(
  direction: Axis.vertical,
  children: List.generate(10, (index) => Chip(label: Text('标签$index'))),
)
4)、alignment - 主轴对齐方式
Row(
  children: [
    _buildWrapExample('start', WrapAlignment.start),
    _buildWrapExample('center', WrapAlignment.center),
    _buildWrapExample('end', WrapAlignment.end),
    _buildWrapExample('spaceBetween', WrapAlignment.spaceBetween),
    _buildWrapExample('spaceAround', WrapAlignment.spaceAround),
    _buildWrapExample('spaceEvenly', WrapAlignment.spaceEvenly),
  ],
)

Widget _buildWrapExample(String title, WrapAlignment alignment) {
  return Expanded(
    child: Column(
      children: [
        Text(title),
        Container(
          height: 100,
          child: Wrap(
            alignment: alignment,
            children: List.generate(4, (index) => Chip(label: Text('$index'))),
          ),
        ),
      ],
    ),
  );
}
5)、spacing - 主轴间距
Wrap(
  spacing: 20.0, // 水平间距 20px
  children: List.generate(6, (index) => Chip(label: Text('标签$index'))),
)
6)、runAlignment - 交叉轴对齐方式
Container(
  height: 150,
  color: Colors.grey[200],
  child: Wrap(
    runAlignment: WrapAlignment.spaceBetween, // 行与行之间的对齐
    children: List.generate(8, (index) => Chip(label: Text('行$index'))),
  ),
)
7)、runSpacing - 交叉轴间距
Wrap(
  runSpacing: 16.0, // 行间距 16px
  children: List.generate(12, (index) => Chip(label: Text('标签$index'))),
)
8)、crossAxisAlignment - 交叉轴对齐
Wrap(
  crossAxisAlignment: WrapCrossAlignment.center, // 行内项目垂直居中
  children: [
    Container(height: 30, width: 60, color: Colors.red),
    Container(height: 50, width: 80, color: Colors.green),
    Container(height: 40, width: 70, color: Colors.blue),
  ],
)

有点太详细啦,实际场景还未补充完,明天继续~

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐