ViewPager 超詳解:玩出十八般花樣

受權聲明:本文已受權微信公衆號:鴻洋(hongyangAndroid)原創首發。其餘轉載請再諮詢做者許可!android

雖然沒有 RecyclerView 這種列表控件經常使用些,可是在開發中你ViewPager 確定也是不可或缺的控件,引導頁、輪播圖、卡片畫廊等效果老是缺乏不了 ViewPager 的身影。
相信每一位朋友對 ViewPager 的基礎使用都已經很熟練了,今天在這裏就從簡至繁將 ViewPager 的每一個用法都梳理一邊。git

主要包括如下內容:github

  • ViewPager 基本使用(簡介、適配器)
  • ViewPager + TabLayout + Fragment 的使用
  • ViewPager 輪播圖的使用(指示器、標題、自動輪播、首尾循環)
  • ViewPager 的切換效果(PageTransformer)
  • ViewPager 切換效果進階

ViewPager 的基礎使用

對於 ViewPager ,官方的描述大概是這樣的:頁面容許左右滑動的佈局管理器,而不一樣頁面帶有不一樣的數據。緩存

這裏簡單歸結以下:微信

  • ViewPager 是 v4 包中的一個類。
  • ViewPager 類直接繼承了 ViewGroup 類,它是一個容器類,能夠在其中添加其餘的 view 。
  • 相似於 ListView,也有本身的適配器,用來填充數據頁面。

關於 ViewPager 在佈局文件中的聲明,這裏就再也不說了。實際上是沒什麼好說的,並無什麼能夠直接聲明的特殊屬性,因爲繼承於 ViewGroup 有的也都是些 ViewGroup 的屬性。ide

