iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 11
0

當我們在使用ViewPager時,有時候會覺得滑到最後一個項目時,要重新返回第一項是很麻煩的事情。

除了使用ViewPager的setCurrentItem(0)方法,就只能手動滑到第一項。

如果項目數量一多就是件很可怕的事。

如果可以直接回第一項就好了...

沒問題!! 今天就來作一個無限Loop ViewPager!!

先上個架構 :

  • 自訂的ViewPager : RoomViewPager
  • 自訂的ViewPagerAdapter : RoomViewPagerAdapter
  • activity_main.xml
  • MainActivity

首先,我們先自訂一個ViewPager。

我們直覺就是直接宣告一個類別去繼承ViewPager

class RoomViewPager (context: Context) :ViewPager(context) { }

開心地編譯,先炸個半熟。

Caused by: java.lang.NoSuchMethodException: [class android.content.Context, interface android.util.AttributeSet]
Caused by: android.view.InflateException: Binary XML file line #9: Error inflating class com.example.room.RoomViewPager.RoomViewPager

這是因為當我們在繼承ViewPager時,裡面的constructor並沒有正確的定義,像這個例子就是缺少了AttributeSet這個參數。

那麼改成下列的方式編譯

class RoomViewPager (context: Context, attrs : AttributeSet) :ViewPager(context,attrs) { }

果然找到失散多年的親人,痛哭流涕,成功編譯。

上程式碼先

class RoomViewPager (context: Context, attrs : AttributeSet) :ViewPager(context,attrs) {
    override fun setCurrentItem(item :Int,  smoothScroll :Boolean) {
        var mItem:Int=0
        if (adapter!!.getCount() == 0) {
            super.setCurrentItem(item, smoothScroll)
            return
        }
        mItem = getOffsetAmount() + (item % adapter!!.count);
        super.setCurrentItem(mItem, smoothScroll);
    }
    fun getOffsetAmount(): Int {
        if (adapter!!.count == 0) {
            return 0
        }
        if (adapter is RoomViewPagerAdapter) {
            val adapter = adapter as RoomViewPagerAdapter?
            return adapter!!.getRealCount() * 100
        } else {
            return 0
        }
    }
}

我們需要override setCurrentItem 函式,並指向一個假的數值。
這段程式碼的用意,是增加我們畫面上滑動的項目數量,實際上還是我們給定的項目數量在循環。

接著自訂一個ViewPagerAdapter 。

class RoomViewPagerAdapter( private val mListViews:List<View>) : PagerAdapter() {
    override fun isViewFromObject(p0: View, p1: Any): Boolean {
        return p0==p1
    }
    override fun getCount(): Int {
        if (getRealCount() == 0) {
            return 0
        }
        return Integer.MAX_VALUE;
    }
    fun getRealCount(): Int {
        return mListViews.size
    }
    override fun instantiateItem(container: ViewGroup, position: Int): Any {
        val virtualPosition = position % getRealCount()
        if (mListViews != null) {
            var targetView=mListViews[virtualPosition]
            container.addView(targetView)
            return targetView
        }
        return super.instantiateItem(container, position)
    }

    override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
        val virtualPosition = position % getRealCount()
        if (mListViews != null) {
            val view = mListViews.get(virtualPosition)
            container.removeView(view)
        }
    }
}

這邊我們需要覆寫4個方法

  • isViewFromObject(p0: View, p1: Any)
  • getCount()
  • instantiateItem(container: ViewGroup, position: Int)
  • destroyItem(container: ViewGroup, position: Int, object: Any)
在isViewFromObject(p0: View, p1: Any)裡,

我們回傳了return p0==p1作為判斷物件是否為View

在getCount()裡

我們判斷當真實的列表項目的數量為0時,回傳0
不然回傳Integer.MAX_VALUE這個大值來實現無限的概念(其實還是有限值,但我想沒人這麼有耐心刷到這個數值..

public static final int MAX_VALUE = 0x7fffffff;

在instantiateItem(container: ViewGroup, position: Int)裡

我們先把虛擬位置給還原成真實位置

val virtualPosition = position % getRealCount()

將View項目加到ViewGroup裡並回傳列表位置的VIEW項目。

在destroyItem(container: ViewGroup, position: Int, object: Any)裡

一樣先把虛擬位置給還原成真實位置,然後從ViewGroup裡移除該項目.

我們完成了

  • 自訂的ViewPager : RoomViewPager
  • 自訂的ViewPagerAdapter : RoomViewPagerAdapter

那麼就完成剩下的部分吧

  • activity_main.xml
  • MainActivity

來設定activity_main.xml

<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    <com.example.room.RoomViewPager.RoomViewPager
            android:layout_width="0dp"
            android:layout_height="325dp" app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp" android:layout_marginRight="8dp"
            app:layout_constraintStart_toStartOf="parent" android:layout_marginLeft="8dp"
            android:layout_marginStart="8dp" android:layout_marginTop="8dp" app:layout_constraintTop_toTopOf="parent"
            android:id="@+id/roomViewpager"/>
</android.support.constraint.ConstraintLayout>

我們使用我們上次自訂的RoomViewPager,訂好ID後準備給MainActivity使用。

來實作MainActivity

class MainActivity : AppCompatActivity() {
var roomList = ArrayList<View>()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initRoomView()
        var roomAdapter=RoomViewPagerAdapter(roomList)
        roomViewpager.adapter=roomAdapter
        roomViewpager.setCurrentItem(Integer.MAX_VALUE/2)
    }
    private fun initRoomView() {
        val mInflater : LayoutInflater= layoutInflater
        val v1 = mInflater.inflate(R.layout.room_n, null)
        val v2 = mInflater.inflate(R.layout.room_e, null)
        val v3 = mInflater.inflate(R.layout.room_s, null)
        val v4 = mInflater.inflate(R.layout.room_w, null)
        roomList=arrayListOf(v1,v2,v3,v4)
    }
}

怎麼建立room_n等4個layout就不提了。

我們在MainActivity先宣告一個var roomList = ArrayList<View>()來存放layout項目。

然後,宣告一個var roomAdapter=RoomViewPagerAdapter(roomList)
傳入roomList並交給我們自訂好的roomViewpager元件接收。

那麼就可以來編譯執行了。

這時候我們的第一項往左可以繼續接到最後一項的畫面,反之最後一項往右也可以接到第一項的畫面,如此完成了我們的無限loop的功能。


上一篇
任意移動的RecycleView -使用ItemTouchHelper 左右滑動篇
下一篇
畫面下拉更新RecycleView,使用SwipeRefreshLayout
系列文
跟Kotlin一起來聊Android元件 或許還有應用,或許還有一些資訊雜談30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言