小工具知识网

当前位置: 首页 > 行业知识 > 【Android】ViewPager2+TabLayout实现无限轮播图

行业知识

【Android】ViewPager2+TabLayout实现无限轮播图

2026-11-05 14:39:43 来源:互联网转载

文章目录

  • 需求分析
  • ViewPager2
    • 如何使用
    • 如何无限循环
    • 如何自动滑动
    • TabLayout实现指示器
    • 封装成组件

      需求分析

      • 需要无限循环向右轮播,间隔为3S
      • 支持手动向左或向右滑动
      • 支持点击跳转目标页面
      • 支持关闭

        ViewPager2

        ViewPager2是ViewPager的升级版本并且在功能和性能上有所提升,ViewPager2主要用在APP中进行页面切换。

        ViewPager2本身支持左右滑动,且兼容嵌套滑动容器,这两点为我们的轮播图减少了不少工作量。

        如何使用

        ViewPager2内部实现依靠的是RecyclerView,所以它的使用方法和RecyclerView是一模一样的。都需要定义Adapter、ViewHolder。

                                    

        上面的xml代码,定义了我们轮播图的骨架,由于关闭按钮比较简单,这里省略了。

         

        上面的代码定义了每一个轮播图的Item布局,在我们这个需求中只需要展示图片即可。

        // PagerViewHolder.ktclass PagerViewHolder(    private val bind: LayoutBannerItemBinding,    private val onClickListener:((bean: BannerData)->Unit)?=null) : RecyclerView.ViewHolder(bind.root) {        /**         * @param bean BannerData 渲染每个轮播图Item的数据源         */        fun render(bean: BannerData) {            // todo 替换图片加载            bind.imageView.setBackgroundColor(Color.BLUE)            bind.imageView.setOnClickListener {                onClickListener?.invoke(bean)            }        }    }
        // BannerAdapter.ktclass BannerAdapter(val list: MutableList) :    RecyclerView.Adapter() {    private var onClickListener:((bannerData: BannerData)->Unit)?=null    fun setOnClickListener(onClickListener:((bannerData: BannerData)->Unit)?=null){        this.onClickListener=onClickListener    }    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PagerViewHolder {            return PagerViewHolder(                LayoutBannerItemBinding.inflate(                    LayoutInflater.from(parent.context),                    parent,                    false                )            ){                onClickListener?.invoke(it)            }        }    override fun getItemCount() = list.size    override fun onBindViewHolder(holder: PagerViewHolder, position: Int) {        holder.render(list[position])    }}        

        如何无限循环

        到这里ViewPager虽然可以正常显示了,但是在使用的时候我们会发现,虽然可以左右滑,但是滑到最后一张的时候,不能继续左滑,或者在第一张的时候,不可以右滑。

        解决这个问题,需要在数据源的首位插入2张图片,详细见下图:

        当我们滑动到最右边的3时,也就是理论上的最后一张,此时继续左滑就可以滑动到最右边的1,营造一种进入循环的假象。此时需要调用viewPager的setCurrentItem手动把当前的Item移动到左边的1,如下图所示:

        同样的道理,如果当前处于最左边的1时,此时向右滑,会滑动到最左边的3,这时候需要再次调用setCurrentItem移动到最右边的3

        对上面的理论进行代码实践:

        //BannerAdapter.ktclass BannerAdapter {    // ...        /**     * @param data List : 更新的全量数据     * 图片大于一张时,需要在首尾插入图片达到无限轮播的目的     */    fun updateAllList(data: List) {        list.clear()        if (data.isNotEmpty()) {            if (data.size > 1) {                list.add(data[data.size - 1])            }            list.addAll(data)            if (data.size > 1) {                list.add(data[0])            }        }        notifyDataSetChanged()    }        // ...}

        首先是为Adapter新增一个更新数据的方法,这个方法当图片大于一张的时候,会为首尾插入需要的图片。下一步就是要监听viewPager的页面变化,这里是ViewPager2.OnPageChangeCallback

        val pageChangeCallback =  object : ViewPager2.OnPageChangeCallback(){       override fun onPageSelected(position: Int) {        super.onPageSelected(position)        // todo 当前ViewPager切换时,同时计算tab应该如何切换    }    override fun onPageScrollStateChanged(state: Int) {        super.onPageScrollStateChanged(state)                // 当滑动到最右边时,需要移动到index1的位置        val total = (binding.viewPager.adapter?.itemCount ?: 0)        if (state == ViewPager2.SCROLL_STATE_IDLE && total > 1 && binding.viewPager.currentItem == total - 1) {            binding.viewPager.setCurrentItem(1, false)        }                // 当滑动到最左边的时候,需要移动到index = total-2的位置        if (state == ViewPager2.SCROLL_STATE_IDLE && total > 1 &&  binding.viewPager.currentItem == 0) {            binding.viewPager.setCurrentItem(total - 2, false)        }    }}

        在onPageScrollStateChanged处理每次滑动完以后的事件,并移动到相应的位置。其中SCROLL_STATE_IDLE是等page稳定以后在执行,这样移动page会非常无感。

        如何自动滑动

        想让轮播图自己动起来比较简单,只需要启动定时器或者Handler就可以达到这个目的,这部分的代码放到后面一起展示。

        TabLayout实现指示器

        TabLayout主要是用于实现选项卡式布局,经常与ViewPager搭配使用,其中TabLayout还提供setupWithViewPager(ViewPager)方法用于自动与ViewPager绑定。

        我们在layout_banner.xml文件中已经定义了TabLayout在布局中的代码了。接下来就是如何绑定,如果正常使用ViewPager只需要调用setupWithViewPager就可以自动绑定了,但是由于我们要达到无限循环的效果,在ViewPager首尾添加了重复数据,所以我们如果直接用setupWithViewPager那么TabLayout就会多两个Tab。

        // 初始化下方圆点指示器binding.indicatorLayout.removeAllTabs()for (i in 0 until data.size) {    val tab = binding.indicatorLayout.newTab()    // 禁用tab点击事件    tab.view.isEnabled = false    binding.indicatorLayout.addTab(tab)}
        val pageChangeCallback =  object : ViewPager2.OnPageChangeCallback(){    private var lastPage=-1    override fun onPageSelected(position: Int) {        super.onPageSelected(position)        // 当前ViewPager切换时,同时计算tab应该如何切换        val total = (binding.viewPager.adapter?.itemCount ?: 0)        val curPosition = if (total > 1 && position >= 1 && position     private var binding: LayoutBannerBinding    private var bannerAdapter:BannerAdapter    // 每个页面之间的延迟时间(毫秒)    private val DELAY_TIME_MS: Long = 3000    private val viewPagerHandler = Handler(Looper.getMainLooper())    private var viewPagerRunnable:Runnable    private var listener:((currentPage:Int)-Unit)?=null    init {        binding=LayoutBannerBinding.inflate(LayoutInflater.from(context),this)        bannerAdapter= BannerAdapter(mutableListOf())        binding.viewPager.adapter=bannerAdapter        val pageChangeCallback =  object : ViewPager2.OnPageChangeCallback(){                  override fun onPageSelected(position: Int) {                // 参上            }            override fun onPageScrollStateChanged(state: Int) {                // 参上            }        }        binding.viewPager.registerOnPageChangeCallback(pageChangeCallback)       viewPagerRunnable = object : Runnable {            override fun run() {                binding.viewPager.apply {                    if (currentItem + 1 Unit)?=null){        bannerAdapter.setOnClickListener(onClickListener)    }    /**     * 设置页面切换时的监听     * @param listener Function1     */    fun setPageChangedListener(listener:(currentPage:Int)->Unit){        this.listener=listener    }    /**     * @param data List : 更新的全量数据     * 用于更新广告数据     */    fun updateList(data: List) {        bannerAdapter.updateAllList(data)        // 超过一张才需要轮播        if (data.size > 1) {            // 默认展示第一张图            binding.viewPager.setCurrentItem(1, false)            // 初始化下方圆点指示器            binding.indicatorLayout.removeAllTabs()            for (i in 0 until data.size) {                val tab = binding.indicatorLayout.newTab()                tab.view.isEnabled = false                binding.indicatorLayout.addTab(tab)            }        } else {            binding.indicatorLayout.removeAllTabs()        }    }    /**     * 设置视图是否自动滚动轮播     * @param autoScroll Boolean 是否自动滚动     */    fun autoScroll(autoScroll:Boolean){        viewPagerHandler.removeCallbacks(viewPagerRunnable)        if(autoScroll) {            viewPagerHandler.postDelayed(viewPagerRunnable, DELAY_TIME_MS)        }    }}

        对于使用者只需要

         

        这样我们就优雅的实现了一个无限轮播图组件

上一篇:微信朋友圈文字发布秘籍,轻松掌握纯文本分享技巧!

下一篇:outlook怎么收发邮件?