ViewPager + Fragment 实现无限滑动

·  阅读 2677

一、实现效果图

iShot2020-09-0423.23.18.gif

二、实现方式

主要的实现方式有两种:

第一种是采用Adapter内的getCount()方法返回Integer.MAX_VALUE。 第二种在列表的最前面插入最后一条数据,在列表末尾插入第一个数据,造成循环的假象。

两种方式各有优缺点,第一种方式滑动更流畅,不过试过需要至少 4 个元素才能使用。否则要么报错要么就会有白屏。第二种方法的缺点是第一个和最后一个元素切换效果可能不是太好。

2.1 第一种实现方法Integer.MAX_VALUE

    1. 简单的布局
<?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="match_parent"
    android:orientation="vertical">


    <androidx.viewpager.widget.ViewPager
        android:id="@+id/vp_vp_test_vp"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <LinearLayout
        android:id="@+id/ll_vp_test_indicator"
        android:layout_width="match_parent"
        android:layout_height="20dp"
        android:background="@color/blue_74D3FF"
        android:orientation="horizontal"
        android:gravity="center"/>

</LinearLayout>
复制代码
    1. activity

为了可以向左也能无限滑动,设置初始位置可以在中间,也可以是一个大一点的数字,一般不需要去处理滑到到 0 时的位置,如果要处理的话可以通过监听滚动,在 position = 0,设置新的位置。同理向右滑动到最后一个时,做相同的处理。

class VpTestActivity : BaseActivity(R.layout.activity_vp_test) {
    override fun initData() {

    }

    override fun initEvent() {

    }

    override fun initInterface() {

        val dataList = arrayListOf<Fragment>()
        for (i in 0..3){
            dataList.add(VpTestFg.newInstance(i))
        }
        val adapter = VpTestAdapter(supportFragmentManager,dataList)
        vp_vp_test_vp.adapter = adapter

        //初始位置设置到比较大的位置
        vp_vp_test_vp.currentItem = dataList.size * 1000


        //设置圆点指示器
        ll_vp_test_indicator.removeAllViews()
        val dimen = resources.getDimensionPixelOffset(R.dimen.m10)
        for (i in 0..3){
            val image = ImageView(this)
            image.setBackgroundResource(R.drawable.circle_white)
            ll_vp_test_indicator.addView(image)
            //设置间隔
            val layoutParams: LinearLayout.LayoutParams = image.layoutParams as LinearLayout.LayoutParams
            layoutParams.setMargins(dimen,0,dimen,0)
            image.layoutParams = layoutParams
        }

        //设置第一个指示器是红色
        ll_vp_test_indicator.getChildAt(0)?.setBackgroundResource(R.drawable.circle_red)

        vp_vp_test_vp.addOnPageChangeListener(object : ViewPager.OnPageChangeListener{
            override fun onPageScrollStateChanged(state: Int) {

            }

            override fun onPageScrolled(
                position: Int,
                positionOffset: Float,
                positionOffsetPixels: Int
            ) {

            }

            override fun onPageSelected(position: Int) {
                //切换指示器
                changeIndicator(position)
            }

        })
    }

    private fun changeIndicator(position: Int) {
        val size = ll_vp_test_indicator.childCount
        for (i in 0..size){
            ll_vp_test_indicator.getChildAt(i)?.setBackgroundResource(R.drawable.circle_white)
        }
        ll_vp_test_indicator.getChildAt(position%size)?.setBackgroundResource(R.drawable.circle_red)

    }

    override fun onReload() {
    }
}
复制代码
    1. adapter

在这里 getCount 返回一个很大的值,这样就可以滑动很久也不会滑到头。在获取 getItem时,不做处理,对 position 的处理要放在instantiateItem里才行,在 getItem 中会报错。

class VpTestAdapter(fragmentManager: FragmentManager, val data: ArrayList<Fragment>) :
    FragmentPagerAdapter(fragmentManager) {

    override fun getItem(position: Int): Fragment = data[position]

    override fun getCount(): Int = Int.MAX_VALUE


    override fun instantiateItem(container: ViewGroup,  position: Int): Any {
        //处理position。让数组下标落在[0,fragmentList.size)中,防止越界
        var position = position
        position %= data.size
        return super.instantiateItem(container, position)
    }
}
复制代码
    1. 圆点指示器

只是两个简单的背景,可以是图片,也可以是 drawable 文件,这里用的是简单的文件,如下:
白色的圆:

<?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:height="10dp" android:width="10dp"/>
</shape>
复制代码

红色的圆:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="@color/red_FF8EB7"/>
    <size android:height="10dp" android:width="10dp"/>
</shape>
复制代码
    1. 简单的 fragment

布局非常简单,只有中间一个 TextView。

class VpTestFg: Fragment() {