這裏值得介紹的也就是幾個能夠動態設置方法了,經常使用的有如下幾個:佈局

  • setAdapter(PagerAdapter adapter) 設置適配器
  • setOffscreenPageLimit(int limit) 設置緩存的頁面個數,默認是 1
  • setCurrentItem(int item) 跳轉到特定的頁面
  • setOnPageChangeListener(..) 設置頁面滑動時的監聽器(如今API中建議使用 addOnPageChangeListener(..)
  • setPageTransformer(..PageTransformer) 設置頁面切換時的動畫效果
  • setPageMargin(int marginPixels) 設置不一樣頁面之間的間隔
  • setPageMarginDrawable(..) 設置不一樣頁面間隔之間的裝飾圖也就是 divide ,要想顯示設置的圖片,須要同時設置 setPageMargin()

謹記上面這幾個方法,玩轉 ViewPager 其實都是圍繞它們進行的,能不能玩出花樣,就看你把它們運用的怎麼樣了。post

上面的方面大多一看說明就明白了,這裏值得一提的就是 ViewPager 的適配器了。測試

PagerAdapter

PagerAdapter 是抽象的類,因此使用時只能使用它的子類,實現子類必需要實現如下四個方法:動畫

  • getCount(); 是獲取當前窗體界面數,也就是數據的個數。
  • isViewFromObject(View view, Object object); 這個方法用於判斷是否由對象生成界面,官方建議直接返回 return view == object;
  • instantiateItem(View container, int position); 要顯示的頁面或須要緩存的頁面,會調用這個方法進行佈局的初始化。
  • destroyItem(ViewGroup container, int position, Object object); 若是頁面不是當前顯示的頁面也不是要緩存的頁面,會調用這個方法,將頁面銷燬。

相信你們對上面這些方法的實現並不陌生,這裏就不詳細介紹了。另外咱們知道官方給咱們提供的還有 PagerAdapter 的兩個直接子類 FragmentPagerAdapter 和 FragmentStatePagerAdapter 。而咱們經常會在 ViewPager 和 Fragment 結合使用的時候來使用這兩個適配器。具體的用法和它們之間的區別,咱們在下個章節講。

ViewPager + TabLayout + Fragment 的結合使用

在引導頁中咱們經常用 ViewPager 和 Fragment 結合使用,而像新聞分類的頁面咱們會再加上一個 TabLayout 三者聯動使用。而此時,咱們不會再使用 PagerAdapter 了,而是直接使用官方提供的專門用於與 Fragment 結合使用的 FragmentPagerAdapter。

FragmentPagerAdapter 它將每個頁面表示爲一個 Fragment,而且每個 Fragment 都將會保存到 FragmentManager 當中。並且,當用戶沒可能再次回到頁面的時候,FragmentManager 纔會將這個 Fragment 銷燬。

使用 FragmentPagerAdapter 須要實現兩個方法:

  • public Fragment getItem(int position) 返回的是對應的 Fragment 實例,通常咱們在使用時,會經過構造傳入一個要顯示的 Fragment 的集合,咱們只要在這裏把對應的 Fragment 返回就好了。
  • public int getCount() 這個上面介紹過了返回的是頁面的個數,咱們只要返回傳入集合的長度就好了。

使用起來是很是簡單的,FragmentStatePagerAdapter 的使用也和上面同樣,那二者到底有什麼區別呢?

區別以下:

  • FragmentPagerAdapter:對於再也不須要的 fragment,選擇調用 onDetach() 方法,僅銷燬視圖,並不會銷燬 fragment 實例。
  • FragmentStatePagerAdapter:會銷燬再也不須要的 fragment,噹噹前事務提交之後,會完全的將 fragmeng 從當前 Activity 的FragmentManager 中移除,state 標明,銷燬時,會將其 onSaveInstanceState(Bundle outState) 中的 bundle 信息保存下來,當用戶切換回來,能夠經過該 bundle 恢復生成新的 fragment,也就是說,你能夠在 onSaveInstanceState(Bundle outState) 方法中保存一些數據,在 onCreate 中進行恢復建立。

由上總結:
使用 FragmentStatePagerAdapter 更省內存,可是銷燬後新建也是須要時間的。通常狀況下,若是你是製做主頁面,就 三、4 個 Tab,那麼能夠選擇使用 FragmentPagerAdapter,若是你是用於 ViewPager 展現數量特別多的條目時,那麼建議使用 FragmentStatePagerAdapter。

那 Tablayout 如何和 Viewpager 聯動呢?因爲咱們這裏主要是講解 ViewPager 的,所謂 「術業有專攻」 因此關於 TabLayout 的使用咱們就再也不摻和了。
第一步,初始化 TabLayout 和 ViewPager 後只要經過調用 TabLayout 的 tabLayout.setupWithViewPager(viewPager) 方法就將二者綁定在一塊兒了。
第二步,重寫 PagerAdapter 的 public CharSequence getPageTitle(int position) 方法,而 TabLayout 也正是經過 setupWithViewPager() 方法底部會調用 PagerAdapter 中的getPageTitle() 方法來實現聯動的。

ViewPager 輪播圖的使用

關於此章本想給你們細細到來,才寫上面兩章都這麼多篇幅了,咱們還有給下面兩章重點講的部分留點空間呢。若是非得想看還不嫌我囉嗦,那我有時間再把這段給不出來。

這章就這樣結束,固然沒有,雖然不負責任,可是也不能撩完妹子就閃人啊!這裏仍是要基本原理給你們論道論道的。

Banner 元素組成圖

從上圖咱們能夠知道,通常咱們使用 ViewPager 作 Banner 時主要有以上幾個元素:

標題 & 指示器
咱們能夠把標題和指示器直接寫在咱們 Banner 的 item 的佈局中,這樣經過在 PageAdapter 的 instantiateItem() 方法初始化頁面時,直接設置。可是通常咱們不會這樣作(若是標題沒有陰影的話,能夠如上面說的那樣),由於這樣在頁面滑動的時候,會顯得特別生硬,尤爲是指示器。
那該如何呢?通常咱們會在 ViewPager 所在的佈局文件中,聲明指示器和標題佈局,以下:

<FrameLayout
     ...>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewPager"
        ..."/>

    <LinearLayout
        android:layout_gravity="bottom"
        ...>
		<!--指示器佈局,由於不知道 item 的個數,因此會動態的把指示器的View添加到這裏-->
        <LinearLayout
            android:id="@+id/bannerIndicators"
            .../>
		<!--標題-->
        <TextView
            android:id="@+id/bannerTitle"
            .../>
    </LinearLayout>

</FrameLayout>
複製代碼

那如何才能實現當頁面滑動時,標題和指示器伴隨改變呢?還記不記得,一開始介紹 ViewPager 時,它有一個能夠設置監聽頁面改變的方法 addOnPageChangeListener(),在 OnPageChangeListener 監聽器中有一個頁面滑動結束時的回調方法 onPageSelected(int position) ,咱們只須要在這個方法中,來設置標題和指示器跟隨變化就好了。

自動輪播
實現自動輪播的原理其實更簡單,只要咱們每隔必定時間發送一個切換頁面的事件就好了。實現這個功能有不少種方法,相信做爲 Android 開發者,你最快想到的就是 Handler.sendEmptyMessageDelayed(int what, long delayMillis) 了吧。

Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (mAutoPlay) {
                //mViewPager.setCurrentItem(mViewPager.getCurrentItem() + 1);//無限輪播時
				mViewPager.setCurrentItem((mViewPager.getCurrentItem()+1) % mViewPagerItemCount)
                this.sendEmptyMessageDelayed(MSG_WHAT, delayMillis);
            }
        }
    };
