Flutter---PageView

PageView是Flutter中的一个可滑动的页面切换组件,可以让用户通过左右或者上下滑动来切换不同页面的页面。这里我们主要介绍一个案例,由PageView.builder实现,类似抖音的上下滑动视频的效果。

效果图
核心滑动组件
Dart 复制代码
PageView.builder(

        controller: _pageController,
        scrollDirection: Axis.vertical,  // 垂直滑动
        itemCount: RoleList.length,//总页数
        onPageChanged: (int index) { //页面切换回调
          setState(() {
            _currentIndex = index; //更新当前页索引
          });
        },
        itemBuilder: (context, index) { //懒加载创建页面
          return VideoPlayerItem(
            roleModel: RoleList[index],
            isActive: _currentIndex == index,  // 只有当前视频才播放
          );
        },
      ),
核心数据模型
Dart 复制代码
class RoleModel {
  final String id;           // 唯一标识(重要:用于区分不同角色)
  final String name;         // 角色名称(显示用)
  final String description;  // 角色描述(副标题)
  final String prompt;       // 角色介绍(显示在气泡中)
  String imageUrl;           // 背景图片URL
  DateTime createdAt;        // 创建时间(元数据)
  DateTime updatedAt;        // 更新时间(元数据)
}
PageView的工作原理
Dart 复制代码
┌─────────────────────────────────────────────┐
│              可见区域 (Viewport)              │
│  ┌─────────────────────────────────────┐    │
│  │         当前显示的页面                │    │
│  │         (index = 2)                 │    │
│  └─────────────────────────────────────┘    │
└─────────────────────────────────────────────┘
         ↑                    ↓
    ┌─────────┐          ┌─────────┐
    │ 页面 1  │           │ 页面 3  │
    │(预加载) │           │(预加载) │
    └─────────┘          └─────────┘
         ↑                    ↑
    ┌─────────────────────────────────┐
    │      PageView 缓存机制           │
    │  - 默认缓存前后各1页              │
    │  - 滑动时动态创建/销毁            │
    └─────────────────────────────────┘
数据流向图
Dart 复制代码
用户滑动屏幕
    ↓
PageView 检测手势
    ↓
触发 onPageChanged 回调
    ↓
setState(() { _currentIndex = index })
    ↓
重建 PageView.builder
    ↓
itemBuilder 根据新 index 创建页面
    ↓
传递 isActive 参数(_currentIndex == index)
    ↓
子页面根据 isActive 决定是否执行资源消耗操作

实现步骤

1.创建数据模型

Dart 复制代码
///角色类
class RoleModel {
  final String id;
  final String description;
  String imageUrl;
  String name;
  String prompt;
  DateTime createdAt;
  DateTime updatedAt;

  RoleModel({
    required this.id,
    required this.description,
    required this.imageUrl,
    required this.name,
    required this.prompt,
    required this.createdAt,
    required this.updatedAt,
  });

  // 从 JSON 创建对象
  factory RoleModel.fromJson(Map<String, dynamic> json) {
    return RoleModel(
      id: json["id"] ?? "",
      description: json["description"] ?? "",
      imageUrl:json["image_url"] ?? "",
      name: json["name"] ?? "",
      prompt: json["prompt"] ?? "",
      createdAt: DateTime.tryParse(json["created_at"] ?? "") ?? DateTime.now(),
      updatedAt: DateTime.tryParse(json["updated_at"] ?? "") ?? DateTime.now(),
    );
  }

  // 转换为 JSON
  Map<String, dynamic> toJson() {
    return {
      "id": id,
      "description": description,
      "image_url":imageUrl,
      "name":name,
      "prompt":prompt,
      "created_at": createdAt.toIso8601String(),
      "updated_at": updatedAt.toIso8601String(),
    };
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    return other is RoleModel && other.id == id;
  }

  @override
  int get hashCode => id.hashCode;
}

2.创建主页的主要框架

Dart 复制代码
class DemoPage extends StatefulWidget {
  const DemoPage({super.key});

  @override
  State<DemoPage> createState() => _DemoPageState();
}

