如何实现自动滚动的无限循环横向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
相关产品推荐
相关产品推荐