複製代碼

固然不要忘了在外部,初始化完成後調用一次mHandler.sendEmptyMessageDelayed(MSG_WHAT, delayMillis);

首尾循環無限輪播
固然,咱們在設置自動輪播時,已經作到了首尾循環無限輪播了呀。

其實這裏所說的無限輪播是指:當咱們手動滑到最後一個頁面時,依然能夠向後伴隨手指滑動,並跳轉顯示的是第一個頁面;反之滑到首個頁面也是同樣。
旁邊那位腦子靈光的大兄弟又說了,那還不簡單,在頁面監聽器 OnPageChangeListener 裏,經過 position 分辨滑動的是否是首頁或者最後一頁,而後經過 setCurrentItem() 設置一下不就好了,還那麼麻煩。
大胸弟,你且消消氣!這裏並不使用這種方式是由於,這種方式的跳轉是十分生硬的,同時是不能實現「伴隨手指滑動」這個條件的。

那究竟如何如何才能實現呢?目前江湖流傳的有一下兩種方法:

  1. 使 adapter 的 getCount() 返回 Integer.MAX_VALUE,再在初始化時設置當前頁面爲幾千頁(如:ViewPager.setCurrentItem(1000*data.size)),其實就是障眼法,大爺心情好的話,向前滑動幾千頁也不是不可能的;
  2. 經過監聽 viewpager 的滑動來設置頁面。如當前有數據 123,則設置頁面爲 31231,當頁面滑動到第一個 3 時,設置當前頁面爲第二個 3,那麼左右均可以滑動,當其滑動到第二個 1 時同理。

關於第一種是目前流行最廣的方法,若是你們想查看詳細的說明能夠參考下面這篇文章(網上隨便找的,對可靠性不做擔保啊):
ViewPager真正的無限輪播

關於第二種方式,嚴格意義上分析是會在滑動過程當中產生生硬的跳動的。不過有位江湖義士聲稱已經解決了這種不和諧狀況的發生,附上文章地址(可靠性更不做擔保啊):
打造真正的無限循環viewpager (不負責的我真的沒有測試這個可靠性,你們閒的測試下,若是效果很差的話,告訴我,我趕快把這個連接刪除~~)

自定義 ViewPager 的切換效果

原本最近封裝一個了 ViewPager 十八般花樣、樣樣都有的 PageTransformer 動效庫,想着前面少囉嗦點,而後把這章做爲重點來說的。沒想到前面仍是囉裏囉嗦這麼多(恍然間,我好像找到本身一直撩妹不成功單身的緣由了~),好了,步入正題。

關於 ViewPager 的切換動畫,官方提供了一個內部接口 ViewPager.PageTransformer 來供咱們實現自定義切換動效。這個接口裏只提供了一個方法 public void transformPage(View view, float position),可是千萬不要小看了這兩個方法,這裏面的道道有不少呢。

transformPage 方法兩個參數,一個是 View ,這個好理解就是當前要設置動效的頁面。這個頁面並不僅僅是指當前顯示的頁面,即將滑出的頁面、即將滑入的頁面、已經隱藏的頁面,也就是說這個 View 是指全部的頁面。那如何分辨 View 究竟是指哪一個頁面呢,這個須要根據第二個參數 position 來辨別。

你千萬不要把 position 理解成了 ViewPager 頁面的下標,必定要看仔細,這個 position 但是 float 類型,下標怎麼多是浮點型呢!

從 doc 註釋來看,當前選中的 item 的 position 永遠是 0 ,被選中 item 的前一個爲 -1,被選中 item 的後一個爲 1。

其實這裏文檔的描述並非徹底正確的,先後 item position 爲 -1 和 1 的前提是你沒有給 ViewPager 設置 pageMargin。
若是你設置了 pageMargin,先後 item 的 position 須要分別加上(或減去,前減後加)一個偏移量(偏移量的計算方式爲 pageMargin / pageWidth)。