class _DemoPageState extends State<DemoPage> {
  // 1. 声明数据变量
  late List<RoleModel> roleList = [];      // 角色列表数据
  late PageController _pageController;      // 页面控制器
  int _currentIndex = 0;                    // 当前页面索引

  // 2. 初始化方法
  @override
  void initState() {
    super.initState();
    _pageController = PageController(initialPage: _currentIndex);
    _loadRoleData();  // 加载数据
  }

  // 3. 释放资源
  @override
  void dispose() {
    _pageController.dispose();  // ⚠️ 必须释放
    super.dispose();
  }

  // 4. 加载数据方法(待实现)
  Future<void> _loadRoleData() async {
    // 将在第三步实现
  }

  // 5. 构建UI(待实现)
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 将在第四步实现
    );
  }
}

3.准备数据

Dart 复制代码
  List<RoleModel> _getMockData() {
    return [
      RoleModel(
        id: "1",
        name: "温柔女友",
        description: "善解人意的知心伴侣",
        prompt: "你好呀!今天过得怎么样?",
        imageUrl: "https://picsum.photos/id/20/400/600",
        createdAt: DateTime.now(),
        updatedAt: DateTime.now(),
      ),
      RoleModel(
        id: "2", 
        name: "知心姐姐",
        description: "人生导师,为你答疑解惑",
        prompt: "生活中有遇到什么困难吗?",
        imageUrl: "https://picsum.photos/id/24/400/600",
        createdAt: DateTime.now(),
        updatedAt: DateTime.now(),
      ),
      // 添加更多角色...
    ];
  }

 

4.实现核心滑动功能

Dart 复制代码
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.transparent,
        leading: IconButton(
            onPressed: (){
              Navigator.pop(context);
            },
            icon: Icon(Icons.arrow_back_ios)
        ),
        title: Column(
          children: [
            Text(practicePartner),
            Text("滑动切换角色", style: TextStyle(fontSize: 10)),
          ],
        ),
        centerTitle: true,
      ),
      backgroundColor:  Color(0xFFF3F3F3),
      body: PageView.builder(

        controller: _pageController,
        scrollDirection: Axis.vertical,  // 垂直滑动
        itemCount: RoleList.length,//总页数
        onPageChanged: (int index) { //页面切换回调
          setState(() {
            _currentIndex = index; //更新当前页索引
          });
        },
        itemBuilder: (context, index) { //懒加载创建页面
          return VideoPlayerItem(
            roleModel: RoleList[index],
            isActive: _currentIndex == index,  // 只有当前视频才播放
          );
        },
      ),

    );
  }

5.创建子页面组件

Dart 复制代码
class VideoPlayerItem extends StatefulWidget {
  final RoleModel roleModel;
  final bool isActive;

  const VideoPlayerItem({
    Key? key,
    required this.roleModel,
    required this.isActive,
  }) : super(key: key);

  @override
  State<VideoPlayerItem> createState() => _VideoPlayerItemState();
}

class _VideoPlayerItemState extends State<VideoPlayerItem> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
            height: double.infinity,
            width: double.infinity,
            decoration: BoxDecoration(
              image: DecorationImage(
                image: NetworkImage(widget.roleModel.imageUrl,), //背景图 图片未完善,先不使用图片
                fit: BoxFit.cover
              ),

            ),
            child: Container(
              margin: const EdgeInsets.symmetric(horizontal: 20),

              child: Column(
                children: [

                  //聊天列表 - 只显示最新消息
                  Expanded(child: Container()),

                  Container(//角色介绍
                      padding: const EdgeInsets.symmetric(horizontal: 10,vertical: 10),
                      decoration: BoxDecoration(
                          color: const Color(0xFF2187EF).withOpacity(0.8),borderRadius: BorderRadius.circular(15)
                      ),
                      child: RichText(
                        text: TextSpan(
                            children: [
                              WidgetSpan(child: Image.asset("assets/images/banana.png",scale: 8.9,)),
                              const TextSpan(text: ' '), //空格
                              TextSpan(
                                  text: widget.roleModel.prompt,
                                  style: TextStyle(color: Colors.white)
                              )
                            ]
                        ),
                      )
                  ),



                  const SizedBox(height: 5,),
                  Row(
                    children: [
                      //名称和介绍
                      Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [

                          Text("@${widget.roleModel.name}",style: TextStyle(color: Colors.white,fontSize: 15,fontWeight: FontWeight.bold),),

                          Text("${widget.roleModel.description}",style: TextStyle(color: Colors.black.withOpacity(0.6),fontSize: 9)),
                        ],
                      ),

                      const Spacer(),
                    ],
                  ),

                  const SizedBox(height: 10,),

                ],
              ),
            )
        )
    );
  }

}
代码实例
Dart 复制代码
import 'package:flutter/material.dart';
import 'dart:math';
import 'dart:async';

