一、实现效果图

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”?>
  1. <androidx.viewpager.widget.ViewPager
  2. android:id="@+id/vp_vp_test_vp"
  3. android:layout_width="match_parent"
  4. android:layout_height="0dp"
  5. android:layout_weight="1"/>
  6. <LinearLayout
  7. android:id="@+id/ll_vp_test_indicator"
  8. android:layout_width="match_parent"
  9. android:layout_height="20dp"
  10. android:background="@color/blue_74D3FF"
  11. android:orientation="horizontal"
  12. android:gravity="center"/>

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

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

  1. }
  2. override fun initEvent() {
  3. }
  4. override fun initInterface() {
  5. val dataList = arrayListOf<Fragment>()
  6. for (i in 0..3){
  7. dataList.add(VpTestFg.newInstance(i))
  8. }
  9. val adapter = VpTestAdapter(supportFragmentManager,dataList)
  10. vp_vp_test_vp.adapter = adapter
  11. //初始位置设置到比较大的位置
  12. vp_vp_test_vp.currentItem = dataList.size * 1000
  13. //设置圆点指示器
  14. ll_vp_test_indicator.removeAllViews()
  15. val dimen = resources.getDimensionPixelOffset(R.dimen.m10)
  16. for (i in 0..3){
  17. val image = ImageView(this)
  18. image.setBackgroundResource(R.drawable.circle_white)
  19. ll_vp_test_indicator.addView(image)
  20. //设置间隔
  21. val layoutParams: LinearLayout.LayoutParams = image.layoutParams as LinearLayout.LayoutParams
  22. layoutParams.setMargins(dimen,0,dimen,0)
  23. image.layoutParams = layoutParams
  24. }
  25. //设置第一个指示器是红色
  26. ll_vp_test_indicator.getChildAt(0)?.setBackgroundResource(R.drawable.circle_red)
  27. vp_vp_test_vp.addOnPageChangeListener(object : ViewPager.OnPageChangeListener{
  28. override fun onPageScrollStateChanged(state: Int) {
  29. }
  30. override fun onPageScrolled(
  31. position: Int,
  32. positionOffset: Float,
  33. positionOffsetPixels: Int
  34. ) {
  35. }
  36. override fun onPageSelected(position: Int) {
  37. //切换指示器
  38. changeIndicator(position)
  39. }
  40. })
  41. }
  42. private fun changeIndicator(position: Int) {
  43. val size = ll_vp_test_indicator.childCount
  44. for (i in 0..size){
  45. ll_vp_test_indicator.getChildAt(i)?.setBackgroundResource(R.drawable.circle_white)
  46. }
  47. ll_vp_test_indicator.getChildAt(position%size)?.setBackgroundResource(R.drawable.circle_red)
  48. }
  49. override fun onReload() {
  50. }

}

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

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

  1. override fun getItem(position: Int): Fragment = data[position]
  2. override fun getCount(): Int = Int.MAX_VALUE
  3. override fun instantiateItem(container: ViewGroup, position: Int): Any {
  4. //处理position。让数组下标落在[0,fragmentList.size)中,防止越界
  5. var position = position
  6. position %= data.size
  7. return super.instantiateItem(container, position)
  8. }

}

  1. - 4. 圆点指示器
  2. 只是两个简单的背景,可以是图片,也可以是 drawable 文件,这里用的是简单的文件,如下:<br />白色的圆:

<?xml version=”1.0” encoding=”utf-8”?>

  1. 红色的圆:

<?xml version=”1.0” encoding=”utf-8”?>

  1. - 5. 简单的 fragment
  2. 布局非常简单,只有中间一个 TextView

class VpTestFg: Fragment() {

  1. companion object{
  2. fun newInstance(type: Int): VpTestFg{
  3. val bundle = Bundle()
  4. bundle.putInt("type",type)
  5. val fragment = VpTestFg()
  6. fragment.arguments = bundle
  7. return fragment
  8. }
  9. }
  10. override fun onCreateView(
  11. inflater: LayoutInflater,
  12. container: ViewGroup?,
  13. savedInstanceState: Bundle?
  14. ): View? {
  15. return inflater.inflate(R.layout.fg_vp_test,container,false)
  16. }
  17. @SuppressLint("SetTextI18n")
  18. override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
  19. super.onViewCreated(view, savedInstanceState)
  20. val type = arguments?.getInt("type")
  21. tv_fg_vp_test_text.text = "this is fragment $type"
  22. tv_fg_vp_test_text.setOnClickListener {
  23. //进入另一种方法
  24. startActivity(Intent(context,VpTestTwoActivity::class.java))
  25. }
  26. }

}

  1. - 6. 实现自动轮播

