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

Flutter自定义彩虹式弧形水平滚动ListView开发需求

Flutter 实现弧形彩虹式水平滚动 ListView

要实现选中元素平直、相邻元素呈弧形排列的水平滚动列表,可以通过滚动监听+动态变换的方式实现,核心思路是根据每个item与屏幕中心的偏移量,为其应用不同的旋转变换,模拟彩虹弧形效果。

完整实现代码

import 'package:flutter/material.dart';

class CurvedRainbowListView extends StatefulWidget {
  final List<String> items;

  const CurvedRainbowListView({super.key, required this.items});

  @override
  State<CurvedRainbowListView> createState() => _CurvedRainbowListViewState();
}

class _CurvedRainbowListViewState extends State<CurvedRainbowListView> {
  final ScrollController _scrollController = ScrollController();
  double _currentOffset = 0.0;
  final double _itemWidth = 120.0; // 单个item宽度
  final double _curveRadius = 200.0; // 弧形半径,值越小弯曲越明显

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(() {
      setState(() => _currentOffset = _scrollController.offset);
    });
  }

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

  // 计算item的变换矩阵
  Matrix4 _getItemTransform(int index) {
    // 计算item中心与屏幕中心的偏移距离
    final itemCenter = index * _itemWidth + _itemWidth / 2;
    final screenCenter = _currentOffset + MediaQuery.of(context).size.width / 2;
    final offsetFromCenter = itemCenter - screenCenter;

    // 处于屏幕中心的item(选中态)保持无变换
    if (offsetFromCenter.abs() < _itemWidth / 2) {
      return Matrix4.identity();
    }

    // 根据偏移距离计算弯曲角度和位移
    final normalizedOffset = offsetFromCenter / _curveRadius;
    final bendAngle = normalizedOffset.abs() * 0.3; // 控制弯曲角度(弧度)

    if (offsetFromCenter > 0) {
      // 右侧item:向下弯曲
      return Matrix4.rotationX(bendAngle)..translate(0, normalizedOffset * 20, 0);
    } else {
      // 左侧item:向上弯曲
      return Matrix4.rotationX(-bendAngle)..translate(0, normalizedOffset * 20, 0);
    }
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 200, // 容器高度需足够容纳弧形item,避免截断
      child: ListView.builder(
        controller: _scrollController,
        scrollDirection: Axis.horizontal,
        // 左右padding让第一个/最后一个item能滚动到屏幕中心
        padding: EdgeInsets.symmetric(horizontal: MediaQuery.of(context).size.width / 2 - _itemWidth / 2),
        itemCount: widget.items.length,
        itemBuilder: (context, index) {
          return Transform(
            transform: _getItemTransform(index),
            origin: Offset(_itemWidth / 2, 100), // 变换原点,控制弧形的旋转中心
            child: Container(
              width: _itemWidth,
              margin: const EdgeInsets.symmetric(horizontal: 8),
              decoration: BoxDecoration(
                color: Colors.primaries[index % Colors.primaries.length],
                borderRadius: BorderRadius.circular(12),
              ),
              child: Center(
                child: Text(
                  widget.items[index],
                  style: const TextStyle(color: Colors.white, fontSize: 16),
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

// 使用示例
void main() {
  runApp(const MaterialApp(
    home: Scaffold(
      body: Center(
        child: CurvedRainbowListView(
          items: ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6', 'Item 7', 'Item 8'],
        ),
      ),
    ),
  ));
}

关键实现细节

  • 滚动监听:通过ScrollController实时获取滚动偏移量,触发UI更新
  • 动态变换:_getItemTransform方法根据item与屏幕中心的距离,为左右两侧item应用不同方向的旋转变换,模拟彩虹弧形
  • 变换原点:Transform的origin参数设置为item底部中心,让item围绕该点旋转,更贴合彩虹的弧形效果
  • 参数调整:修改_curveRadius(弧形半径)、bendAngle(弯曲角度)可以调整弧形的弯曲程度,修改_itemWidth可以适配不同大小的item

注意事项

  • 容器高度需要设置足够大,避免弧形item被截断
  • 若需要更顺滑的滚动体验,可以添加自定义ScrollPhysics
  • 可根据需求扩展item的样式,比如添加选中态高亮、阴影等效果

内容的提问来源于stack exchange,提问作者ADARSH K VINOD

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 08:30:59