Flutter那些事-PageView

Flutter PageView组件详解

一、什么是PageView?

PageView是一个可以左右滑动切换页面的滚动组件,类似于Android的ViewPager或iOS的UIScrollView,常用于实现轮播图、引导页、标签页等场景。

二、基本用法

1. 基础构造函数

less 复制代码
PageView(
  children: [
    Container(color: Colors.red),
    Container(color: Colors.green),
    Container(color: Colors.blue),
  ],
)

2. PageView.builder(常用)

dart 复制代码
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage());
}
class MainPage extends StatefulWidget {
  MainPage({Key? key}) : super(key: key);

  @override
  _MainPageState createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
       home: Scaffold(
        appBar: AppBar(
          title: Text("PageView"),
        ),
        body: PageView.builder(itemBuilder: (BuildContext context,int index){
          return Container(
            child: Text("页面 $index"),
          );
        }),
        
       ),
    );
  }
}

执行效果如下:

3. PageView.custom

dart 复制代码
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage());
}
class MainPage extends StatefulWidget {
  MainPage({Key? key}) : super(key: key);

  @override
  _MainPageState createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
       home: Scaffold(
        appBar: AppBar(
          title: Text("PageView"),
        ),
        body: PageView.custom(childrenDelegate: SliverChildBuilderDelegate((context, index) => Container(child: Text('Item$index'),),
          childCount: 10
        )
          
        ),
        
       ),
    );
  }
}

三、核心属性详解

属性 类型 说明
controller PageController 控制页面跳转、获取当前页面
children List 子页面列表
scrollDirection Axis 滚动方向(horizontal/vertical)
physics ScrollPhysics 滚动物理效果
pageSnapping bool 是否锁定到整页(默认true)
onPageChanged ValueChanged 页面切换回调
allowImplicitScrolling bool 是否允许隐式滚动
padEnds bool 是否填充两端(默认true)

四、PageController详解

常用方法

less 复制代码
PageController _controller = PageController(
  initialPage: 0,        // 初始页面索引
  keepPage: true,        // 是否保持页面状态
  viewportFraction: 0.8, // 视口占比(0.8表示一屏显示80%)
);

// 跳转到指定页(带动画)
_controller.animateToPage(
  2,
  duration: Duration(milliseconds: 300),
  curve: Curves.easeInOut,
);

// 跳转到指定页(无动画)
_controller.jumpToPage(2);

// 下一页
_controller.nextPage(
  duration: Duration(milliseconds: 300),
  curve: Curves.easeInOut,
);

// 上一页
_controller.previousPage(
  duration: Duration(milliseconds: 300),
  curve: Curves.easeInOut,
);

// 获取当前页面
int currentPage = _controller.page?.round() ?? 0;

五、实际应用场景

1. 轮播图(带自动播放)

less 复制代码
class CarouselDemo extends StatefulWidget {
  @override
  _CarouselDemoState createState() => _CarouselDemoState();
}

class _CarouselDemoState extends State<CarouselDemo> {
  late PageController _controller;
  int _currentPage = 0;
  late Timer _timer;

  @override
  void initState() {
    super.initState();
    _controller = PageController();
    _startAutoPlay();
  }

  void _startAutoPlay() {
    _timer = Timer.periodic(Duration(seconds: 3), (timer) {
      if (_controller.hasClients) {
        int nextPage = (_currentPage + 1) % 10;
        _controller.animateToPage(
          nextPage,
          duration: Duration(milliseconds: 300),
          curve: Curves.easeInOut,
        );
      }
    });
  }

  @override
  void dispose() {
    _timer.cancel();
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        PageView.builder(
          controller: _controller,
          onPageChanged: (page) => setState(() => _currentPage = page),
          itemCount: 10,
          itemBuilder: (context, index) => Image.network(
            'https://picsum.photos/id/${index + 1}/400/300',
            fit: BoxFit.cover,
          ),
        ),
        Positioned(
          bottom: 10,
          left: 0,
          right: 0,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: List.generate(10, (index) => Container(
              margin: EdgeInsets.symmetric(horizontal: 4),
              width: 8,
              height: 8,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: _currentPage == index ? Colors.white : Colors.grey,
              ),
            )),
          ),
        ),
      ],
    );
  }
}

2. 引导页(Onboarding)

dart

复制下载

less 复制代码
class OnboardingPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        onPageChanged: (index) {
          if (index == 2) {
            // 最后一页,显示按钮
          }
        },
        children: [
          OnboardingItem(
            title: '欢迎使用',
            description: '发现精彩内容',
            icon: Icons.favorite,
          ),
          OnboardingItem(
            title: '探索世界',
            description: '连接全球用户',
            icon: Icons.public,
          ),
          OnboardingItem(
            title: '开始体验',
            description: '立即加入我们',
            icon: Icons.start,
          ),
        ],
      ),
    );
  }
}