private val mDelayTime: Long = 3000

  1. private val mHandler = @SuppressLint("HandlerLeak")
  2. object : Handler(){
  3. override fun handleMessage(msg: Message) {
  4. super.handleMessage(msg)
  5. }
  6. }
  7. override fun run() {
  8. var currentItem = vp_vp_test_vp.currentItem
  9. currentItem ++
  10. if (currentItem == vp_vp_test_vp.childCount - 1){//滑到最后一个
  11. currentItem = 0
  12. vp_vp_test_vp.setCurrentItem(currentItem,false)
  13. mHandler.postDelayed(this,mDelayTime)
  14. }else{
  15. vp_vp_test_vp.setCurrentItem(currentItem,true)
  16. mHandler.postDelayed(this,mDelayTime)
  17. }
  18. }
  19. override fun onResume() {
  20. super.onResume()
  21. mHandler.postDelayed(this,mDelayTime)
  22. }
  23. override fun onPause() {
  24. super.onPause()
  25. mHandler.removeCallbacks(this)
  26. }
  1. - 7. 手动滚动时,停止轮播

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. - 8. 完整的 activity 如下:

class VpTestActivity : BaseActivity(R.layout.activity_vp_test), Runnable {

  1. private val mDelayTime: Long = 3000
  2. private val mHandler = @SuppressLint("HandlerLeak")
  3. object : Handler(){
  4. override fun handleMessage(msg: Message) {
  5. super.handleMessage(msg)
  6. }
  7. }
  8. override fun run() {
  9. var currentItem = vp_vp_test_vp.currentItem
  10. currentItem ++
  11. if (currentItem == vp_vp_test_vp.childCount - 1){//滑到最后一个
  12. currentItem = 0
  13. vp_vp_test_vp.setCurrentItem(currentItem,false)
  14. mHandler.postDelayed(this,mDelayTime)
  15. }else{
  16. vp_vp_test_vp.setCurrentItem(currentItem,true)
  17. mHandler.postDelayed(this,mDelayTime)
  18. }
  19. }
  20. override fun onResume() {
  21. super.onResume()
  22. mHandler.postDelayed(this,mDelayTime)
  23. }
  24. override fun onPause() {
  25. super.onPause()
  26. mHandler.removeCallbacks(this)
  27. }
  28. override fun initData() {
  29. }
  30. override fun initEvent() {
  31. }
  32. @SuppressLint("ClickableViewAccessibility")
  33. override fun initInterface() {
  34. val dataList = arrayListOf<Fragment>()
  35. for (i in 0..3){
  36. dataList.add(VpTestFg.newInstance(i))
  37. }
  38. val adapter = VpTestAdapter(supportFragmentManager,dataList)
  39. vp_vp_test_vp.adapter = adapter
  40. //初始位置设置到比较大的位置
  41. vp_vp_test_vp.currentItem = dataList.size * 1000
  42. //设置圆点指示器
  43. ll_vp_test_indicator.removeAllViews()
  44. val dimen = resources.getDimensionPixelOffset(R.dimen.m10)
  45. for (i in 0..3){
  46. val image = ImageView(this)
  47. image.setBackgroundResource(R.drawable.circle_white)
  48. ll_vp_test_indicator.addView(image)
  49. //设置间隔
  50. val layoutParams: LinearLayout.LayoutParams = image.layoutParams as LinearLayout.LayoutParams
  51. layoutParams.setMargins(dimen,0,dimen,0)
  52. image.layoutParams = layoutParams
  53. }
  54. //设置第一个指示器是红色
  55. ll_vp_test_indicator.getChildAt(0)?.setBackgroundResource(R.drawable.circle_red)
  56. vp_vp_test_vp.addOnPageChangeListener(object : ViewPager.OnPageChangeListener{
  57. override fun onPageScrollStateChanged(state: Int) {
  58. }
  59. override fun onPageScrolled(
  60. position: Int,
  61. positionOffset: Float,
  62. positionOffsetPixels: Int
  63. ) {
  64. }
  65. override fun onPageSelected(position: Int) {
  66. //切换指示器
  67. changeIndicator(position)
  68. }
  69. })
  70. //设置切换动画
  71. vp_vp_test_vp.setPageTransformer(true, DepthPageTransformer())
  72. vp_vp_test_vp.setOnTouchListener { v, event ->
  73. when(event.action){
  74. MotionEvent.ACTION_DOWN -> {
  75. LogUtils.e("action-down")
  76. mHandler.removeCallbacks(this)
  77. }
  78. MotionEvent.ACTION_UP -> {
  79. LogUtils.e("action-up")
  80. mHandler.postDelayed(this,mDelayTime)
  81. }
  82. MotionEvent.ACTION_MOVE -> {
  83. //LogUtils.e("action-move")
  84. //mHandler.removeCallbacks(this)
  85. }
  86. }
  87. false
  88. }
  89. }
  90. private fun changeIndicator(position: Int) {
  91. val size = ll_vp_test_indicator.childCount
  92. for (i in 0..size){
  93. ll_vp_test_indicator.getChildAt(i)?.setBackgroundResource(R.drawable.circle_white)
  94. }
  95. ll_vp_test_indicator.getChildAt(position%size)?.setBackgroundResource(R.drawable.circle_red)
  96. }
  97. override fun onReload() {
  98. }

}

  1. <a name="tOwub"></a>
  2. ### 2.2 第二种方法
  3. - 1. 布局文件同上
  4. - 2. activity

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

  1. }
  2. override fun initEvent() {
  3. }
  4. lateinit var dataList: ArrayList<Fragment>
  5. var mCurrent2 = 1
  6. override fun initInterface() {
  7. dataList = arrayListOf<Fragment>()
  8. //第一个位置加上最后一个 fragment,最后一个位置加上第一个 fragment
  9. dataList.add(VpTestFg.newInstance(3))
  10. for (i in 0..3){
  11. dataList.add(VpTestFg.newInstance(i))
  12. }
  13. dataList.add(VpTestFg.newInstance(0))
  14. val adapter = VpTestAdapter2(supportFragmentManager,dataList)
  15. vp_vp_test_vp.adapter = adapter
  16. vp_vp_test_vp.currentItem = mCurrent2
  17. //设置圆点指示器
  18. ll_vp_test_indicator.removeAllViews()
  19. val dimen = resources.getDimensionPixelOffset(R.dimen.m10)
  20. for (i in 0..3){
  21. val image = ImageView(this)
  22. image.setBackgroundResource(R.drawable.circle_white)
  23. ll_vp_test_indicator.addView(image)
  24. //设置间隔
  25. val layoutParams: LinearLayout.LayoutParams = image.layoutParams as LinearLayout.LayoutParams
  26. layoutParams.setMargins(dimen,0,dimen,0)
  27. image.layoutParams = layoutParams
  28. }
  29. //设置第一个指示器是红色
  30. ll_vp_test_indicator.getChildAt(0)?.setBackgroundResource(R.drawable.circle_red)
  31. vp_vp_test_vp.addOnPageChangeListener(object : ViewPager.OnPageChangeListener{
  32. override fun onPageScrollStateChanged(state: Int) {
  33. //判断是否滑动结束
  34. if (state == ViewPager.SCROLL_STATE_IDLE){
  35. if (mCurrent2 == 0){
  36. vp_vp_test_vp.setCurrentItem(dataList.size - 2, false);//切換,不要動畫效果
  37. }else if (mCurrent2 == dataList.size - 1){
  38. vp_vp_test_vp.setCurrentItem(1, false);//切換,不要動畫效果
  39. }
  40. }
  41. }
  42. @SuppressLint("MissingSuperCall")
  43. override fun onPageScrolled(
  44. position: Int,
  45. positionOffset: Float,
  46. positionOffsetPixels: Int
  47. ) {
  48. //这里可以自定义指示器切换动画效果
  49. }
  50. override fun onPageSelected(position: Int) {
  51. mCurrent2 = position
  52. //切换指示器
  53. changeIndicator(position)
  54. }
  55. })
  56. }
  57. private fun changeIndicator(position: Int) {
  58. val size = ll_vp_test_indicator.childCount
  59. for (i in 0..size){
  60. ll_vp_test_indicator.getChildAt(i)?.setBackgroundResource(R.drawable.circle_white)
  61. }
  62. when (position) {
  63. 0 -> {
  64. ll_vp_test_indicator.getChildAt(size - 1)?.setBackgroundResource(R.drawable.circle_red)
  65. }
  66. dataList.size - 1 -> {
  67. ll_vp_test_indicator.getChildAt(0)?.setBackgroundResource(R.drawable.circle_red)
  68. }
  69. else -> {
  70. ll_vp_test_indicator.getChildAt(position - 1)?.setBackgroundResource(R.drawable.circle_red)
  71. }
  72. }
  73. }
  74. override fun onReload() {
  75. }

}

  1. - 3. adapter

