今天的内容是去实现仓库详情页,即在仓库列表页面,点击某个仓库后,展示其详情页面。不过为了实现该功能,我们需要了解一些前置知识。

一、页面导航

在 flutter 中,我们想要从一个页面跳转到另一个页面,可以借助Navigator来实现界面之间的跳转。

1. Navigator 介绍

Navigator 是 flutter 中页面导航的核心组件,基于栈结构。通过push入栈打开新的页面。通过pop弹出当前页面,从而返回上一个页面。

Navigator 不但能管理页面之间的导航逻辑,还可以处理页面之间的参数传递、页面返回值处理、以及路由动画等功能。

2. Navigator 核心方法

Navigator 的导航分为基本导航方式和命名路由方式,由于我们没有跨模块,只在当前模块跳转界面,所以只需要使用基本导航即可。

// 打开页面
Navigator.push(context, MaterialPageRoute(builder: (context) => NewScreen()));

// 关闭当前页面
Navigator.pop(context);

// 关闭当前页面并返回数据
Navigator.pop(context, "返回的数据");

// 使用替换的方式打开新页面
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => NewScreen()));

// 清空当前栈中的页面,再打开新页面
Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (context) => NewScreen()), (route) => false);

在跳转页面时,还有一个重要的功能就是传递参数。

// 传递参数
Navigator.push(context,
  MaterialPageRoute(builder: (context) => NewScreen('要传递的参数'))
);

// 接收方
class NewScreen extends StatefulWidget {
  final XXX param;// 接受参数
  
  const NewScreen({Key? key, required this.param}) : super(key: key);
  
  @override
  State<NewScreen> createState() => _NewScreenState();
}

我们编写一个page/repo/RepoDetailTestScreen.dart页面,内容非常简单,就是展示接受传递的参数,然后显示在界面上。

import 'package:flutter/material.dart';

class RepoDetailTestScreen extends StatefulWidget {
  final String param;// 接受一个 String 类型的参数

  const RepoDetailTestScreen({super.key, required this.param});

  @override
  State<RepoDetailTestScreen> createState() => _RepoDetailTestScreenState();
}

class _RepoDetailTestScreenState extends State<RepoDetailTestScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('仓库详情测试'),
      ),
      body: Center(
        child: Text('传入的参数: ${widget.param}'),
      ),
    );
  }
}

然后使用我们的测试页面,把 Button 点击事件中的代码删除,替换成 Navigator。

const param = "Hello Test Page";
Navigator.push(context, MaterialPageRoute(builder: (context) => RepoDetailTestScreen(param: param)));

如图所示,可以界面可以正常打开,而且参数接受正常。

二、编写仓库详情页

仓库详情页稍微比较复杂,这一章我们先实现上半部分的功能。

1. 仓库详细信息展示

我们在仓库列表页,点击某个 item 后,跳转到仓库详情页,并且把当前点击的仓库实体类传递到仓库详情页,并且展示仓库的一些信息。

然后我们在仓库详情页,接受这个数据。

从上到下,我们分别展示项目名称,项目描述,项目语言,项目默认分支,项目所有者信息,项目命名空间 path,以及项目对应的 start、fork、issues 数量。

由于手机屏幕空间有限,我们设计一个可折叠项,可以将 start、fork、issues 进行折叠。如下图所示,分别是折叠和打开的截图。而且为了提高用户体验,我们使用动画来折叠和打开。

2. 使用动画

我们借助 SizeTransition 内置组件来实现动画效果,该组件可以通过axis属性来设置大小改变的方向,例如:设置Axis.vertical表示垂直方向。使用sizeFactor设置动画对象。child属性表示子组件,即动画要控制的组件。axisAlignment表示动画起始方向,-1.0 在垂直方向表示从顶部开始,从上往下展开。

// 折叠相关状态,控制当前状态是打开还是折叠
bool _isStatsExpanded = true;
// 动画控制器,控制动画启动和暂停
late AnimationController _expandController;
// 动画实例对象
late Animation<double> _expandAnimation;

// 初始化动画控制器
_expandController = AnimationController(
  vsync: this,
  duration: const Duration(milliseconds: 300),
  value: _isStatsExpanded ? 1.0 : 0.0,
);
// 初始化动画对象,使用 Curves.easeInOut 效果
_expandAnimation = _expandController.drive(CurveTween(curve: Curves.easeInOut));

// 使用 SizeTransition 动画组件,根据动画改变组件大小。
SizeTransition(
  axis: Axis.vertical,
  // 折叠时从下往上隐藏,打开时从上往下显示
  axisAlignment: -1.0,
  sizeFactor: _expandAnimation,
  child: Padding(
    padding: const EdgeInsets.only(top: 16),
    child: Column(
      children: [
        // 第一行:Watch、Star
        Row(
          children: [
            Expanded(
              child: _buildStatItemWithBorder('Star', repo.stargazersCount),
            ),
          ],
        ),
        // 第二行:Fork、Pull Requests
        Padding(
          padding: const EdgeInsets.only(top: 12),
          child: Row(
            children: [
              Expanded(
                child: _buildStatItemWithBorder('Fork', repo.forksCount),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: _buildStatItemWithBorder('Issues', repo.openIssuesCount),
              ),
            ],
          ),
        ),
      ],
    ),
  ),
),

此时如果你的代码可能有报错,如截图所示:

这个是因为参数需要一个TickerProvider实例对象,而我们当前类不是这个类型。所以我们需要让我们的类集成TickerProvider接口。但是由于我们的类已经extends State,所以需要使用 with 来实现多继承。

class _RepoDetailScreenState extends State<RepoDetailScreen> with TickerProviderStateMixin

最终实现效果如下:

三、报错问题

如上图所示,报错是因为动画的组件使用错误导致的问题。

1. TickerProvider

Ticker对象是系统提供的动画组件。而 TickerProvider 是一个接口,负责管理Ticker实例对象,他可以自动管理 Ticker 的生命周期,确保动画在组件可见时执行,防止资源浪费。

2. SingleTickerProviderStateMixin 和 TickerProviderStateMixin

SingleTickerProviderStateMixin 可以为 State 类提供创建单个Ticker的能力。适用于页面只需要一个动画控制器的情况。

TickerProviderStateMixin 可以为 State 类提供创建多个Ticker的能力。如果页面中需要多个动画控制器,则建议使用这个。

我们的页面只有一个折叠和打开的动画,所以刚开始我使用的是 SingleTickerProviderStateMixin。运行后就会出现上图的错误。经过查询,出现改问题是因为我们项目里虽然只有折叠和打开一个动画,但是还有一个 TabController,他在切换页面的时候内部也会执行动画,所以 TabController + 折叠、打开动画,就需要使用 TickerProviderStateMixin 了。

Logo

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

更多推荐