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

如何实现自动滚动的无限循环横向ListView?

实现横向无限自动滚动的ListView

Flutter 实现方案

核心思路

  • 构建无限循环列表:通过ListView.builder对索引取模,复用原始数据源实现视觉上的无限滚动
  • 匀速自动滚动:用AnimationController配合ScrollController,以固定像素速度驱动滚动,而非跳转到指定条目
  • 无缝循环衔接:监听滚动位置,当接近原始数据源末尾时,重置滚动位置到起始点,保持视觉连续性

代码示例

import 'package:flutter/material.dart';

class InfiniteHorizontalScroll extends StatefulWidget {
  final List<Widget> items;
  final double scrollSpeed; // 滚动速度(像素/秒)

  const InfiniteHorizontalScroll({
    super.key,
    required this.items,
    this.scrollSpeed = 100.0,
  });

  @override
  State<InfiniteHorizontalScroll> createState() => _InfiniteHorizontalScrollState();
}

class _InfiniteHorizontalScrollState extends State<InfiniteHorizontalScroll> with SingleTickerProviderStateMixin {
  late ScrollController _scrollController;
  late AnimationController _animationController;
  late double _totalItemWidth; // 所有原始条目的总宽度

  @override
  void initState() {
    super.initState();
    _scrollController = ScrollController();
    // 假设每个条目宽度150,实际可通过LayoutBuilder动态获取
    _totalItemWidth = widget.items.length * 150;
    // 根据总宽度和速度计算滚动时长
    _animationController = AnimationController(
      vsync: this,
      duration: Duration(seconds: (_totalItemWidth / widget.scrollSpeed).round()),
    )..repeat();

    // 绑定动画到滚动逻辑
    _animationController.addListener(() {
      if (_scrollController.hasClients) {
        final scrollPosition = _animationController.value * _totalItemWidth;
        _scrollController.jumpTo(scrollPosition);

        // 接近末尾时重置,实现无缝循环
        if (scrollPosition >= _totalItemWidth - 100) {
          _animationController.reset();
          _scrollController.jumpTo(0);
        }
      }
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: _scrollController,
      scrollDirection: Axis.horizontal,
      itemCount: widget.items.length * 3, // 重复3次确保衔接自然
      itemBuilder: (context, index) {
        return widget.items[index % widget.items.length];
      },
    );
  }
}

// 使用示例
// InfiniteHorizontalScroll(
//   items: [
//     Container(width: 150, color: Colors.red, child: Center(child: Text('Item 1'))),
//     Container(width: 150, color: Colors.blue, child: Center(child: Text('Item 2'))),
//     Container(width: 150, color: Colors.green, child: Center(child: Text('Item 3'))),
//   ],
//   scrollSpeed: 150,
// )

Android 原生实现方案

核心思路

  • 用RecyclerView实现横向列表,通过Adapter返回极大条目数(Integer.MAX_VALUE),对真实数据源索引取模实现无限循环
  • 使用ValueAnimator控制滚动像素偏移,实现匀速滚动
  • 监听滚动事件,当滚动到真实数据源末尾时,平滑跳转至起始位置,保持循环

代码示例

布局文件(activity_main.xml)

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/horizontalRecyclerView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"/>

Kotlin 代码实现

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.animation.ValueAnimator

class MainActivity : AppCompatActivity() {
    private lateinit var recyclerView: RecyclerView
    private lateinit var adapter: InfiniteHorizontalAdapter
    private val items = listOf("Item 1", "Item 2", "Item 3", "Item 4")
    private val scrollSpeed = 100 // 滚动速度(像素/秒)
    private var totalItemWidth = 0
    private lateinit var scrollAnimator: ValueAnimator

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        recyclerView = findViewById(R.id.horizontalRecyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
        adapter = InfiniteHorizontalAdapter(items)
        recyclerView.adapter = adapter

        // 列表渲染后计算总宽度
        recyclerView.post {
            val itemWidth = recyclerView.getChildAt(0).width
            totalItemWidth = items.size * itemWidth
            startAutoScroll()
        }
    }

    private fun startAutoScroll() {
        scrollAnimator = ValueAnimator.ofInt(0, totalItemWidth).apply {
            duration = (totalItemWidth * 1000 / scrollSpeed).toLong() // 转换为毫秒
            repeatCount = ValueAnimator.INFINITE
            addUpdateListener { animation ->
                val scrollPos = animation.animatedValue as Int
                recyclerView.scrollBy(scrollPos - recyclerView.scrollX, 0)

                // 接近末尾时跳转回起始位置
                if (recyclerView.scrollX >= totalItemWidth - 200) {
                    recyclerView.scrollToPosition(0)
                }
            }
            start()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        scrollAnimator.cancel()
    }
}

class InfiniteHorizontalAdapter(private val items: List<String>) : RecyclerView.Adapter<InfiniteHorizontalAdapter.ViewHolder>() {
    override fun getItemCount(): Int = Integer.MAX_VALUE

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(android.R.layout.simple_list_item_1, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.textView.text = items[position % items.size]
    }

    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val textView: TextView = itemView.findViewById(android.R.id.text1)
    }
}

关键注意点

  • 滚动速度可通过调整scrollSpeed参数控制,单位为像素/秒
  • 循环阈值(代码中的100、200)需根据条目宽度调整,避免跳转卡顿
  • 若条目宽度不固定,需通过LayoutBuilder(Flutter)或View.post(Android)动态计算总宽度

内容的提问来源于stack exchange,提问作者imamal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 21:37:34