    companion object{
        fun newInstance(type: Int): VpTestFg{
            val bundle = Bundle()
            bundle.putInt("type",type)
            val fragment = VpTestFg()
            fragment.arguments = bundle
            return fragment
        }
    }
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fg_vp_test,container,false)
    }

    @SuppressLint("SetTextI18n")
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val type = arguments?.getInt("type")
        tv_fg_vp_test_text.text = "this is fragment $type"

        tv_fg_vp_test_text.setOnClickListener {
            //进入另一种方法
            startActivity(Intent(context,VpTestTwoActivity::class.java))
        }
    }

}
复制代码
    1. 实现自动轮播
 private val mDelayTime: Long = 3000

    private val mHandler = @SuppressLint("HandlerLeak")
    object : Handler(){
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
        }
    }

    override fun run() {
        var currentItem = vp_vp_test_vp.currentItem
        currentItem ++
        if (currentItem == vp_vp_test_vp.childCount - 1){//滑到最后一个
            currentItem = 0
            vp_vp_test_vp.setCurrentItem(currentItem,false)
            mHandler.postDelayed(this,mDelayTime)
        }else{
            vp_vp_test_vp.setCurrentItem(currentItem,true)
            mHandler.postDelayed(this,mDelayTime)
        }
    }

    override fun onResume() {
        super.onResume()
        mHandler.postDelayed(this,mDelayTime)
    }

    override fun onPause() {
        super.onPause()
        mHandler.removeCallbacks(this)
    }
复制代码
    1. 手动滚动时,停止轮播
vp_vp_test_vp.setOnTouchListener { v, event ->
            when(event.action){
                MotionEvent.ACTION_DOWN -> {
                    LogUtils.e("action-down")
                    mHandler.removeCallbacks(this)
                }
                MotionEvent.ACTION_UP -> {
                    LogUtils.e("action-up")
                    mHandler.postDelayed(this,mDelayTime)
                }
                MotionEvent.ACTION_MOVE -> {
                    //LogUtils.e("action-move")
                    //mHandler.removeCallbacks(this)
                }
            }
             false
        }
复制代码
    1. 完整的 activity 如下:
class VpTestActivity : BaseActivity(R.layout.activity_vp_test), Runnable {

    private val mDelayTime: Long = 3000

    private val mHandler = @SuppressLint("HandlerLeak")
    object : Handler(){
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
        }
    }

    override fun run() {
        var currentItem = vp_vp_test_vp.currentItem
        currentItem ++
        if (currentItem == vp_vp_test_vp.childCount - 1){//滑到最后一个
            currentItem = 0
            vp_vp_test_vp.setCurrentItem(currentItem,false)
            mHandler.postDelayed(this,mDelayTime)
        }else{
            vp_vp_test_vp.setCurrentItem(currentItem,true)
            mHandler.postDelayed(this,mDelayTime)
        }
    }

    override fun onResume() {
        super.onResume()
        mHandler.postDelayed(this,mDelayTime)
    }

    override fun onPause() {
        super.onPause()
        mHandler.removeCallbacks(this)
    }


    override fun initData() {

    }

    override fun initEvent() {

    }

    @SuppressLint("ClickableViewAccessibility")
    override fun initInterface() {

        val dataList = arrayListOf<Fragment>()
        for (i in 0..3){
            dataList.add(VpTestFg.newInstance(i))
        }
        val adapter = VpTestAdapter(supportFragmentManager,dataList)
        vp_vp_test_vp.adapter = adapter

        //初始位置设置到比较大的位置
        vp_vp_test_vp.currentItem = dataList.size * 1000


        //设置圆点指示器
        ll_vp_test_indicator.removeAllViews()
        val dimen = resources.getDimensionPixelOffset(R.dimen.m10)
        for (i in 0..3){
            val image = ImageView(this)
            image.setBackgroundResource(R.drawable.circle_white)
            ll_vp_test_indicator.addView(image)
            //设置间隔
            val layoutParams: LinearLayout.LayoutParams = image.layoutParams as LinearLayout.LayoutParams
            layoutParams.setMargins(dimen,0,dimen,0)
            image.layoutParams = layoutParams
        }

        //设置第一个指示器是红色
        ll_vp_test_indicator.getChildAt(0)?.setBackgroundResource(R.drawable.circle_red)

        vp_vp_test_vp.addOnPageChangeListener(object : ViewPager.OnPageChangeListener{
            override fun onPageScrollStateChanged(state: Int) {

            }

            override fun onPageScrolled(
                position: Int,
                positionOffset: Float,
                positionOffsetPixels: Int
            ) {

            }

            override fun onPageSelected(position: Int) {
                //切换指示器
                changeIndicator(position)
            }

        })

        //设置切换动画
        vp_vp_test_vp.setPageTransformer(true, DepthPageTransformer())

        vp_vp_test_vp.setOnTouchListener { v, event ->
            when(event.action){
                MotionEvent.ACTION_DOWN -> {
                    LogUtils.e("action-down")
                    mHandler.removeCallbacks(this)
                }
                MotionEvent.ACTION_UP -> {
                    LogUtils.e("action-up")
                    mHandler.postDelayed(this,mDelayTime)
                }
                MotionEvent.ACTION_MOVE -> {
                    //LogUtils.e("action-move")
                    //mHandler.removeCallbacks(this)
                }
            }
             false
        }
    }

    private fun changeIndicator(position: Int) {
        val size = ll_vp_test_indicator.childCount
        for (i in 0..size){
            ll_vp_test_indicator.getChildAt(i)?.setBackgroundResource(R.drawable.circle_white)
        }
        ll_vp_test_indicator.getChildAt(position%size)?.setBackgroundResource(R.drawable.circle_red)

    }

    override fun onReload() {
    }


}
复制代码