import 'package:my_flutter/role_model.dart';

class DemoPage extends StatefulWidget {
  const DemoPage({super.key});

  @override
  State<DemoPage> createState() => _DemoPageState();
}

class _DemoPageState extends State<DemoPage>{


  String practicePartner = "AI 语音助手";

  late PageController _pageController;
  int _currentIndex = 0;

  // 角色列表数据
  late List<RoleModel> RoleList = [];


  @override
  void initState() {
    super.initState();
    _pageController = PageController(initialPage: _currentIndex);
    initRoleList();
  }

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  Future<void> initRoleList() async{
    // 模拟从服务器获取数据
    await Future.delayed(Duration(milliseconds: 500));

    setState(() {
      RoleList = getMockRoleList();
    });

  }

  // 提供模拟数据
  List<RoleModel> getMockRoleList() {
    return [
      RoleModel(
        id: "1",
        name: "温柔女友",
        description: "善解人意的知心伴侣,随时倾听你的心声",
        prompt: "你好呀!我是你的温柔女友,今天过得怎么样?有什么想和我聊聊的吗?我会一直陪在你身边的~",
        imageUrl: "https://picsum.photos/id/20/400/600",
        createdAt: DateTime.now(),
        updatedAt: DateTime.now(),
      ),
      RoleModel(
        id: "2",
        name: "知心姐姐",
        description: "人生导师,为你答疑解惑,指引方向",
        prompt: "妹妹/弟弟,生活中有遇到什么困难吗?姐姐可以给你一些建议哦。记得要保持积极乐观的心态!",
        imageUrl: "https://picsum.photos/id/24/400/600",
        createdAt: DateTime.now(),
        updatedAt: DateTime.now(),
      ),
      RoleModel(
        id: "3",
        name: "专业导师",
        description: "职场专家,助你提升技能,实现梦想",
        prompt: "欢迎来到职场课堂。今天我们来聊聊如何提升专业技能,你目前在职业发展上有什么困惑吗?",
        imageUrl: "https://picsum.photos/id/26/400/600",
        createdAt: DateTime.now(),
        updatedAt: DateTime.now(),
      ),
      RoleModel(
        id: "4",
        name: "开心果",
        description: "幽默风趣,为你带来欢乐和正能量",
        prompt: "嘿嘿,今天又有什么开心事啊?让我给你讲个笑话:为什么程序员总是分不清万圣节和圣诞节?因为 Oct 31 = Dec 25!哈哈哈!",
        imageUrl: "https://picsum.photos/id/29/400/600",
        createdAt: DateTime.now(),
        updatedAt: DateTime.now(),
      ),
      RoleModel(
        id: "5",
        name: "健身教练",
        description: "专业私教,带你科学运动,塑造完美身材",
        prompt: "坚持就是胜利!今天的目标是100个俯卧撑,你准备好了吗?健身不仅能改变体型,更能磨练意志!",
        imageUrl: "https://picsum.photos/id/28/400/600",
        createdAt: DateTime.now(),
        updatedAt: DateTime.now(),
      ),
      RoleModel(
        id: "6",
        name: "心理顾问",
        description: "心理健康专家,帮你缓解压力,找回平静",
        prompt: "深呼吸,放松身心。你最近的睡眠质量如何?让我教你一个缓解压力的冥想方法。",
        imageUrl: "https://picsum.photos/id/30/400/600",
        createdAt: DateTime.now(),
        updatedAt: DateTime.now(),
      ),
    ];
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.transparent,
        leading: IconButton(
            onPressed: (){
              Navigator.pop(context);
            },
            icon: Icon(Icons.arrow_back_ios)
        ),
        title: Column(
          children: [
            Text(practicePartner),
            Text("滑动切换角色", style: TextStyle(fontSize: 10)),
          ],
        ),
        centerTitle: true,
      ),
      backgroundColor:  Color(0xFFF3F3F3),
      body: PageView.builder(

        controller: _pageController,
        scrollDirection: Axis.vertical,  // 垂直滑动
        itemCount: RoleList.length,//总页数
        onPageChanged: (int index) { //页面切换回调
          setState(() {
            _currentIndex = index; //更新当前页索引
          });
        },
        itemBuilder: (context, index) { //懒加载创建页面
          return VideoPlayerItem(
            roleModel: RoleList[index],
            isActive: _currentIndex == index,  // 只有当前视频才播放
          );
        },
      ),

    );
  }

}