3. 垂直滚动页面(类似抖音)

dart

复制下载

less 复制代码
PageView(
  scrollDirection: Axis.vertical,  // 垂直方向
  children: [
    VideoPlayerWidget(videoUrl: 'url1'),
    VideoPlayerWidget(videoUrl: 'url2'),
    VideoPlayerWidget(videoUrl: 'url3'),
  ],
)

4. 部分可见效果(类似卡片轮播)

dart

复制下载

less 复制代码
PageView.builder(
  controller: PageController(viewportFraction: 0.85),
  itemCount: images.length,
  itemBuilder: (context, index) {
    return Container(
      margin: EdgeInsets.symmetric(horizontal: 8),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(20),
        image: DecorationImage(
          image: NetworkImage(images[index]),
          fit: BoxFit.cover,
        ),
      ),
    );
  },
)

六、性能优化技巧

1. 使用 builder 而非 children

dart

复制下载

scss 复制代码
// ❌ 不推荐:一次性创建所有页面
PageView(children: List.generate(100, (index) => MyPage()))

// ✅ 推荐:按需创建
PageView.builder(itemCount: 100, itemBuilder: (context, index) => MyPage())

2. 缓存页面状态

dart

复制下载

less 复制代码
PageView(
  controller: PageController(keepPage: true),  // 保持页面状态
  children: [...],
)

3. 懒加载图片

dart

复制下载

css 复制代码
PageView.builder(
  itemBuilder: (context, index) {
    return FutureBuilder(
      future: precacheImage(NetworkImage(urls[index]), context),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          return Image.network(urls[index]);
        }
        return Center(child: CircularProgressIndicator());
      },
    );
  },
)

七、常见问题和解决方案

问题1:PageView 高度自适应

dart

复制下载

less 复制代码
// 解决方案:使用 ConstrainedBox
ConstrainedBox(
  constraints: BoxConstraints(maxHeight: 300),
  child: PageView(children: [...]),
)

问题2:嵌套滚动冲突

dart

复制下载

less 复制代码
PageView(
  physics: NeverScrollableScrollPhysics(),  // 禁用PageView滚动
  children: [
    ListView(),  // 让ListView自己滚动
  ],
)

问题3:页面切换时保持状态

dart

复制下载

scala 复制代码
PageView(
  children: [
    AutomaticKeepAliveClientWrapper(child: MyPage1()),
    AutomaticKeepAliveClientWrapper(child: MyPage2()),
  ],
)

class AutomaticKeepAliveClientWrapper extends StatefulWidget {
  final Widget child;
  const AutomaticKeepAliveClientWrapper({required this.child});
  
  @override
  _AutomaticKeepAliveClientWrapperState createState() => 
      _AutomaticKeepAliveClientWrapperState();
}

class _AutomaticKeepAliveClientWrapperState 
    extends State<AutomaticKeepAliveClientWrapper> 
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;
  
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return widget.child;
  }
}

八、与其他组件的对比

组件 适用场景 特点
PageView 整页滑动、轮播图 自带滑动动画,性能好
TabBarView 配合TabBar使用 需要与TabBar联动
ListView 列表数据 支持多种item类型
SingleChildScrollView 单个可滚动内容 简单内容滚动

九、最佳实践总结

  1. 大量页面使用 PageView.builder,避免内存问题
  2. 及时释放 PageController,在 dispose 中调用
  3. 自动轮播时检查 hasClients,避免空指针
  4. 使用 AutomaticKeepAliveClientMixin 保持页面状态
  5. 合理设置 viewportFraction 实现特殊效果
  6. 注意嵌套滚动,设置正确的 physics
相关推荐
程序员老刘3 小时前
跑分第一的编程大模型,我为啥不用?
flutter·ai编程·vibecoding
恋猫de小郭18 小时前
苹果 AirPods 协议,Android 也可以使用完整版 AirPods 能力
android·前端·flutter
张风捷特烈19 小时前
Flutter 类库大揭秘#01 | path_provider架构与设计
android·flutter
恋猫de小郭3 天前
Android 限制侧载新进展,谷歌联合国内厂商推验证计划
android·前端·flutter
恋猫de小郭3 天前
解读 Android 17 全新内存限制,有没有“豁免”后门?
android·前端·flutter
程序员老刘6 天前
跨平台开发地图 | 2026年6月
flutter·ai编程·客户端
悟空瞎说6 天前
Flutter 架构详解:新手必懂底层原理
flutter
SoaringHeart7 天前
Flutter最佳实践:IM聊天文字链接自动识别跳转
前端·flutter
恋猫de小郭7 天前
KMP / CMP 鸿蒙版本 Beta 发布,他有什么特别之处?
android·前端·flutter
风华圆舞8 天前
Flutter + 鸿蒙 Intents Kit:页面直达能力的完整接入方案
flutter·ui·华为·harmonyos