2.2 第二种方法

    1. 布局文件同上
    1. activity
class VpTestTwoActivity: BaseActivity(R.layout.activity_vp_test) {
    override fun initData() {

    }

    override fun initEvent() {
    }

    lateinit var dataList: ArrayList<Fragment>
    var mCurrent2 = 1
    override fun initInterface() {

         dataList = arrayListOf<Fragment>()
        //第一个位置加上最后一个 fragment,最后一个位置加上第一个 fragment
        dataList.add(VpTestFg.newInstance(3))
        for (i in 0..3){
            dataList.add(VpTestFg.newInstance(i))
        }
        dataList.add(VpTestFg.newInstance(0))


        val adapter = VpTestAdapter2(supportFragmentManager,dataList)
        vp_vp_test_vp.adapter = adapter

        vp_vp_test_vp.currentItem = mCurrent2

        //设置圆点指示器
        ll_vp_test_indicator.removeAllViews()
        val dimen = resources.getDimensionPixelOffset(R.dimen.m10)
        for (i in 0..3){
            val image = ImageView(this)
            image.setBackgroundResource(R.drawable.circle_white)
            ll_vp_test_indicator.addView(image)
            //设置间隔
            val layoutParams: LinearLayout.LayoutParams = image.layoutParams as LinearLayout.LayoutParams
            layoutParams.setMargins(dimen,0,dimen,0)
            image.layoutParams = layoutParams
        }

        //设置第一个指示器是红色
        ll_vp_test_indicator.getChildAt(0)?.setBackgroundResource(R.drawable.circle_red)

        vp_vp_test_vp.addOnPageChangeListener(object : ViewPager.OnPageChangeListener{
            override fun onPageScrollStateChanged(state: Int) {
                //判断是否滑动结束
                if (state == ViewPager.SCROLL_STATE_IDLE){
                    if (mCurrent2 == 0){
                        vp_vp_test_vp.setCurrentItem(dataList.size - 2, false);//切換,不要動畫效果
                    }else if (mCurrent2 == dataList.size - 1){
                        vp_vp_test_vp.setCurrentItem(1, false);//切換,不要動畫效果
                    }
                }
            }

            @SuppressLint("MissingSuperCall")
            override fun onPageScrolled(
                position: Int,
                positionOffset: Float,
                positionOffsetPixels: Int
            ) {
                //这里可以自定义指示器切换动画效果

            }

            override fun onPageSelected(position: Int) {
                mCurrent2 = position
                //切换指示器
                changeIndicator(position)
            }

        })
    }

    private fun changeIndicator(position: Int) {
        val size = ll_vp_test_indicator.childCount
        for (i in 0..size){
            ll_vp_test_indicator.getChildAt(i)?.setBackgroundResource(R.drawable.circle_white)
        }
        when (position) {
            0 -> {
                ll_vp_test_indicator.getChildAt(size - 1)?.setBackgroundResource(R.drawable.circle_red)
            }
            dataList.size - 1 -> {
                ll_vp_test_indicator.getChildAt(0)?.setBackgroundResource(R.drawable.circle_red)
            }
            else -> {
                ll_vp_test_indicator.getChildAt(position - 1)?.setBackgroundResource(R.drawable.circle_red)
            }
        }
    }

    override fun onReload() {
    }
}
复制代码
    1. adapter
class VpTestAdapter2(fragmentManager: FragmentManager, val data: ArrayList<Fragment>) :
    FragmentPagerAdapter(fragmentManager) {

    override fun getItem(position: Int): Fragment = data[position]

    override fun getCount(): Int = data.size
    
}
复制代码
    1. 指示器和 fragment 同上
    1. 设置轮播

和上面设置轮播的方法基本类似,不同的地方就是runnable 里有些不同。

 override fun run() {
        var currentItem = vp_vp_test_vp.currentItem
        currentItem ++
       vp_vp_test_vp.currentItem = currentItem
        mHandler.postDelayed(this,mDelayTime)
    }