class VpTestAdapter2(fragmentManager: FragmentManager, val data: ArrayList) : FragmentPagerAdapter(fragmentManager) {

  1. override fun getItem(position: Int): Fragment = data[position]
  2. override fun getCount(): Int = data.size

}

  1. - 4. 指示器和 fragment 同上
  2. - 5. 设置轮播
  3. 和上面设置轮播的方法基本类似,不同的地方就是runnable 里有些不同。

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

  1. - 6. 完整的 activity 如下

class VpTestTwoActivity: BaseActivity(R.layout.activity_vp_test) ,Runnable{

  1. private val mDelayTime: Long = 3000
  2. private val mHandler = @SuppressLint("HandlerLeak")
  3. object : Handler(){
  4. override fun handleMessage(msg: Message) {
  5. super.handleMessage(msg)
  6. }
  7. }
  8. override fun run() {
  9. var currentItem = vp_vp_test_vp.currentItem
  10. currentItem ++
  11. vp_vp_test_vp.currentItem = currentItem
  12. mHandler.postDelayed(this,mDelayTime)
  13. }
  14. override fun onResume() {
  15. super.onResume()
  16. mHandler.postDelayed(this,mDelayTime)
  17. }
  18. override fun onPause() {
  19. super.onPause()
  20. mHandler.removeCallbacks(this)
  21. }
  22. override fun initData() {
  23. }
  24. override fun initEvent() {
  25. }
  26. lateinit var dataList: ArrayList<Fragment>
  27. var mCurrent2 = 1
  28. @SuppressLint("ClickableViewAccessibility")
  29. override fun initInterface() {
  30. dataList = arrayListOf<Fragment>()
  31. //第一个位置加上最后一个 fragment,最后一个位置加上第一个 fragment
  32. dataList.add(VpTestFg.newInstance(3))
  33. for (i in 0..3){
  34. dataList.add(VpTestFg.newInstance(i))
  35. }
  36. dataList.add(VpTestFg.newInstance(0))
  37. val adapter = VpTestAdapter2(supportFragmentManager,dataList)
  38. vp_vp_test_vp.adapter = adapter
  39. vp_vp_test_vp.currentItem = mCurrent2
  40. //设置圆点指示器
  41. ll_vp_test_indicator.removeAllViews()
  42. val dimen = resources.getDimensionPixelOffset(R.dimen.m10)
  43. for (i in 0..3){
  44. val image = ImageView(this)
  45. image.setBackgroundResource(R.drawable.circle_white)
  46. ll_vp_test_indicator.addView(image)
  47. //设置间隔
  48. val layoutParams: LinearLayout.LayoutParams = image.layoutParams as LinearLayout.LayoutParams
  49. layoutParams.setMargins(dimen,0,dimen,0)
  50. image.layoutParams = layoutParams
  51. }
  52. //设置第一个指示器是红色
  53. ll_vp_test_indicator.getChildAt(0)?.setBackgroundResource(R.drawable.circle_red)
  54. vp_vp_test_vp.addOnPageChangeListener(object : ViewPager.OnPageChangeListener{
  55. override fun onPageScrollStateChanged(state: Int) {
  56. //判断是否滑动结束
  57. if (state == ViewPager.SCROLL_STATE_IDLE){
  58. if (mCurrent2 == 0){
  59. vp_vp_test_vp.setCurrentItem(dataList.size - 2, false);//切換,不要動畫效果
  60. }else if (mCurrent2 == dataList.size - 1){
  61. vp_vp_test_vp.setCurrentItem(1, false);//切換,不要動畫效果
  62. }
  63. }
  64. }
  65. @SuppressLint("MissingSuperCall")
  66. override fun onPageScrolled(
  67. position: Int,
  68. positionOffset: Float,
  69. positionOffsetPixels: Int
  70. ) {
  71. //这里可以自定义指示器切换动画效果
  72. }
  73. override fun onPageSelected(position: Int) {
  74. mCurrent2 = position
  75. //切换指示器
  76. changeIndicator(position)
  77. }
  78. })
  79. //监听,手动滑动时取消轮播
  80. vp_vp_test_vp.setOnTouchListener { v, event ->
  81. when(event.action){
  82. MotionEvent.ACTION_DOWN -> {
  83. LogUtils.e("action-down")
  84. mHandler.removeCallbacks(this)
  85. }
  86. MotionEvent.ACTION_UP -> {
  87. LogUtils.e("action-up")
  88. mHandler.postDelayed(this,mDelayTime)
  89. }
  90. MotionEvent.ACTION_MOVE -> {
  91. //LogUtils.e("action-move")
  92. //mHandler.removeCallbacks(this)
  93. }
  94. }
  95. false
  96. }
  97. }
  98. private fun changeIndicator(position: Int) {
  99. val size = ll_vp_test_indicator.childCount
  100. for (i in 0..size){
  101. ll_vp_test_indicator.getChildAt(i)?.setBackgroundResource(R.drawable.circle_white)
  102. }
  103. when (position) {
  104. 0 -> {
  105. ll_vp_test_indicator.getChildAt(size - 1)?.setBackgroundResource(R.drawable.circle_red)
  106. }
  107. dataList.size - 1 -> {
  108. ll_vp_test_indicator.getChildAt(0)?.setBackgroundResource(R.drawable.circle_red)
  109. }
  110. else -> {
  111. ll_vp_test_indicator.getChildAt(position - 1)?.setBackgroundResource(R.drawable.circle_red)
  112. }
  113. }
  114. }
  115. override fun onReload() {
  116. }

} ```

三、参考

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