一、實作效果圖
二、實作方式
主要的實作方式有兩種:
第一種是采用Adapter内的getCount()方法傳回Integer.MAX_VALUE。第二種在清單的最前面插入最後一條資料,在清單末尾插入第一個資料,造成循環的假象。
兩種方式各有優缺點,第一種方式滑動更流暢,不過試過需要至少 4 個元素才能使用。否則要麼報錯要麼就會有白屏。第二種方法的缺點是第一個和最後一個元素切換效果可能不是太好。
2.1 第一種實作方法Integer.MAX_VALUE
- 簡單的布局
<?xml version="1.0" encoding="utf-8"?> xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> android:id="@+id/vp_vp_test_vp" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> 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"/>
- activity
為了可以向左也能無限滑動,設定初始位置可以在中間,也可以是一個大一點的數字,一般不需要去處理滑到到 0 時的位置,如果要處理的話可以通過監聽滾動,在 position = 0,設定新的位置。同理向右滑動到最後一個時,做相同的處理。
class VpTestActivity : BaseActivity(R.layout.activity_vp_test) { override fun initData() { } override fun initEvent() { } override fun initInterface() { val dataList = arrayListOf() 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() { }}
- adapter
在這裡 getCount 傳回一個很大的值,這樣就可以滑動很久也不會滑到頭。 在擷取 getItem時,不做處理,對 position 的處理要放在instantiateItem 裡才行,在 getItem 中會報錯。
class VpTestAdapter(fragmentManager: FragmentManager, val data: ArrayList) : 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) }}
- 圓點訓示器
隻是兩個簡單的背景,可以是圖檔,也可以是 drawable 檔案, 這裡用的是簡單的檔案,如下: 白色的圓:
<?xml version="1.0" encoding="utf-8"?> android:shape="oval">
紅色的圓:
<?xml version="1.0" encoding="utf-8"?> android:shape="oval">
- 簡單的 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)) } }}
- 實作自動輪播
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) }
- 手動滾動時,停止輪播
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 }
- 完整的 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() 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 第二種方法
- 布局檔案同上
- activity
class VpTestTwoActivity: BaseActivity(R.layout.activity_vp_test) { override fun initData() { } override fun initEvent() { } lateinit var dataList: ArrayList var mCurrent2 = 1 override fun initInterface() { dataList = arrayListOf() //第一個位置加上最後一個 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() { }}
- adapter
class VpTestAdapter2(fragmentManager: FragmentManager, val data: ArrayList) : FragmentPagerAdapter(fragmentManager) { override fun getItem(position: Int): Fragment = data[position] override fun getCount(): Int = data.size }
- 訓示器和 fragment 同上
- 設定輪播
和上面設定輪播的方法基本類似,不同的地方就是runnable 裡有些不同。
override fun run() { var currentItem = vp_vp_test_vp.currentItem currentItem ++ vp_vp_test_vp.currentItem = currentItem mHandler.postDelayed(this,mDelayTime) }
- 完整的 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 var mCurrent2 = 1 @SuppressLint("ClickableViewAccessibility") override fun initInterface() { dataList = arrayListOf() //第一個位置加上最後一個 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() { }}
到這裡就結束啦。 往期精彩回顧:
- Android實作短信驗證碼自動填充功能
- Android仿echo精美彈幕功能
- Android實作頭像重疊排列功能
- Android仿QQ個性标簽功能
- Android仿QQ側滑删除的功能