android.support.v4.ViewPager類在 API 4+ Support
支持包中開始爲我們提供,它可以讓我們有能力左右滑動以'頁'的形式展示數據。我們可以通過繼承
PagerAdapter
來生成頁面形式的視圖。介紹具體的使用方式之前先來看下效果
首先需要在layout文件中配置ViewPager View
[html]
view plaincopy
- <android.support.v4.view.ViewPager
- android:id="@+id/pager"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="50dp" />
其實ViewPager本質上是一個View,繼承體系如下,可以發現它的上面就是ViewGroup
,它是有能力add View的
java.lang.Object | |||
↳ | android.view.View | ||
↳ | android.view.ViewGroup | ||
↳ | android.support.v4.view.ViewPager |
上面提到過使用 PagerAdapter
來生成一個頁面視圖,也就是一頁, PagerAdapter 決定了ViewPager一共有多少頁,負責每頁的初始化,每頁的銷燬等工作
[java]
view plaincopy
- class MyPagerAdapter extends PagerAdapter{
- @Override
- public int getCount() {
- return mViewList .size();
- }
- @Override
- public Object instantiateItem(View container, int position) {
- Log. i("INFO", "instantiate item:"+position);
- ((ViewPager) container).addView( mViewList.get(position),0);
- return mViewList .get(position);
- }
- @Override
- public void destroyItem(View container, int position, Object object) {
- ((ViewPager) container).removeView( mViewList.get(position));
- }
- @Override
- public boolean isViewFromObject(View arg0, Object arg1) {
- return arg0 == arg1;
- }
可以看到ViewPager其實是一個組件容器,可以爲它的每頁添加一個要顯示的View,用於展現數據
[java]
view plaincopy
- mLayoutInflater = getLayoutInflater();
- //可以按照需求進行動態創建Layout,這裏暫用靜態的xml layout
- mViewList.add(mLayoutInflater.inflate(R.layout.per_pager1, null));
- mViewList.add(mLayoutInflater.inflate(R.layout.per_pager2, null));
- mViewList.add(mLayoutInflater.inflate(R.layout.per_pager3, null));
- ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
- mPagerAdapter = new MyPagerAdapter();
- viewPager.setAdapter(mPagerAdapter);
上面的每一個經過inflate的Layout就代表的是每一頁的佈局,就像我們平常使用的佈局文件一樣...
我們經常看到一般在ViewPager下會有一些圓點來指示當前我們瀏覽到第幾頁了,要實現這種效果,我們要根據頁面的數量來動態生成圓點的數量,並添加到一個LinearLayout中,便於管理
[java]
view plaincopy
- Bitmap bitmap = BitmapFactory. decodeResource(getResources(), R.drawable.icon_dot_normal );
- for (int i = 0; i < mViewList.size(); i++) {
- Button bt = new Button(this );
- bt.setLayoutParams( new ViewGroup.LayoutParams(bitmap.getWidth(),bitmap.getHeight()));
- bt.setBackgroundResource(R.drawable. icon_dot_normal );
- mNumLayout .addView(bt);
- }
那我們怎麼才能知道當前滑動到第幾頁了呢 ? 其實我們可以爲ViewPager設置一個OnPageChangeListener
頁面改變監聽器來監聽頁面的改變,從而得到當前滑動到了第幾頁
[java]
view plaincopy
- viewPager. setOnPageChangeListener( new OnPageChangeListener() {
- @Override
- public void onPageSelected( int position) {
- if (mPreSelectedBt != null){
- mPreSelectedBt .setBackgroundResource(R.drawable. icon_dot_normal);
- }
- Button currentBt = (Button)mNumLayout .getChildAt(position);
- currentBt.setBackgroundResource(R.drawable. home_page_dot_select );
- mPreSelectedBt = currentBt;
- //Log.i("INFO", "current item:"+position);
- }
- @Override
- public void onPageScrolled( int arg0, float arg1, int arg2) {
- // TODO Auto-generated method stub
- }
- @Override
- public void onPageScrollStateChanged( int arg0) {
- // TODO Auto-generated method stub
- }
- });
mPreSelectedBt 只是爲了記錄當前顯示的圓點,爲下一次圓點焦點切換做準備,下面看一個完整的實現
[java]
view plaincopy
- public class MainActivity extends Activity {
- ArrayList<View> mViewList = new ArrayList<View>();
- LayoutInflater mLayoutInflater;
- LinearLayout mNumLayout;
- Button mPreSelectedBt;
- MyPagerAdapter mPagerAdapter;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- mLayoutInflater = getLayoutInflater();
- //可以按照需求進行動態創建Layout,這裏暫用靜態的xml layout
- mViewList.add(mLayoutInflater.inflate(R.layout.per_pager1, null));
- mViewList.add(mLayoutInflater.inflate(R.layout.per_pager2, null));
- mViewList.add(mLayoutInflater.inflate(R.layout.per_pager3, null));
- ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
- mPagerAdapter = new MyPagerAdapter();
- viewPager.setAdapter(mPagerAdapter);
- mNumLayout = (LinearLayout) findViewById(R.id.ll_pager_num);
- Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_dot_normal);
- for (int i = 0; i < mViewList.size(); i++) {
- Button bt = new Button(this);
- bt.setLayoutParams(new ViewGroup.LayoutParams(bitmap.getWidth(),bitmap.getHeight()));
- bt.setBackgroundResource(R.drawable.icon_dot_normal);
- mNumLayout.addView(bt);
- }
- viewPager.setOnPageChangeListener(new OnPageChangeListener() {
- @Override
- public void onPageSelected(int position) {
- if(mPreSelectedBt != null){
- mPreSelectedBt.setBackgroundResource(R.drawable.icon_dot_normal);
- }
- Button currentBt = (Button)mNumLayout.getChildAt(position);
- currentBt.setBackgroundResource(R.drawable.home_page_dot_select);
- mPreSelectedBt = currentBt;
- //Log.i("INFO", "current item:"+position);
- }
- @Override
- public void onPageScrolled(int arg0, float arg1, int arg2) {
- // TODO Auto-generated method stub
- }
- @Override
- public void onPageScrollStateChanged(int arg0) {
- // TODO Auto-generated method stub
- }
- });
- }
- class MyPagerAdapter extends PagerAdapter{
- @Override
- public int getCount() {
- return mViewList.size();
- }
- @Override
- public Object instantiateItem(View container, int position) {
- Log.i("INFO", "instantiate item:"+position);
- ((ViewPager) container).addView(mViewList.get(position),0);
- return mViewList.get(position);
- }
- @Override
- public void destroyItem(View container, int position, Object object) {
- Log.i("INFO", "destroy item:"+position);
- ((ViewPager) container).removeView(mViewList.get(position));
- }
- @Override
- public boolean isViewFromObject(View arg0, Object arg1) {
- return arg0 == arg1;
- }
- }
- }
如果你仔細觀察打印日誌會發現,PagerAdapter的實現方式有點特殊,永遠都會存在兩個經過初始化的'頁'(page>2時),顯示當前頁時會初始化下一頁,會destroy上上頁,一輪滑動完成的打印信息如下
[html]
view plaincopy
- 04-02 22:39:59.880: I/INFO(27187): instantiate item:0
- 04-02 22:39:59.880: I/INFO(27187): instantiate item:1
- 04-02 22:40:03.890: I/INFO(27187): instantiate item:2
- 04-02 22:40:39.020: I/INFO(27187): destroy item:0
當要顯示的頁數發生改變時,我們可以通過調用PagerAdapter的notifyDataSetChanged()
來通知數據的改變(必須在UI主線程中通知更新),同時圓點也需要進行更新
[java]
view plaincopy
- mViewList.add(mLayoutInflater.inflate(R.layout.per_pager1, null));
- Button bt = new Button(this);
- bt.setLayoutParams(new ViewGroup.LayoutParams(bitmap.getWidth(),bitmap.getHeight()));
- bt.setBackgroundResource(R.drawable.icon_dot_normal);
- mNumLayout.addView(bt);
- mPagerAdapter.notifyDataSetChanged();
以上基本上就是ViewPager基本的使用方式,還是比較簡單的,但ViewPager最常使用的方式是結合
Fragment 來一起使用,這種方式可以很方便的來管理每個頁面的生命週期,Android也爲我們提供了好些種固定的實現好了的Adapters來給ViewPager使用,它們包括
FragmentPagerAdapter,
FragmentStatePagerAdapter,FragmentPagerAdapter,
和 FragmentStatePagerAdapter
,它們中的每一種都可以編寫少量簡單的代碼就爲我們建立一個完整的用戶界面,下一節將進行介紹
使用Fragment
來表示一頁,顯得更加簡單和直觀,Fragment 本身提供的一些特性可以讓我們方便的對每一頁進行管理,使用FragmentManager可以根據ID或TAG來查找Fragment
, 動態添加、刪除、替換,Fragment 可以管理自己的生命週期,像Activity一樣提供了一些生命週期回調方法。
讓Fragment
成爲ViewPager的一頁時,FragmentManager會一直保存管理創建好了的Fragment,即使當前不是顯示的這一頁,Fragment對象也不會被銷燬,在後臺默默等待重新顯示。但如果Fragment不再可見時,它的視圖層次會被銷燬掉,下次顯示時視圖會重新創建。
出於使用FragmentPagerAdapter 時,Fragment對象會一直存留在內存中,所以當有大量的顯示頁時,就不適合用FragmentPagerAdapter
了,FragmentPagerAdapter 適用於只有少數的page情況,像選項卡。這個時候你可以考慮使用FragmentStatePagerAdapter
,當使用FragmentStatePagerAdapter 時,如果Fragment不顯示,那麼Fragment對象會被銷燬,但在回調onDestroy()方法之前會回調onSaveInstanceState(Bundle
outState)方法來保存Fragment的狀態,下次Fragment顯示時通過onCreate(Bundle savedInstanceState)把存儲的狀態值取出來,FragmentStatePagerAdapter 比較適合頁面比較多的情況,像一個頁面的ListView
最後一點要注意,當使用FragmentPagerAdapter
時一定要爲它的宿主ViewPager設置一個有效的ID !
下面是一個使用FragmentPagerAdapter
的示例,需要注意的是destroyItem()方法並不是去Destroy Fragment對象 而是Destroy的是Fragment的視圖
, 這一點需要理解注意了
[java]
view plaincopy
- class MyFragmentPagerAdapter extends FragmentPagerAdapter{
- public MyFragmentPagerAdapter(FragmentManager fm) {
- super(fm);
- }
- @Override
- public Fragment getItem(int position) {
- return MyPageFragment.create(position);
- }
- @Override
- public int getCount() {
- return mPagerNum; // 代表頁數
- }
- @Override
- public void destroyItem(ViewGroup container, int position, Object object) {
- // 這裏Destroy的是Fragment的視圖層次,並不是Destroy Fragment對象
- super.destroyItem(container, position, object);
- Log.i("INFO", "Destroy Item...");
- }
- }
下面是一個Fragment的示例程序,記得前面說過,使用FragmentPagerAdapter
適配器時,創建好了的Fragment會一直在內存中,不會被銷燬,但它的視圖層次是會被銷燬的,所以onCreate()方法只會被調用一次,而
onCreateView() 方法,每次Fragment從不可見到可見時會被調用,可以看到Fragment有一些生命週期回調方法 onPause()、onDestroy()等等
[java]
view plaincopy
- public class MyPageFragment extends Fragment {
- public static final String ARG_PAGE = "page_num";
- // 當前頁
- private int currentPageNum;
- public MyPageFragment() {
- }
- public static MyPageFragment create(int pagerNum) {
- MyPageFragment myPageFrament = new MyPageFragment();
- Bundle arg = new Bundle();
- arg.putInt(ARG_PAGE, pagerNum);
- myPageFrament.setArguments(arg);
- return myPageFrament;
- }
- /* (non-Javadoc)
- * @See android.support.v4.app.Fragment#onCreate(android.os.Bundle)
- *
- * Fragment創建的時候調用
- *
- */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Log.i("INFO", "onCreate..");
- currentPageNum = getArguments().getInt(ARG_PAGE);
- }
- /* (non-Javadoc)
- * @see android.support.v4.app.Fragment#onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle)
- *
- * Fragment從不可見到可見時調用
- *
- */
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- Log.i("INFO", "onCreateView..");
- ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.per_pager1,
- container, false);
- switch (currentPageNum) {
- case 0:
- rootView.setBackgroundResource(R.drawable.page1_bg);
- break;
- case 1:
- rootView.setBackgroundResource(R.drawable.page2_bg);
- break;
- case 2:
- rootView.setBackgroundResource(R.drawable.page3_bg);
- break;
- default:
- break;
- }
- return rootView;
- }
- @Override
- public void onPause() {
- super.onPause();
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- Log.i("INFO", "MyFragment Destroy...");
- }
- }