//===========================子页面详情===================================
class VideoPlayerItem extends StatefulWidget {
  final RoleModel roleModel;
  final bool isActive;

  const VideoPlayerItem({
    Key? key,
    required this.roleModel,
    required this.isActive,
  }) : super(key: key);

  @override
  State<VideoPlayerItem> createState() => _VideoPlayerItemState();
}

class _VideoPlayerItemState extends State<VideoPlayerItem> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
            height: double.infinity,
            width: double.infinity,
            decoration: BoxDecoration(
              image: DecorationImage(
                image: NetworkImage(widget.roleModel.imageUrl,), //背景图 图片未完善,先不使用图片
                fit: BoxFit.cover
              ),

            ),
            child: Container(
              margin: const EdgeInsets.symmetric(horizontal: 20),

              child: Column(
                children: [

                  //聊天列表 - 只显示最新消息
                  Expanded(child: Container()),

                  Container(//角色介绍
                      padding: const EdgeInsets.symmetric(horizontal: 10,vertical: 10),
                      decoration: BoxDecoration(
                          color: const Color(0xFF2187EF).withOpacity(0.8),borderRadius: BorderRadius.circular(15)
                      ),
                      child: RichText(
                        text: TextSpan(
                            children: [
                              WidgetSpan(child: Image.asset("assets/images/banana.png",scale: 8.9,)),
                              const TextSpan(text: ' '), //空格
                              TextSpan(
                                  text: widget.roleModel.prompt,
                                  style: TextStyle(color: Colors.white)
                              )
                            ]
                        ),
                      )
                  ),



                  const SizedBox(height: 5,),
                  Row(
                    children: [
                      //名称和介绍
                      Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [

                          Text("@${widget.roleModel.name}",style: TextStyle(color: Colors.white,fontSize: 15,fontWeight: FontWeight.bold),),

                          Text("${widget.roleModel.description}",style: TextStyle(color: Colors.black.withOpacity(0.6),fontSize: 9)),
                        ],
                      ),

                      const Spacer(),
                    ],
                  ),

                  const SizedBox(height: 10,),

                ],
              ),
            )
        )
    );
  }

}
相关推荐
wuxianda103015 小时前
uniapp项目上架苹果商店4.3a被拒,3天极速解决方案2026.5.8
前端·人工智能·flutter·uni-app·ios上架·苹果上架·苹果4.3a
段子子20 小时前
【在flutter项目中使用get_cli初始化项目】
flutter
W蘭21 小时前
Flutter从入门到实战-03-综合案例实战
flutter
jiejiejiejie_1 天前
Flutter For OpenHarmony:共享元素转场的 OpenHarmony 平台实战
flutter
Zender Han1 天前
Flutter Edge-to-Edge 介绍及适配使用指南
android·flutter·ios
xmdy58661 天前
Flutter + 开源鸿蒙实战|城市智慧停车管理系统 Day4 停车订单生成+多状态管理+在线缴费+我的订单+会员中心+个人中心完善
flutter·开源·harmonyos
xmdy58661 天前
Flutter + 开源鸿蒙实战|城市智慧停车管理系统 Day8 进阶美化与真机调优篇
flutter·华为·harmonyos
Zender Han1 天前
Flutter 高斯模糊介绍与具体实现
android·flutter·ios