You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PageView.builder能否实现每页动态宽度?Airbnb式选项选择器开发求助

提问翻译

我正在开发Airbnb风格的选项选择器,已经实现了滚动效果,但没法让每个元素拥有动态尺寸。设置viewportFraction: 0.26后效果接近预期,但不够美观。我尝试过Carousel、ListView.builder,但效果都不如PageView.Builder。

更新:当前效果截图显示:选项元素宽度固定,滚动时边缘元素的显示不够自然,中心元素的突出效果不够明显


解决Airbnb风格选项选择器的动态尺寸问题

要在PageView.Builder上实现符合预期的动态尺寸效果,核心是放弃固定的viewportFraction,转而通过监听页面偏移量,动态调整每个选项元素的缩放比例与宽度,模拟Airbnb那种中心突出、两侧渐变缩小的视觉效果。以下是可直接复用的实现思路和代码:

核心实现步骤

  • 初始化不带固定viewportFraction的PageController,监听页面偏移实时更新当前页面位置
  • 结合LayoutBuilder获取父容器宽度,让元素尺寸基于父容器动态适配
  • 根据元素索引与当前页面的差值,计算每个元素的缩放比例和宽度,差值越小(越靠近中心),元素越大越突出

示例代码

class AirbnbStyleSelector extends StatefulWidget {
  final List<String> options;

  const AirbnbStyleSelector({super.key, required this.options});

  @override
  State<AirbnbStyleSelector> createState() => _AirbnbStyleSelectorState();
}

class _AirbnbStyleSelectorState extends State<AirbnbStyleSelector> {
  late PageController _pageController;
  double _currentPage = 0.0;

  @override
  void initState() {
    super.initState();
    // 不设置固定viewportFraction,让每个页面占满宽度
    _pageController = PageController()
      ..addListener(() {
        setState(() {
          _currentPage = _pageController.page ?? 0.0;
        });
      });
  }

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

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        // 定义中心元素和侧边元素的宽度占比
        final double centerWidthRatio = 0.7;
        final double sideWidthRatio = 0.5;
        final double maxWidth = constraints.maxWidth;

        return SizedBox(
          height: 90, // 根据需求调整高度
          child: PageView.builder(
            controller: _pageController,
            itemCount: widget.options.length,
            itemBuilder: (context, index) {
              // 计算当前元素与滚动偏移的差值
              final double diff = (index - _currentPage).abs();
              // 缩放比例:差值越小,缩放越大(中心元素最大为1,侧边元素最小为0.8)
              final double scale = 1 - diff * 0.2;
              // 动态计算元素宽度:从侧边宽度过渡到中心宽度
              final double itemWidth = diff < 1
                  ? lerpDouble(maxWidth * sideWidthRatio, maxWidth * centerWidthRatio, 1 - diff)!
                  : maxWidth * sideWidthRatio;

              return Center(
                child: Transform.scale(
                  scale: scale,
                  alignment: Alignment.center,
                  child: Container(
                    width: itemWidth,
                    padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
                    decoration: BoxDecoration(
                      color: diff < 0.5 ? Colors.blueAccent : Colors.grey[100],
                      borderRadius: BorderRadius.circular(24),
                    ),
                    child: Center(
                      child: Text(
                        widget.options[index],
                        style: TextStyle(
                          fontSize: diff < 0.5 ? 17 : 14,
                          fontWeight: diff < 0.5 ? FontWeight.w600 : FontWeight.normal,
                          color: diff < 0.5 ? Colors.white : Colors.black87,
                        ),
                      ),
                    ),
                  ),
                ),
              );
            },
          ),
        );
      },
    );
  }
}

参数调整建议

  • 调整centerWidthRatio和sideWidthRatio可以改变中心与侧边元素的宽度差
  • 修改scale的计算系数(示例中是0.2)可以控制缩放的渐变幅度
  • 调整容器高度、圆角、颜色等样式,完全匹配Airbnb的视觉风格

这个方案既保留了PageView.Builder流畅的滚动体验,又实现了元素的动态尺寸变化,比固定viewportFraction的效果更自然美观。

内容的提问来源于stack exchange,提问作者flutter.dev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 08:05:17