1 認識一下ViewPager?html
ViewPager最先出自4.0版本,那麼低版本如何能使用ViewPager呢?爲了兼容低版本安卓設備,谷歌官方給咱們提供了一個的軟件包android.support.v4.view。這個V4包囊了只有在安卓3.0以上可使用的api,而viewpager就是其中之一。利用它,咱們能夠作不少事情,從最簡單的引導頁導航,到輪轉廣告,到頁面菜單等等,無不出現ViewPager的身影。應用普遍,簡單好用,更好的交互性,這也是ViewPager一出現便大受程序員歡迎的緣由。如此好用的控件,你是否是已經蠢蠢欲動了呢?不廢話,咱們將以項目爲嚮導,由淺入深的講解ViewPager,開始ViewPager的學習之旅吧。java
2 何時可使用ViewPager?android
任何新的技術,最難的不是學習如何使用它,而是明白何時使用它最合適。正所謂物盡其用,只有正確的技術用在了正確的地方,那麼才能發揮該技術最大的功效,作出好的應用。下面結合一些典型場景來讓不瞭解ViewPager的你瞭解在什麼狀況下使用ViewPager纔是最好的。ViewPager最典型的應用場景主要包括引導頁導航,輪轉廣告,和頁面菜單。能夠這麼說,但凡遇到界面切換的需求,均可以考慮ViewPager。拋磚引玉,剩下的就看讀者發揮想象力了。程序員
3 ViewPager的基本入門(和ListView對比學習)數據庫
那如何使用它呢,與ListView相似,咱們也須要一個適配器,他就是PagerAdapter。ViewPager採用MVC模式將前段顯示與後端數據進行分離,也就是說器裝載數據並非直接添加數據,而是,須要使用PagerAdapter。PagerAdapter至關於,MVC模式中的C(Controller,控制器),ViewPager至關MVC模式中的V(View,視圖),爲ViewPager提供的數據List,數組或者數據庫,就至關於MVC中的M(Mode,模型)。後端
學習ViewPager不只僅是學習ViewPager單一個控件那麼簡單,咱們須要圍繞MVC模式,把ViewPager用到的數據(M),視圖(V),控制器(C)都理一遍,明白如何把他們,組合在一塊兒,達到ViewPager的切換效果。api
咱們經過一個簡單的項目來認識一下ViewPager的使用方式。數組
ViewPager,它是google SDk中自帶的一個附加包的一個類,能夠用來實現屏幕間的切換,在V4包中。app
三步曲:ide
在主佈局文件main.xml中添加ViewPager以下:
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" tools:context="com.example.testviewpage_1.MainActivity" > <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" /> </RelativeLayout>
其中,其中 <android.support.v4.view.ViewPager /> 是ViewPager對應的組件,要將其放到想要滑動的位置,能夠全屏顯示,也能夠半屏,任意大小,由程序員按需求控制。
① 新建三個layout,用於滑動切換的視圖:
咱們的三個視圖都很是簡單,裏面沒有任何的控件,你們固然能夠往裏添加各類控件,但這裏是個DEMO,只詳解原理便可,因此我這裏僅僅用背景來區別不用layout佈局。
layout1.xml
<?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:background="#ffffff" android:orientation="vertical" > </LinearLayout>
layout2.xml
<?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:background="#ffff00" android:orientation="vertical" > </LinearLayout>
layout3.xml
<?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:background="#ff00ff" android:orientation="vertical" > </LinearLayout>
② 聲明變量:
private View view1, view2, view3;
private List<View> viewList;//view數組
private ViewPager viewPager; //對應的viewPager
咱們來看看上面的變量聲明:
首先viewPager對應 <android.support.v4.view.ViewPager/>控件。
view1, view2, view3對應咱們的三個layout,即layout1.xml,layout2.xml,layout3.xml
viewList是一個View數組,盛裝上面的三個VIEW
③ 數據的初始化:
viewPager = (ViewPager) findViewById(R.id.viewpager); LayoutInflater inflater=getLayoutInflater(); view1 = inflater.inflate(R.layout.layout1, null); view2 = inflater.inflate(R.layout.layout2,null); view3 = inflater.inflate(R.layout.layout3, null); viewList = new ArrayList<View>();// 將要分頁顯示的View裝入數組中 viewList.add(view1); viewList.add(view2); viewList.add(view3);
獲取到找到ViewPager ,賦值給變量,最後將實例化的view1,view2,view3添加到viewList中。
PagerAdapter 是ViewPager的適配器。
適配器咱們在ListView裏面早就使用過,listView經過重寫GetView()函數來獲取當前要加載的Item。而PageAdapter不太相同,畢竟PageAdapter是單個VIew的合集。PagerAdapter在instantiateItem()裏面給佈局容器添加了將要顯示的視圖。
PageAdapter 必須重寫的四個函數:
boolean isViewFromObject(View arg0, Object arg1)
int getCount()
void destroyItem(ViewGroup container, int position,Object object)
Object instantiateItem(ViewGroup container, int position)
下面,咱們就看看四個主要方法改如何重寫,都分別作了什麼吧
@Override public int getCount() { // TODO Auto-generated method stub return viewList.size(); } getCount(),返回滑動的View的個數。 @Override public void destroyItem(ViewGroup container, int position, Object object) { // TODO Auto-generated method stub container.removeView(viewList.get(position)); } destroyItem,從容器中刪除指定position的View @Override public Object instantiateItem(ViewGroup container, int position) { // TODO Auto-generated method stub container.addView(viewList.get(position)); return viewList.get(position); } };
instantiateItem()方法中,我先講指定position位置的View添加到容器中,末了,將本View返回。
@Override public boolean isViewFromObject(View arg0, Object arg1) { // TODO Auto-generated method stub return arg0 == arg1; }
這裏爲何這麼寫暫不作講解,知道這樣寫便可,後面咱們會單獨講解清楚。
這麼簡單,咱們就實現了三個view間的相互滑動。
第一個界面想第二個界面滑動 第二個界面想第三個界面滑動
如下是所有核心代碼:
package com.example.testviewpage_1; import java.util.ArrayList; import java.util.List; import java.util.zip.Inflater; import android.app.Activity; import android.os.Bundle; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class MainActivity extends Activity { private View view1, view2, view3; private ViewPager viewPager; //對應的viewPager private List<View> viewList;//view數組 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); viewPager = (ViewPager) findViewById(R.id.viewpager); LayoutInflater inflater=getLayoutInflater(); view1 = inflater.inflate(R.layout.layout1, null); view2 = inflater.inflate(R.layout.layout2,null); view3 = inflater.inflate(R.layout.layout3, null); viewList = new ArrayList<View>();// 將要分頁顯示的View裝入數組中 viewList.add(view1); viewList.add(view2); viewList.add(view3); PagerAdapter pagerAdapter = new PagerAdapter() { @Override public boolean isViewFromObject(View arg0, Object arg1) { // TODO Auto-generated method stub return arg0 == arg1; } @Override public int getCount() { // TODO Auto-generated method stub return viewList.size(); } @Override public void destroyItem(ViewGroup container, int position, Object object) { // TODO Auto-generated method stub container.removeView(viewList.get(position)); } @Override public Object instantiateItem(ViewGroup container, int position) { // TODO Auto-generated method stub container.addView(viewList.get(position)); return viewList.get(position); } }; viewPager.setAdapter(pagerAdapter); } }
至此咱們已經基本瞭解了ViewPager,學會了基本用法,接下來,咱們就來詳細學習ViewPager的核心PagerAdapter。
4 從PagerAdapter說開去——解讀PagerAdapter的四大函數
4.1 且看官方文檔怎麼說?
最權威的講解是官方文檔,都是英文的,很差排版,我就不貼出來了,如下是我根據文檔翻譯出來的。有不明白的,能夠本身看官方文檔:
http://developer.android.com/reference/android/support/v4/view/PagerAdapter.html(加紅)
安卓提供一個適配器用於填充ViewPager頁面. 你極可能想要使用一個更加具體的實現, 例如:
FragmentPagerAdapter or FragmentStatePagerAdapter.
當你實現一個PagerAdapter時,至少須要覆蓋如下幾個方法:
instantiateItem(ViewGroup, int)
destroyItem(ViewGroup, int, Object)
isViewFromObject(View, Object)
PagerAdapter比AdapterView的使用更加普通.ViewPager使用回調函數來表示一個更新的步驟,而不是使用一個視圖回收機制。在須要的時候pageradapter也能夠實現視圖的回收或者使用一種更爲巧妙的方法來管理視圖,好比採用能夠管理自身視圖的fragment。
① viewpager不直接處理每個視圖而是將各個視圖與一個鍵聯繫起來。這個鍵用來跟蹤且惟一表明一個頁面,不只如此,該鍵還獨立於這個頁面所在adapter的位置。當pageradapter將要改變的時候他會調用startUpdate函數, 接下來會調用一次或屢次的instantiateItem或者destroyItem。最後在更新的後期會調用finishUpdate。當finishUpdate返回時 instantiateItem返回的對象應該添加到父ViewGroup destroyItem返回的對象應該被ViewGroup刪除。methodisViewFromObject(View, Object)表明了當前的頁面是否與給定的鍵相關聯。
② 對於很是簡單的pageradapter或許你能夠選擇用page自己做爲鍵,在建立而且添加到viewgroup後instantiateItem方法裏返回該page自己便可
destroyItem將會將該page從viewgroup裏面移除。isViewFromObject方法裏面直接能夠返回view == object。
pageradapter支持數據集合的改變,數據集合的改變必需要在主線程裏面執行,而後還要調用notifyDataSetChanged方法。和baseadapter很是類似。數據集合的改變包括頁面的添加刪除和修改位置。viewpager要維持當前頁面是活動的,因此你必須提供getItemPosition方法。
上面的FragmentPagerAdapter 和FragmentStatePagerAdapter很是經常使用,咱們放到後面來說。
上面的話,重點只有兩段:① ,②
(1)第一段說明了,鍵(Key)的概念,首先這裏要清楚的一點是,每一個滑動頁面都對應一個Key,並且這個Key值是用來惟一追蹤這個頁面的,也就是說每一個滑動頁面都與一個惟一的Key一一對應。你們先有這個概念就好,關於這個Key是怎麼來的,下面再講。
(2)當前page自己能夠做爲鍵,直接在destroyItem()返回,用來標示本身。下面,咱們來說講Key
① destroyItem(ViewGroup, int, Object)
該方法把給定位置的界面叢容器中移除,負責從容器中刪除視圖,確保在finishUpdate(viewGroup)返回時視圖可以被移除。
來看看咱們前面的項目是怎麼重寫這個方法的:
@Override public void destroyItem(ViewGroup container, int position, Object object) { // TODO Auto-generated method stub container.removeView(viewList.get(position)); }
將給定位置的視圖從container中移除了…… 這個方法必須被實現,並且不能調用父類,不然拋出異常。(說該方法沒有被覆蓋)
返回當前有效視圖的個數。
@Override public int getCount() { // TODO Auto-generated method stub return viewList.size(); }
返回了當前須要顯示的視圖的個數。
接下來的兩個方法是重點。
③ instantiateItem(ViewGroup, int)
這個方法實現的功能是建立指定位置的視圖,同時肩負着添加該建立的視圖到指定容器container中,而這一步,要確保在finishUpdate(viewGroup)返回以後完成。
該方法返回一個表明該視圖的鍵(key),不必非是視圖自己,也能夠是這個頁面的其餘容器,個人理解是不必視圖自己,只要這個返回值能表明當前視圖,並與視圖一意對應便可,好比返回和該視圖對應的position能夠嗎?(接下來咱們作個例子試試)
總結:
給container添加一個視圖。
返回表明該視圖的Key
該方法和destroyItem(ViewGroup, int, Object)同樣,在finishUpdate(ViewGroup)這句話執行完以後執行。
咱們來看看咱們是怎麼作的:
@Override public Object instantiateItem(ViewGroup container, int position) { // TODO Auto-generated method stub container.addView(viewList.get(position)); return viewList.get(position); } };
沒有錯,這裏咱們給container添加了一個View viewList.get(position),,並將該視圖做爲key返回了。
回過頭來,咱們看看第四章的官方文檔翻譯:
② 對於很是簡單的pageradapter或許你能夠選擇用page自己做爲鍵,在建立而且添加到viewgroup後instantiateItem方法裏返回該page自己便可
destroyItem將會將該page從viewgroup裏面移除。isViewFromObject方法裏面直接能夠返回view == object。
就是這裏,把當前的View做爲key傳出去,那麼這個key在哪裏被使用呢?就得來看看下面的方法了。
④ isViewFromObject(View, Object)
功能:該函數用來判斷instantiateItem(ViewGroup, int)函數所返回來的Key與一個頁面視圖是不是表明的同一個視圖(即它倆是不是對應的,對應的表示同一個View)
返回值:若是對應的是同一個View,返回True,不然返回False。
在上面的項目中,咱們這樣作的:
@Override public boolean isViewFromObject(View arg0, Object arg1) { // TODO Auto-generated method stub return arg0 == arg1; }
因爲在instantiateItem()中,咱們做爲Key返回來的是當前的View,因此在這裏判斷時,咱們直接將Key與View看是否相等來判斷是不是同一個View。
發散思惟:若是咱們在instantiateItem()返回的是表明當前視圖的position而非自己呢?這裏該怎麼作?接下來咱們就解答你的疑問。
上面咱們想必對key有個初步認識,下面咱們舉個例子來講明一下key和View的關係,因爲key要和View一一對應,這裏我把和View一一對應的position做爲key返回,而後在上面的項目的基礎上修改。這裏只展現須要修改的代碼。
咱們更改了兩個地方:
(1)instantiateItem()
@Override public Object instantiateItem(ViewGroup container, int position) { // TODO Auto-generated method stub container.addView(viewList.get(position)); return position ; } };
(2)二、isViewFromObject ()
@Override public boolean isViewFromObject(View arg0, Object arg1) { // TODO Auto-generated method stub //根據傳來的key(arg1),找到view,判斷與傳來的參數View arg0是否是同一個視圖 return arg0 == viewList.get((int)Integer.parseInt(arg1.toString())); }
判斷instantiateItem()返回的key與視圖是否對應,這裏咱們返回的是position,咱們須要根據position找到對應的View,與傳過來的View對比,看看是否對應。注意:這裏,咱們要先將obect對應轉換爲int類型:(int)Integer.parseInt(arg1.toString());而後再根據position找到對應的View;
5 ViewPager的進階,添加標題欄
View能夠添加標題欄,用來指示當前滑動到哪一頁。先來一張效果圖:
PagerTabStrip是ViewPager的一個關於當前頁面、上一個頁面和下一個頁面的一個非交互的指示器。它常常做爲ViewPager控件的一個子控件被被添加在XML佈局文件中。在你的佈局文件中,將它做爲子控件添加在ViewPager中。並且要將它的 android:layout_gravity 屬性設置爲TOP或BOTTOM來將它顯示在ViewPager的頂部或底部。每一個頁面的標題是經過適配器的getPageTitle(int)函數提供給ViewPager的。
① PagerTabStrip能夠做爲控件直接添加到xml佈局文件中。
② 重寫getPageTitle(int)來給PagerTabStrip提供標題。
你也許會發現上面只有上部分一部分的地方纔有滑動切換,是由於我更改了佈局文件。
(1) 先來看看佈局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.testviewpage_2.MainActivity" > <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="wrap_content" android:layout_height="200dip" android:layout_gravity="center"> <android.support.v4.view.PagerTitleStrip android:id="@+id/pagertitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top" /> </android.support.v4.view.ViewPager> </RelativeLayout>
這裏將layout_height更改成200dip,只因此這麼作,是爲了告訴你們,只要在想要實現滑動切換的地方添加上<android.support.v4.view.ViewPager />就能夠實現切換,無所謂位置和大小,跟普通控件同樣!!!!!!
重點是咱們將PagerTabStrip做爲子控件直接鑲嵌在ViewPager中,設置layout_gravity="top" 或者 buttom。
(2) 重寫適配器的getPageTitle()函數
在元項目基礎上咱們作了以下更改:
一、定義變量:
private List<String> titleList; //標題列表數組
申請了一個標題數組,來存儲三個頁面所對應的標題、
二、初始化
titleList = new ArrayList<String>();// 每一個頁面的Title數據
titleList.add("王鵬");
titleList.add("姜語");
titleList.add("結婚");
添加了標題數據
三、重寫CharSequence getPageTitle(int )函數
@Override public CharSequence getPageTitle(int position) { // TODO Auto-generated method stub return titleList.get(position); }
根據位置返回當前所對應的標題。
5.2 PagerTabStrip
PagerTabStrip使用方法和上面相似。
先來看看效果:
效果和PagerTitleStrip差很少,可是有微小差異:
PagerTabStrip在當前頁面下,標題的下方有一個橫線做爲導航。
PagerTabStrip的Tab是能夠點擊的,點擊標題能夠跳轉到對應的頁面。
PagerTabStrip是ViewPager的一個關於當前頁面、上一個頁面和下一個頁面的一個可交互的指示器。它常常做爲ViewPager控件的一個子控件被被添加在XML佈局文件中。在你的佈局文件中,將它做爲子控件添加在ViewPager中。並且要將它的 android:layout_gravity 屬性設置爲TOP或BOTTOM來將它顯示在ViewPager的頂部或底部。每一個頁面的標題是經過適配器的getPageTitle(int)函數提供給ViewPager的。
注意:可交互的,這就是PagerTabStrip和PagerTitleStrip最大的不同。PagerTabStrip是可交互的,PagerTitleStrip是不可交互的。
用法與PagerTitleStrip徹底相同,即:
一、首先,文中提到:在你的佈局文件中,將它做爲子控件添加在ViewPager中。
二、第二,標題的獲取,是重寫適配器的getPageTitle(int)函數來獲取的。
看看實例:
一、XML佈局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.testviewpage_2.MainActivity" > <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center"> <android.support.v4.view.PagerTabStrip android:id="@+id/pagertab" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top"/> </android.support.v4.view.ViewPager> </RelativeLayout>
能夠看到,一樣,是將PagerTabStrip做爲ViewPager的一個子控件直接插入其中,固然android:layout_gravity=""的值同樣要設置爲top或bottom。
二、重寫適配器的getPageTitle()函數
代碼裏面不用改
@Override public CharSequence getPageTitle(int position) { // TODO Auto-generated method stub return titleList.get(position); }
根據位置返回當前所對應的標題。
6 Fragment 和 ViewPager的完美結合—— FragmentPagerAdapter
前面講解了ViewPager的普通實現方法,但android官方最推薦的一種實現方法倒是使用fragment,Fragment的碎片化功能大大的豐富了ViewPager的功能和表現形式。先前咱們實現ViewPager使用的是ViewPagerAdapter。而對於fragment,使用的是FragmentPagerAdapter和FragmentStatePagerAdapter。下面咱們來學習一下。
FragmentPagerAdapter是PagerAdapter的子類,專門用來呈現fragment頁面的,這些Fragment會一直保存在FragmentManager,以方便用戶隨時取用。
FragmentPagerAdapter適用於有限個fragment的頁面管理,由於你所訪問過的fragment都會保存在內存中。因爲,fragment保存着大量的各類狀態,這樣就形成了比較大的內存開銷。故,當遇到大量的頁面切換的時候,建議採用FragmentStatePagerAdapter,這個咱們會在下面的章節講到。
FragmentPagerAdapter使用過程:
public class FragAdapter extends FragmentPagerAdapter { private List<Fragment> mFragments; public FragAdapter(FragmentManager fm,List<Fragment> fragments) { super(fm); // TODO Auto-generated constructor stub mFragments=fragments; } @Override public Fragment getItem(int arg0) { // TODO Auto-generated method stub return mFragments.get(arg0); } @Override public int getCount() { // TODO Auto-generated method stub return mFragments.size(); } }
很簡單吧,只須要繼承FragmentPagerAdapter實現兩個方法getItem(int arg)和 getCount(),就能夠了。
這裏,咱們定義了一個fragment的List對象,在構造方法裏面初始化了。以下
public FragAdapter(FragmentManager fm,List<Fragment> fragments) { super(fm); // TODO Auto-generated constructor stub mFragments=fragments; }
接下來咱們實現了getCount(),和前面同樣返回了頁面的個數。這裏咱們返回了List對象的大小。List就是fragment的集合,有多少個fragment就展現多少個頁面,這點很容易理解。以下:
@Override public int getCount() { // TODO Auto-generated method stub return mFragments.size(); }
最後,根據傳過來的鍵Key參數,返回該當前要顯示的fragment,以下:
@Override public Fragment getItem(int arg0) { // TODO Auto-generated method stub return mFragments.get(arg0); }
下面咱們要分別構造3個Fragment,這裏,咱們第一個fragment1有一個能夠點擊的按鈕,第二個和第三個fragment2,fragment3分別用不一樣的背景代替。
第一個Fragment類:
XML:(layout1.xml)
<?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:background="#ffffff" android:orientation="vertical" > <Button android:id="@+id/fragment1_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="show toast" /> </LinearLayout>
Fragment1的java代碼:
public class Fragment1 extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // TODO Auto-generated method stub View view= inflater.inflate(R.layout.layout1, container, false); //對View中控件的操做方法 Button btn = (Button)view.findViewById(R.id.fragment1_btn); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub Toast.makeText(getActivity(), "點擊了第一個fragment的BTN", Toast.LENGTH_SHORT).show(); } }); return view; } }
這裏我加入了一個按鈕,在onCreateView()方法裏面加載了layout1,返回了要顯示的View,同時,給按鈕添加了一個監聽事件,這裏爲了向讀者說明利用了fragment咱們能夠實現各類各樣的交互,ViewPager能作到的不只僅是動態的圖片,而是動態的交互。
第二個Fragment類:
XML代碼:(layout2.xml)和上面的代碼同樣,沒有作任何更改
<?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:background="#ffff00" android:orientation="vertical" > </LinearLayout>
java代碼:
public class Fragment2 extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // TODO Auto-generated method stub View view=inflater.inflate(R.layout.layout2, container, false); return view; } }
第三個Fragment類:
XML代碼:(layout3.xml)一樣,沒作任何更改
<?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:background="#ff00ff" android:orientation="vertical" > </LinearLayout>
Java代碼
public class Fragment3 extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // TODO Auto-generated method stub View view=inflater.inflate(R.layout.layout3, container, false); return view; } }
6.1.3 主Activity我繼承了FragmentActivity,只有FragmentActivity內部才能內嵌Fragment普通Activity是不行的。
public class MainActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //構造適配器 List<Fragment> fragments=new ArrayList<Fragment>(); fragments.add(new Fragment1()); fragments.add(new Fragment2()); fragments.add(new Fragment3()); FragAdapter adapter = new FragAdapter(getSupportFragmentManager(), fragments); //設定適配器 ViewPager vp = (ViewPager)findViewById(R.id.viewpager); vp.setAdapter(adapter); } }
很簡單,咱們構造了一個適配器,而後爲ViewPager設置的適配器,和前面幾乎同樣。而適配器裏面傳入的,就是那三個咱們準備好的fragment。
看看效果:
在第一個頁面加一個按鈕 第一頁面向第二頁面滑動
第二頁面向第三個頁面滑動
和FragmentPagerAdapter相比,它更適用於大量頁面的展現,當整個fragment再也不被訪問,則會被銷燬(因爲預加載,默認最多保存3個fragment),只保存其狀態,這相對於FragmentPagerAdapter佔有了更少的內存,爲何大量頁面用FragmentStatePagerAdapter?不言而喻了吧。
FragmentStatePagerAdapter的用法和FragmentPagerAdapter同樣,這裏就再也不贅述。
注意:
在初次使用的FragmentPagerAdapter的時候,曾爆出類型轉換的異常。這是爲何呢?跟蹤代碼才發現出錯的地方發生在fragments.add(new Fragment1()); 錯誤提示沒法將Fragment1(Fragment的子類)強制轉換成Fragment,當時真是莫名其妙,明明Fragment1就是Fragment,爲何說不是呢?通過仔細排查才發如今Fragment1裏面導入的是android.app.Fragment,而在Activity類導入的是爲android.support.v4.app.Fragment。統一導入以後才消除異常,不細心形成的錯誤每每難以排查,讓人糾結,這裏咱們在使用FragmentPagerAdapter必須注意導入正確的包android.support.v4.app.Fragment。
ViewPager可以如此流暢的切換頁面得益於其預加載的機制,那麼什麼是ViewPager的預加載呢?
概括掌握兩點:
① ViewPager會預先加載左右兩邊的圖片,預加載的個數最多3個。前方超出當個數由4個的時候,最前方的會被銷燬。預加載和銷燬分別回調如下兩個方法:
instantiateItem(ViewGroup, int)
destroyItem(ViewGroup, int, Object)
② 限制:當左邊圖片的position小於0的時候,不會預加載;
當右邊的圖片的position大於或者等於item總數的時候,也不會預加載。
我畫了一張示意圖:以下左邊0位置的被銷燬
8 學以至用,用ViewPager作個選項卡。
至此,咱們已經基本學完了ViewPager的經常使用特性。學貴於致用,接下來咱們經過一個涵蓋面全的例子,來把咱們所學的知識用一遍。
作一個選項卡效果,咱們馬上想到ViewPagerIndicator,利用咱們以上學過的知識,就能夠輕易實現這個功能。
先來一張效果圖,激發激發熱血吧:
上圖 左右滑動,或者點擊文字,界面會切換,同時,頁卡文字下方的滑塊也會滑動,指示當前顯示的頁面。
8.1 準備佈局
回憶一下,咱們在使用ViewPagerIndicator的時候,會在ViewPager的上面添加ViewPagerIndicator,而後經過ViewPagerIndicator的setViewPager(ViewPager mPager)設置ViewPager,使得ViewPagerIndicator指示器與ViewPager相關聯。
這裏,咱們用一個包含幾個 TextView的LinearLayout,下邊一個ImageView 替換,以下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <LinearLayout android:id="@+id/linearLayout1" android:layout_width="fill_parent" android:layout_height="60dip" android:background="#FFFFFF" > <TextView android:id="@+id/text1" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1.0" android:gravity="center" android:text="頁卡1" android:textColor="#000000" android:textSize="22.0dip" /> <TextView android:id="@+id/text2" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1.0" android:gravity="center" android:text="頁卡2" android:textColor="#000000" android:textSize="22.0dip" /> <TextView android:id="@+id/text3" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1.0" android:gravity="center" android:text="頁卡3" android:textColor="#000000" android:textSize="22.0dip" /> </LinearLayout> <ImageView android:id="@+id/cursor" android:layout_width="fill_parent" android:layout_height="wrap_content" android:scaleType="matrix" android:src="@drawable/a" /> <android.support.v4.view.ViewPager android:id="@+id/vPager" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_weight="1.0" android:background="#000000" android:flipInterval="30" android:persistentDrawingCache="animation" /> </LinearLayout>
下面是ViewPager。
接下來準備3個切換的佈局:(3個佈局都是一個RelativeLayout,只是,背景顏色不一樣而已)
fragment_main_1.xml,fragment_main_2.xml,fragment_main_3.xml
fragment_main_1.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000000" > </RelativeLayout>
fragment_main_2.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" > </RelativeLayout>
fragment_main_3.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ff0000" > </RelativeLayout>
下面由淺入深,一步步把功能作完:
8.2 先完成ViewPager界面切換,和3 基本入門那一章節一致。
① 初始化 ViewPager佈局
private void initViewPager() { vPager = (ViewPager) findViewById(R.id.vPager); List<View> listViews = new ArrayList<View>(); listViews.add(View.inflate(this, R.layout.fragment_main_1, null)); listViews.add(View.inflate(this, R.layout.fragment_main_2, null)); listViews.add(View.inflate(this, R.layout.fragment_main_3, null)); MyPagerAdapter adapter = new MyPagerAdapter(listViews); vPager.setAdapter(adapter); // 給ViewPager設置監聽 MyOnPagerChangeListener listener = new MyOnPagerChangeListener(); vPager.setOnPageChangeListener(listener); }
這一部分相信你們很熟悉了,無非作了2步:
(1)給ViewPager設置適配器。(加載了3個咱們已經準備好的佈局)
(2)給ViewPager設置滑動頁面監聽。
在此就再也不多講,下面是適配器的實現:
class MyPagerAdapter extends PagerAdapter{ List<View> listViews; public MyPagerAdapter(List<View> listViews) { super(); this.listViews = listViews; } @Override public int getCount() { // TODO Auto-generated method stub return listViews.size(); } @Override public boolean isViewFromObject(View arg0, Object arg1) { // TODO Auto-generated method stub return arg0 == arg1; } @Override public Object instantiateItem(View container, int position) { // TODO Auto-generated method stub ((ViewPager)container).addView(listViews.get(position)); return listViews.get(position); } @Override public void destroyItem(View container, int position, Object object) { ((ViewPager)container).removeView(listViews.get(position)); } }
至此,咱們已經能夠切換界面了。但是,咱們發現選項卡下面的指示滑動條並不能隨着頁面的切換而移動,從而標識當前頁面。這就是咱們下一步要作的。
8.3 這裏,咱們完成指示滑動條的移動。
思路:
經過對ViewPager頁面切換的監聽,用惟一動畫相應的距離實現標識滑塊的移動。
① 滑塊相關數據初始化
這一段是很重要的,先貼出核心代碼,隨後詳細講解。
private void initImageView() { cursor = (ImageView) findViewById(R.id.cursor); bmpw = BitmapFactory.decodeResource(getResources(), R.drawable.a).getWidth(); //滑塊的寬度 DisplayMetrics dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); //給DisplayMetrics賦值 screenW = dm.widthPixels; offset = (screenW/3 - bmpw)/2; //滑塊動畫初始位置 //設置動畫初始位置 Matrix matrix = new Matrix(); matrix.postTranslate(offset, 0); cursor.setImageMatrix(matrix); }
經過上面的代碼主要作了這幾個事兒:
(1)
cursor = (ImageView) findViewById(R.id.cursor);
//滑塊的寬度
bmpw = BitmapFactory.decodeResource(getResources(), R.drawable.a).getWidth();
加載了滑塊,得到了滑塊的寬度。
(2)得到了屏幕的寬度
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm); //給DisplayMetrics賦值
screenW = dm.widthPixels; //得到屏幕寬度
上面的代碼經過一個類WindowManager得到屏幕的相關信息,保存在 DisplayMetrics 對象裏面,而後獲取其屏幕寬度。
(3)計算滑塊的初始位置
offset = (screenW/3 - bmpw)/2; //滑塊動畫初始位置
滑塊的初始位置的計算,請看以下示意圖
(4)
//設置動畫初始位置
Matrix matrix = new Matrix();
matrix.postTranslate(offset, 0);
cursor.setImageMatrix(matrix);
這段代碼作的事情也很簡單,給滑塊設置了初始位置,即當選項頁面爲第0頁的時候滑塊的位置。這裏是經過Matrix 對象來設置滑塊的位置信息。
② 實現頁面監聽類的方法
class MyOnPagerChangeListener implements OnPageChangeListener{ int one = offset * 2 + bmpw; //選項卡0 -> 1 的偏移量 int two = one * 2; // //選項卡1 -> 2 的偏移量 @Override public void onPageScrollStateChanged(int arg0) { // TODO Auto-generated method stub } @Override public void onPageScrolled(int arg0, float arg1, int arg2) { // TODO Auto-generated method stub } @Override public void onPageSelected(int position) { Animation animation = null; Toast.makeText(MainActivity.this, "position:"+position, Toast.LENGTH_SHORT).show(); Log.i("hql-->", "one=="+one+" two=="+two); Log.i("hql-->", "offset=="+offset+" bmpw=="+bmpw+"screenW"+screenW); switch (position) { case 0: if(currentIndex == 1){ animation = new TranslateAnimation(one, 0, 0, 0); }else if(currentIndex == 2){ animation = new TranslateAnimation(two, 0, 0, 0); } break; case 1: if(currentIndex == 0){ animation = new TranslateAnimation(offset, one, 0, 0); }else if(currentIndex == 2){ animation = new TranslateAnimation(two, one, 0, 0); } break; case 2: if(currentIndex == 0){ animation = new TranslateAnimation(offset, two, 0, 0); }else if(currentIndex == 1){ animation = new TranslateAnimation(one, two, 0, 0); } break; } currentIndex = position; //記錄當前的頁面號 animation.setDuration(300); //設置動畫時間 animation.setFillAfter(true); //設置停留在動畫後 cursor.startAnimation(animation); } }
上面主要作了兩件事:
① 計算由第0頁到第1頁,滑塊移動的距離。
int one = offset * 2 + bmpw; //選項卡0 -> 1 的偏移量
int two = one * 2; // //選項卡1 -> 2 的偏移量
計算方法無非就是數學題,我畫了張示意圖。
② 實現onPageSelected(int Position)方法
@Override public void onPageSelected(int position) { Animation animation = null; Toast.makeText(MainActivity.this, "position:"+position, Toast.LENGTH_SHORT).show(); Log.i("hql-->", "one=="+one+" two=="+two); Log.i("hql-->", "offset=="+offset+" bmpw=="+bmpw+"screenW"+screenW); switch (position) { case 0: if(currentIndex == 1){ animation = new TranslateAnimation(one, 0, 0, 0); }else if(currentIndex == 2){ animation = new TranslateAnimation(two, 0, 0, 0); } break; case 1: if(currentIndex == 0){ animation = new TranslateAnimation(offset, one, 0, 0); }else if(currentIndex == 2){ animation = new TranslateAnimation(two, one, 0, 0); } break; case 2: if(currentIndex == 0){ animation = new TranslateAnimation(offset, two, 0, 0); }else if(currentIndex == 1){ animation = new TranslateAnimation(one, two, 0, 0); } break; } currentIndex = position; //記錄當前的頁面號 animation.setDuration(300); //設置動畫時間 animation.setFillAfter(true); //設置停留在動畫後 cursor.startAnimation(animation); }
經過該方法,咱們能夠很清晰的看到,這個方法里根據傳入的表明當前頁面的鍵,這裏是Position,來在滑動的時候使滑塊作相應的移動。
作完這一步,咱們的滑塊已經能夠隨着頁面切換而移動起來了。
8.4 爲了更好的交互,完成點擊選項卡切換頁面。
思路:給3個選項卡(這裏是3個TextView)設置點擊事件,在點擊事件裏面經過ViewPager.setCurrentItem(int num)設置當前頁面的鍵(KEY).
private void initTextView() { TextView text1 = (TextView) findViewById(R.id.text1); TextView text2 = (TextView) findViewById(R.id.text2); TextView text3 = (TextView) findViewById(R.id.text3); text1.setOnClickListener(this); text2.setOnClickListener(this); text3.setOnClickListener(this); }
初始化選項卡文字,這些文字作成控件的時候能夠設置,同時給他們設置監聽。
處理點擊事件:
@Override public void onClick(View v) { switch (v.getId()) { case R.id.text1: vPager.setCurrentItem(0); break; case R.id.text2: vPager.setCurrentItem(1); break; case R.id.text3: vPager.setCurrentItem(2); break; }
代碼很簡單,至此,點擊選項卡也能夠實現頁面切換,實現了雙向互動。
這裏我再把變量申明和OnCreate()方法裏面的調用代碼貼出。
private int offset; //滑塊動畫初始位置 private int bmpw; //滑塊的寬度 private int currentIndex = 0; //默認當前也卡號爲0 private ImageView cursor; //滑塊 private int screenW; //屏幕寬度 private ViewPager vPager; //ViewPager @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initImageView(); //初始化滑塊 initTextView(); //初始化文字 initViewPager(); //初始化ViewPager佈局 }
好了,一個雙向互動的選項卡就完成了,怎麼樣,是否是很簡單?其實ViewPagerIndicator實現的思路和咱們作的選項卡差很少,親愛讀者們,花點時間把這個Demo封裝一下,提供一些方便的設置方法,好比設置任意長度的title,就是一個精簡的選顯卡控件了。
由此咱們能夠逆推,固然咱們要掌握一個開源的控件時並不難,都是由使用到熟悉。一般都是在佈局裏面引用該控件,而後在java代碼經過findViewById()方法找到該空間,以後經過該控件的一些方法,設置相應參數便可使用。固然,熟悉的基礎上,下一步,就是深刻了解,畢竟市面上的開源控件再怎麼樣都是人寫的,那就極可能不是你想固然那樣,因此經過一些方法去了解開源控件很重要。我瞭解一個控件的特性通常先閱讀說明,而後經過調試,打log日誌和假設驗證的方式,來了解一個控件,這些方式都很普通,也很簡單,卻多用幾回就駕輕就熟了,可是卻很實用,重要的是要有求真精神。
一路來,從認識到靈活運用,由淺入深,咱們好像挺順利,其實否則,一個好的應用都是從bug中產出的,好的程序也是錯誤中不斷優化出來。咱們在計算滑塊的移動距離的時候,會發生計算結果爲0的狀況:以下
int one = offset * 2 + bmpw; //選項卡0 -> 1 的偏移量
int two = one * 2; // //選項卡1 -> 2 的偏移量
這是爲何呢?
通過代碼跟蹤,最後才發現咱們先初始化ViewPager的監聽類,在初始化ViewPager的過程當中,咱們計算了移動的偏移量,滑塊動畫初始位置offset 和screenW都是尚未初始化,都是0,此時咱們再計算滑塊長度和初始值,致使移動距離one和two都爲0.因此當咱們遇到問題,調試是一個很好的辦法。
前面的代碼太簡單了,就不上傳了,這裏分享最後的自定義選項卡的Demo你們能夠下載來看看: