Android控件——ViewPager

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控件

ViewPager,它是google SDk中自帶的一個附加包的一個類,能夠用來實現屏幕間的切換,在V4包中。app

三步曲:ide

3.1  準備視圖 View

        在主佈局文件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對應的組件,要將其放到想要滑動的位置,能夠全屏顯示,也能夠半屏,任意大小,由程序員按需求控制。

3.2   準備數據模型 ,Mode

① 新建三個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中。

3.3  準備控制器(Controller)—— PagerAdapter 

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)

 

getCount()

 

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

 

4.2  ViewPager的 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中移除了…… 這個方法必須被實現,並且不能調用父類,不然拋出異常。(說該方法沒有被覆蓋)

getCount()   

  返回當前有效視圖的個數。

@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而非自己呢?這裏該怎麼作?接下來咱們就解答你的疑問。

4.3   自定義key

上面咱們想必對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的進階,添加標題欄

5.1 PagerTitleStrip

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。下面咱們來學習一下。

6.1  FragmentPagerAdapter

 FragmentPagerAdapter是PagerAdapter的子類,專門用來呈現fragment頁面的,這些Fragment會一直保存在FragmentManager,以方便用戶隨時取用。

FragmentPagerAdapter適用於有限個fragment的頁面管理,由於你所訪問過的fragment都會保存在內存中。因爲,fragment保存着大量的各類狀態,這樣就形成了比較大的內存開銷。故,當遇到大量的頁面切換的時候,建議採用FragmentStatePagerAdapter,這個咱們會在下面的章節講到。

FragmentPagerAdapter使用過程:

6.1.1 適配器的實現:

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);  

    } 

 

6.1.2  構造Fragment類。

  下面咱們要分別構造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。

看看效果:

                   在第一個頁面加一個按鈕                   第一頁面向第二頁面滑動

      

第二頁面向第三個頁面滑動

6.2 FragmentStatePagerAdapter

和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。

7  ViewPager的預加載機制。

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你們能夠下載來看看:

                                                                                                                       ViewPager自定義選項卡.zip

相關文章
相關標籤/搜索