You need to enable JavaScript to run this app. 如何使用Kotlin实现带圆点指示器的Android自动轮播图
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Kotlin实现带圆点指示器的Android自动轮播图

Kotlin 原生实现Android带圆点指示器的自动轮播图

以下实现全靠原生组件,无需引入第三方轮播库。


1. 准备圆点状态资源

在res/drawable目录下新建两个shape文件:

  • dot_selected.xml(选中状态圆点)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="@color/white" />
    <size android:width="8dp" android:height="8dp" />
</shape>
  • dot_unselected.xml(未选中状态圆点)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="@color/gray" />
    <size android:width="8dp" android:height="8dp" />
</shape>

2. 编写布局文件

主页面布局(示例:activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/vp_banner"
        android:layout_width="match_parent"
        android:layout_height="200dp" />

    <LinearLayout
        android:id="@+id/ll_indicator"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal"
        android:paddingVertical="8dp" />

</LinearLayout>

轮播Item布局(item_banner.xml)

<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/iv_banner"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="centerCrop" />

3. 实现轮播适配器

class BannerAdapter(private val imageList: List<Int>) : RecyclerView.Adapter<BannerAdapter.BannerViewHolder>() {

    inner class BannerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val ivBanner: ImageView = itemView.findViewById(R.id.iv_banner)
    }

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

    override fun onBindViewHolder(holder: BannerViewHolder, position: Int) {
        // 无限轮播取真实位置
        val realPosition = position % imageList.size
        holder.ivBanner.setImageResource(imageList[realPosition])
        // 若加载网络图片,替换为Glide/Coil加载逻辑即可
    }

    override fun getItemCount(): Int {
        // 返回Int最大值实现虚拟无限轮播
        return Int.MAX_VALUE
    }
}

4. Activity中完成逻辑绑定

class MainActivity : AppCompatActivity() {
    // 替换为你的轮播图片资源列表
    private val bannerImages = listOf(R.drawable.banner1, R.drawable.banner2, R.drawable.banner3)
    private lateinit var vpBanner: ViewPager2
    private lateinit var llIndicator: LinearLayout
    private val bannerScope = CoroutineScope(Dispatchers.Main)
    private var currentPos = 0

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

        vpBanner = findViewById(R.id.vp_banner)
        llIndicator = findViewById(R.id.ll_indicator)

        initIndicator()
        initBanner()
        startAutoScroll()
    }

    private fun initIndicator() {
        llIndicator.removeAllViews()
        for (i in bannerImages.indices) {
            val dot = ImageView(this)
            val params = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
            params.marginStart = 4.dpToPx()
            params.marginEnd = 4.dpToPx()
            dot.layoutParams = params
            // 默认第一个圆点选中
            dot.setImageDrawable(if (i == 0) getDrawable(R.drawable.dot_selected) else getDrawable(R.drawable.dot_unselected))
            llIndicator.addView(dot)
        }
    }

    private fun initBanner() {
        val adapter = BannerAdapter(bannerImages)
        vpBanner.adapter = adapter
        // 初始位置设到中间,避免用户一开始滑到边界
        vpBanner.setCurrentItem(bannerImages.size * 100, false)
        // 监听页面切换更新指示器
        vpBanner.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
                super.onPageSelected(position)
                currentPos = position
                val realPos = position % bannerImages.size
                // 更新所有圆点状态
                for (i in bannerImages.indices) {
                    val dot = llIndicator.getChildAt(i) as ImageView
                    dot.setImageDrawable(if (i == realPos) getDrawable(R.drawable.dot_selected) else getDrawable(R.drawable.dot_unselected))
                }
            }
        })
    }

    private fun startAutoScroll() {
        bannerScope.launch {
            while (isActive) {
                delay(3000) // 3秒切换一次,可自行调整
                currentPos += 1
                vpBanner.setCurrentItem(currentPos, true)
            }
        }
    }

    // dp转px扩展方法
    private fun Int.dpToPx(): Int {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), resources.displayMetrics).toInt()
    }

    override fun onDestroy() {
        super.onDestroy()
        // 销毁时停止协程,避免内存泄漏
        bannerScope.cancel()
    }
}

如果需要支持手势滑动暂停轮播,可在OnPageChangeCallback的onPageScrollStateChanged方法中监听拖拽状态,临时停止轮播,松手后再恢复即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.18 20:54:06