在用戶滑動界面的時候,position 是動態變化的,下面以左滑爲例(以向左爲正方向):

  • 選中 item 的 position:從 0 漸至 -1 - offset (pageMargin / pageWidth)
  • 前一個 item 的 position:從 -1 漸至 -1 - offset (pageMargin / pageWidth)
  • 前兩個 item 的 position:從 -2 漸至 -2 - offset (pageMargin / pageWidth),再往前就以此類推
  • 後一個 item 的 position:從 1 + offset (pageMargin / pageWidth) 漸至 0,再日後就以此類推

每一次滑動,每一個 View 對應的 position 是一個在一個區間範圍內動態漸變的過程,因此咱們能夠將 position 的值應用於 setAlpha(), setTranslationX(), 或者 setScaleY() 等等方法,從而實現自定義的切換動畫有一個漸變的效果。

這裏給你們舉一個視差切換動效的實現方式,咱們先來看一下效果:

其實實現起來很簡單,就是滑動時給頁面再設置一個頁面橫向滑動的動畫,讓頁面實現滑動的速度慢於手指滑動的速度,這樣就會有種視差的效果:

@Override
public void transformPage(View page, float position) {
    int width = page.getWidth();
	//咱們給不一樣狀態的頁面設置不一樣的效果
	//經過position的值來分辨頁面所處於的狀態
    if (position < -1) {//滑出的頁面
        page.setScrollX((int) (width * 0.75 * -1));
    } else if (position <= 1) {//[-1,1]
        if (position < 0) {//[-1,0]
            page.setScrollX((int) (width * 0.75 * position));
        } else {//[0,1]
            page.setScrollX((int) (width * 0.75 * position));
        }
    } else {//即將滑入的頁面
        page.setScrollX((int) (width * 0.75));
    }
}
複製代碼

其實這裏處於大於1或者小於-1的狀態的頁面都好理解。須要詳細講解的就是 [-1,1] 這個區間狀態的頁面。你們能夠想象一下:

像左滑動

由上圖能夠看出,當滑動時,(若是沒有偏移量)界面上最多出現兩個 item,一個即將滑出即將隱藏的頁面(postion變化爲:從0漸到-1),一個滑入即將徹底顯示的頁面(postion變化爲:從1漸到0)。

這樣給每一個不一樣狀態的頁面設置不一樣的動效就達到咱們想要的目的了。回到上面例子中的代碼,剛纔那位大兄弟又說話了,你的代碼明明 [0 -> -1] 和 [1 -> 0] 兩個狀態的 item 設置的是同樣的動效啊。這裏只是代碼同樣,動效其實是不同的,由於一個position 是大於 0 的,一個 position 是小於 0 的,滑動的方向天然是相反的。難道你非得讓我寫成 page.setScrollX((int) (width * -0.75 * -position)) 這樣嗎?

想實現更多炫酷的動效,能夠查看爲你們封裝好的 PageTransformerHelp 庫,GitHub地址:github.com/OCNYang/Pag…

ViewPager 切換效果進階

其實上面已經把該講的自定義的方面都講的很清楚了;整個梳理下來,上面一開始給你們列舉的着重強調的幾個 ViewPager 的動態設置的方法就剩 setPageMargin(int marginPixels) 沒有說了,那麼這個方法又會給咱們帶來什麼樣的神奇效果呢?

界面可以同時看到多個 item

那是如何實現上面這種效果呢,能夠確定的是:兩邊兩個 item 被縮小且透明度變低,是經過設置上面所說的自定義動效完成的。那如何在一個界面可以同時看到 3 個 item 呢?那麼就經過下圖的分析和解釋告訴實現的原理:

卡片式輪播效果實現原理分析

看了上面的解釋有沒有一種恍然大悟,火燒眉毛想試試的衝動?若是經過上圖仍然略有疑惑能夠看看鴻洋大神的這篇文章,能夠說是很全面了:
巧用ViewPager 打造不同的廣告輪播切換效果

結尾

到此,ViewPager 的基本使用方式已經講的差很少了。想查看更多 切換動畫 的效果,能夠到本文的源碼地址進行查看。

源碼地址github.com/OCNYang/Pag…

參考文章:
blog.csdn.net/lmj62356579…
blog.csdn.net/qq_30716173…

相關文章
相關標籤/搜索