复制代码
    1. 完整的 activity 如下
class VpTestTwoActivity: BaseActivity(R.layout.activity_vp_test) ,Runnable{
    
    private val mDelayTime: Long = 3000

    private val mHandler = @SuppressLint("HandlerLeak")
    object : Handler(){
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
        }
    }

    override fun run() {
        var currentItem = vp_vp_test_vp.currentItem
        currentItem ++
       vp_vp_test_vp.currentItem = currentItem
        mHandler.postDelayed(this,mDelayTime)
    }

    override fun onResume() {
        super.onResume()
        mHandler.postDelayed(this,mDelayTime)
    }

    override fun onPause() {
        super.onPause()
        mHandler.removeCallbacks(this)
    }





    override fun initData() {

    }

    override fun initEvent() {
    }

    lateinit var dataList: ArrayList<Fragment>
    var mCurrent2 = 1
    @SuppressLint("ClickableViewAccessibility")
    override fun initInterface() {

         dataList = arrayListOf<Fragment>()
        //第一个位置加上最后一个 fragment,最后一个位置加上第一个 fragment
        dataList.add(VpTestFg.newInstance(3))
        for (i in 0..3){
            dataList.add(VpTestFg.newInstance(i))
        }
        dataList.add(VpTestFg.newInstance(0))


        val adapter = VpTestAdapter2(supportFragmentManager,dataList)
        vp_vp_test_vp.adapter = adapter

        vp_vp_test_vp.currentItem = mCurrent2

        //设置圆点指示器
        ll_vp_test_indicator.removeAllViews()
        val dimen = resources.getDimensionPixelOffset(R.dimen.m10)
        for (i in 0..3){
            val image = ImageView(this)
            image.setBackgroundResource(R.drawable.circle_white)
            ll_vp_test_indicator.addView(image)
            //设置间隔
            val layoutParams: LinearLayout.LayoutParams = image.layoutParams as LinearLayout.LayoutParams
            layoutParams.setMargins(dimen,0,dimen,0)
            image.layoutParams = layoutParams
        }

        //设置第一个指示器是红色
        ll_vp_test_indicator.getChildAt(0)?.setBackgroundResource(R.drawable.circle_red)

        vp_vp_test_vp.addOnPageChangeListener(object : ViewPager.OnPageChangeListener{
            override fun onPageScrollStateChanged(state: Int) {
                //判断是否滑动结束
                if (state == ViewPager.SCROLL_STATE_IDLE){
                    if (mCurrent2 == 0){
                        vp_vp_test_vp.setCurrentItem(dataList.size - 2, false);//切換,不要動畫效果
                    }else if (mCurrent2 == dataList.size - 1){
                        vp_vp_test_vp.setCurrentItem(1, false);//切換,不要動畫效果
                    }
                }
            }

            @SuppressLint("MissingSuperCall")
            override fun onPageScrolled(
                position: Int,
                positionOffset: Float,
                positionOffsetPixels: Int
            ) {
                //这里可以自定义指示器切换动画效果

            }

            override fun onPageSelected(position: Int) {
                mCurrent2 = position
                //切换指示器
                changeIndicator(position)
            }

        })

        //监听,手动滑动时取消轮播
        vp_vp_test_vp.setOnTouchListener { v, event ->
            when(event.action){
                MotionEvent.ACTION_DOWN -> {
                    LogUtils.e("action-down")
                    mHandler.removeCallbacks(this)
                }
                MotionEvent.ACTION_UP -> {
                    LogUtils.e("action-up")
                    mHandler.postDelayed(this,mDelayTime)
                }
                MotionEvent.ACTION_MOVE -> {
                    //LogUtils.e("action-move")
                    //mHandler.removeCallbacks(this)
                }
            }
            false
        }
    }

    private fun changeIndicator(position: Int) {
        val size = ll_vp_test_indicator.childCount
        for (i in 0..size){
            ll_vp_test_indicator.getChildAt(i)?.setBackgroundResource(R.drawable.circle_white)
        }
        when (position) {
            0 -> {
                ll_vp_test_indicator.getChildAt(size - 1)?.setBackgroundResource(R.drawable.circle_red)
            }
            dataList.size - 1 -> {
                ll_vp_test_indicator.getChildAt(0)?.setBackgroundResource(R.drawable.circle_red)
            }
            else -> {
                ll_vp_test_indicator.getChildAt(position - 1)?.setBackgroundResource(R.drawable.circle_red)
            }
        }
    }

    override fun onReload() {
    }
}
复制代码

三、参考

ViewPager两种方式实现无限轮播
ViewPager自动轮播,手指按住停止轮播
ViewPager结合Fragment进行无限滑动
ViewPager封装轮播效果+指示器 实现一行代码展示轮播图

分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改