Flutter那些事-PageView

0 阅读3分钟

Flutter PageView组件详解

一、什么是PageView?

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

二、基本用法

1. 基础构造函数

PageView(
  children: [
    Container(color: Colors.red),
    Container(color: Colors.green),
    Container(color: Colors.blue),
  ],
)

2. PageView.builder(常用)

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"),
          );
        }),
        
       ),
    );
  }
}

执行效果如下:

image.png

3. PageView.custom

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
        )
          
        ),
        
       ),
    );
  }
}

三、核心属性详解

属性类型说明
controllerPageController控制页面跳转、获取当前页面
childrenList子页面列表
scrollDirectionAxis滚动方向(horizontal/vertical)
physicsScrollPhysics滚动物理效果
pageSnappingbool是否锁定到整页(默认true)
onPageChangedValueChanged页面切换回调
allowImplicitScrollingbool是否允许隐式滚动
padEndsbool是否填充两端(默认true)

四、PageController详解

常用方法

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. 轮播图(带自动播放)

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

复制下载

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

复制下载

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

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

dart

复制下载

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

复制下载

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

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

2. 缓存页面状态

dart

复制下载

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

3. 懒加载图片

dart

复制下载

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

复制下载

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

问题2:嵌套滚动冲突

dart

复制下载

PageView(
  physics: NeverScrollableScrollPhysics(),  // 禁用PageView滚动
  children: [
    ListView(),  // 让ListView自己滚动
  ],
)

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

dart

复制下载

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