Android控件——ViewPager


1 認識一下ViewPager?

     ViewPager最早出自4.0版本,那么低版本如何能使用ViewPager呢?為了兼容低版本安卓設備,谷歌官方給我們提供了一個的軟件包android.support.v4.view。這個V4包囊了只有在安卓3.0以上可以使用的api,而viewpager就是其中之一。利用它,我們可以做很多事情,從最簡單的引導頁導航,到輪轉廣告,到頁面菜單等等,無不出現ViewPager的身影。應用廣泛,簡單好用,更好的交互性,這也是ViewPager一出現便大受程序員歡迎的原因。如此好用的控件,你是不是已經蠢蠢欲動了呢?不廢話,我們將以項目為向導,由淺入深的講解ViewPager,開始ViewPager的學習之旅吧。

2 什么時候可以使用ViewPager?

任何新的技術,最難的不是學習如何使用它,而是明白什么時候使用它最合適。正所謂物盡其用,只有正確的技術用在了正確的地方,那么才能發揮該技術最大的功效,做出好的應用。下面結合一些典型場景來讓不了解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的切換效果。

      我們通過一個簡單的項目來認識一下ViewPager的使用方式。

首先新建項目,引入ViewPager控件

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

三步曲:

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)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()函數

在元項目基礎上我們做了如下更改:

1、定義變量:

private List<String> titleList;  //標題列表數組 

申請了一個標題數組,來存儲三個頁面所對應的標題、

2、初始化

titleList = new ArrayList<String>();// 每個頁面的Title數據  

titleList.add("王鵬");  

titleList.add("姜語");  

titleList.add("結婚");

添加了標題數據

3、重寫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完全相同,即:

1、首先,文中提到:在你的布局文件中,將它作為子控件添加在ViewPager中。

2、第二,標題的獲取,是重寫適配器的getPageTitle(int)函數來獲取的。

看看實例:

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"  

    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。

2、重寫適配器的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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM