你的位置:首页 > 操作系统

[操作系统]ViewPager之Fragment页面切换


一、概述

ViewPager是android-support-v4中提供的类,它是一个容器类,常用于页面之间的切换。

继上篇文章《ViewPager之引导页》之后,本文主要介绍ViewPager更为通用的实践:ViewPager搭配Fragment实现页面切换。

这种实现方式相对于上篇文章而言,可以更好的支持不同页面各自的复杂逻辑,与此同时,也能够保障页面之间的耦合度尽可能的低。

按照惯例,先晒出效果图:

      

 

二、实现思路

首先分析一下不同区域的交互需求:

中间灰色区域除了要支持三套完全不同的逻辑之外,还要支持左右滑动切换。而顶部ActionBar和底部Tab切换都只需要更新状态,无需整体变换,也不需要整体滑动;

因此,中间灰色区域用ViewPager实现,它包含三个Fragment子页面。而顶部ActionBar和底部Tab切换各自是一个Fragment,直接隶属于Activity。

然后解决不同Fragment之间的依赖关系:

五个Fragment之间沟通的唯一纽带就是ViewPager页面的切换,因此,ViewPager页面切换时通知到不同的Fragment即可。

 [转载请保留本文地址:http://www.cnblogs.com/snser/p/5700754.html] 

三、开始干活

3.1 搭建整体框架

本文的五个Fragment采用三种方式载入:

ViewPager中的三个Fragment自然是通过其Adatper进行载入,顶部的TitleFragment(ActionBar)直接声明在layout布局中,而底部的TabFragment采用动态载入。

这样做的目的是方便后续分析不同载入方式的实际载入实际。

ok,整体的layout布局自然也就成了下面的样子:

viewpager_fragment. 

 1 <RelativeLayout ="http://schemas.android.com/apk/res/android" 2   ="http://schemas.android.com/tools" 3   android:layout_width="match_parent" 4   android:layout_height="match_parent" 5   android:background="@color/background_default" 6   tools:context="${relativePackage}.${activityClass}" > 7    8   <fragment 9     android:id="@+id/viewpager_fragment_title"10     android:name="cc.snser.cnblog5700754.TitleFragment"11     android:layout_width="match_parent"12     android:layout_height="wrap_content"13     android:layout_alignParentTop="true" />14 15   <android.support.v4.view.ViewPager16     android:id="@+id/viewpager_fragment_pager"17     android:layout_width="match_parent"18     android:layout_height="match_parent"19     android:layout_above="@+id/viewpager_fragment_container"20     android:layout_below="@+id/viewpager_fragment_title" />21   22   <FrameLayout23     android:id="@id/viewpager_fragment_container"24     android:layout_width="match_parent"25     android:layout_height="wrap_content"26     android:layout_alignParentBottom="true" >27   </FrameLayout>28   29 </RelativeLayout>

对应的,主界面的逻辑如下:

ViewPagerFragmentActivity.java:

 1 public class ViewPagerFragmentActivity extends FragmentActivity { 2   private FragmentManager mManager = getSupportFragmentManager(); 3    4   private TitleFragment mTitleFragment; 5   private TabFragment mTabFragment; 6   private ViewPager mPager; 7    8   private ViewPagerAdapter mAdapter; 9    10   private static final int DEFAULT_PAGE = 1; //默认页面 11    12   @Override 13   protected void onCreate(Bundle savedInstanceState) { 14     Log.d("Snser", "ViewPagerFragmentActivity onCreate"); 15     super.onCreate(savedInstanceState); 16     Log.d("Snser", "ViewPagerFragmentActivity onCreate setContentView"); 17     setContentView(R.layout.viewpager_fragment); 18     Log.d("Snser", "ViewPagerFragmentActivity onCreate initView"); 19     initView(); 20   } 21    22   private void initView() { 23     mTitleFragment = (TitleFragment)mManager.findFragmentById(R.id.viewpager_fragment_title); 24     mTabFragment = new TabFragment(); 25     mTabFragment.setOnTabClickListenser(new ViewPageTabClickListenser()); 26     mManager.beginTransaction().replace(R.id.viewpager_fragment_container, mTabFragment).commit(); 27     mPager = (ViewPager)findViewById(R.id.viewpager_fragment_pager); 28     mPager.setAdapter(mAdapter = new ViewPagerAdapter(mManager)); 29     mPager.setOnPageChangeListener(new ViewPageChangeListener()); 30     setCurrentItem(DEFAULT_PAGE); 31   } 32    33   @Override 34   protected void onResume() { 35     Log.d("Snser", "ViewPagerFragmentActivity onResume"); 36     super.onResume(); 37   } 38    39   private class ViewPageChangeListener implements OnPageChangeListener { 40     @Override 41     public void onPageSelected(int position) { 42       setCurrentItem(position); 43     } 44      45     @Override 46     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 47     } 48      49     @Override 50     public void onPageScrollStateChanged(int state) { 51     } 52   } 53    54   private class ViewPagerAdapter extends FragmentPagerAdapter { 55     private ArrayList<PagerFragment> mFragments = new ArrayList<PagerFragment>(); 56      57     public ViewPagerAdapter(FragmentManager fm) { 58       super(fm); 59       mFragments.add(new ClickFragment()); 60       mFragments.add(new DateFragment()); 61       mFragments.add(new AnimFragment()); 62     } 63  64     @Override 65     public Fragment getItem(int position) { 66       return mFragments.get(position); 67     } 68  69     @Override 70     public int getCount() { 71       return mFragments.size(); 72     } 73   } 74    75   private class ViewPageTabClickListenser implements OnTabClickListenser { 76     @Override 77     public void onTabClick(int tab) { 78       setCurrentItem(tab); 79     } 80   } 81     82   private void setCurrentItem(int item) { 83     if (item == mPager.getCurrentItem()) { 84       //此时是源于initView或onPageSelected的调用 85       notifyPageChangeToFragments(item); 86     } else { 87       //此时是源于initView或onTabClick的调用,后续会自动触发一次onPageSelected 88       mPager.setCurrentItem(item); 89     } 90   } 91    92   private void notifyPageChangeToFragments(int item) { 93     for (int page = 0; page != mAdapter.getCount(); ++page) { 94       final Fragment fragment = mAdapter.getItem(page); 95       if (fragment instanceof PagerFragment) { 96         if (page == item) { 97           ((PagerFragment)fragment).onPageIn(); 98         } else { 99           ((PagerFragment)fragment).onPageOut();100         }101       }102     }103     mTitleFragment.setCurrentTab(item);104     mTabFragment.setCurrentTab(item);105   }106 }

可以看到,包含三种完全不同功能的Activity,其主界面代码居然只有区区106行,这甚至比上篇文章《ViewPager之引导页》还要少。

这必须要归功于Fragment的使用。

三个页面的具体逻辑分别由DateFragment、ClickFragment、AnimFragment进行维护,而ViewPager要做的,只是进行Fragment的切换和通知。

具体来说,ViewPagerAdapter负责根据不同的postion取出不同的Fragment。而三个页面Fragment都继承自PagerFragment:

PagerFragment.java: 

1 public class PagerFragment extends Fragment{2   3   public void onPageIn() {4   }5   6   public void onPageOut() {7   }8   9 }

亦即在页面切换的时候(不管是来自滑动还是点击tab),activity都会通知三个fragment它们各自是否可见。

同时,在 notifyPageChangeToFragments 方法中,也会通知TitleFragment和TabFragment,它们该切换状态了。

3.2 Fragment的各自实现

拿AnimFragment举例,这个动画Fragment的功能是在其切入(onPageIn)的时候播放淡入的动画,而在其切出(onPageOut)的时候销毁动画。 

 1 public class AnimFragment extends PagerFragment { 2   private View mViewRoot; 3   private ImageView mImg; 4    5   private Animation mAnim; 6    7   @Override 8   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 9     Log.d("Snser", "AnimFragment onCreateView");10     mViewRoot = inflater.inflate(R.layout.viewpager_fragment_anim, container, false);11     mAnim = AnimationUtils.loadAnimation(getActivity(), R.anim.anim_fade_in);12     mAnim.setInterpolator(new LinearInterpolator());13     initView(mViewRoot);14     return mViewRoot;15   }16   17   private void initView(View root) {18     mImg = (ImageView)root.findViewById(R.id.viewpager_fragment_anim_img);19     startAnim();20   }21   22   @Override23   public void onDestroy() {24     super.onDestroy();25     stopAnim();26   }27   28   @Override29   public void onPageIn() {30     super.onPageIn();31     startAnim();32   }33   34   @Override35   public void onPageOut() {36     super.onPageOut();37     stopAnim();38   }39   40   private void startAnim() {41     if (mImg != null && mAnim != null) {42       mImg.startAnimation(mAnim);43     }44   }45   46   private void stopAnim() {47     if (mImg != null && mAnim != null) {48       mImg.clearAnimation();49     }50   }51   52 }

DateFragment的作用是在切入的时候刷新当前日期和时间:

DateFragment.java:

 1 public class DateFragment extends PagerFragment { 2  3   private View mViewRoot; 4   private TextView mTxtDate; 5    6   private SimpleDateFormat mFormat = new SimpleDateFormat("yyyy/MM/dd\nHH:mm:ss", Locale.CHINA); 7    8   @Override 9   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {10     Log.d("Snser", "DateFragment onCreateView");11     mViewRoot = inflater.inflate(R.layout.viewpager_fragment_date, container, false);12     initView(mViewRoot);13     return mViewRoot;14   }15   16   @Override17   public void onPageIn() {18     super.onPageIn();19     refreshDate();20   }21   22   private void initView(View root) {23     mTxtDate = (TextView)root.findViewById(R.id.viewpager_fragment_date_txt);24     refreshDate();25   }26   27   private void refreshDate() {28     if (mTxtDate != null) {29       mTxtDate.setText(mFormat.format(new Date()));30     }31   }32   33 }

View Code

ClickFragment则搞定了一个多次连续点击“中彩蛋”的效果:

ClickFragment.java:

 1 public class ClickFragment extends PagerFragment { 2   private View mViewRoot; 3   private Button mBtnClick; 4    5   @Override 6   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 7     Log.d("Snser", "ClickFragment onCreateView"); 8     mViewRoot = inflater.inflate(R.layout.viewpager_fragment_click, container, false); 9     initView(mViewRoot);10     return mViewRoot;11   }12   13   private void initView(View root) {14     mBtnClick = (Button)root.findViewById(R.id.viewpager_fragment_click_btn);15     mBtnClick.setOnClickListener(new MultiClickListener());16   }17   18   private static class MultiClickListener implements View.OnClickListener {19     private int mCount = 0;20     private long mLastClickTime = 0;21     22     private static final long MAX_CLICK_COUNT = 4;23     private static final long TIMEOUT_CLICK = 500;24     private static final int MSG_TIMEOUT_CLICK = 0x1001;25     26     private static Handler sHandler = new Handler() {27       @Override28       public void handleMessage(Message msg) {29         if (msg.what == MSG_TIMEOUT_CLICK && msg.obj instanceof Button) {30           Log.d("Snser", "MultiClickListener 连续点击超时");31           ((Button)msg.obj).setText("点我");32         }33       }34     };35     36     @Override37     public void onClick(View v) {38       if (v instanceof Button) {39         final long time = System.currentTimeMillis();40         if (mCount == 0 || time - mLastClickTime < TIMEOUT_CLICK) {41           //这是连续点击42           Log.d("Snser", "MultiClickListener 连续点击 mCount=" + (mCount + 1));43           ++mCount;44           mLastClickTime = time;45           sHandler.removeMessages(MSG_TIMEOUT_CLICK);46           sHandler.sendMessageDelayed(Message.obtain(sHandler, MSG_TIMEOUT_CLICK, v), TIMEOUT_CLICK);47           if (mCount < MAX_CLICK_COUNT) {48             ((Button)v).setText("点我  +" + mCount);49           } else {50             ((Button)v).setText("死机了!");51           }52         } else {53           //这是新的点击54           Log.d("Snser", "MultiClickListener 新的点击 mCount=0");55           mCount = 0;56           onClick(v);57         }58       }59     }60   }61   62 }

View Code

除了页面Fragment之外,TabFragment和TitleFragment都会在 setCurrentTab 方法中更新自己的状态。

不同的是,TabFragment会将点击tab的事件通过 OnTabClickListenser 反馈给activity: 

 1 public class TabFragment extends Fragment { 2   private View mRootView; 3    4   private TextView mTxtTabApp; 5   private TextView mTxtTabHome; 6   private TextView mTxtTabUser; 7    8   private OnTabClickInternalListener mTabClickInternalListener = new OnTabClickInternalListener(); 9   private OnTabClickListenser mOnTabClickListenser;10   11   private int mDefaultTab = 0;12   private int mCurrentTab = -1;13   14   @Override15   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {16     Log.d("Snser", "TabFragment onCreateView");17     mRootView = inflater.inflate(R.layout.viewpager_fragment_tab, container, false);18     initView(mRootView);19     return mRootView;20   }21   22   private void initView(View root) {23     mTxtTabApp = (TextView)root.findViewById(R.id.viewpager_fragment_tab_app);24     mTxtTabApp.setTag(Integer.valueOf(0));25     mTxtTabApp.setOnClickListener(mTabClickInternalListener);26     mTxtTabHome = (TextView)root.findViewById(R.id.viewpager_fragment_tab_home);27     mTxtTabHome.setTag(Integer.valueOf(1));28     mTxtTabHome.setOnClickListener(mTabClickInternalListener);29     mTxtTabUser = (TextView)root.findViewById(R.id.viewpager_fragment_tab_user);30     mTxtTabUser.setTag(Integer.valueOf(2));31     mTxtTabUser.setOnClickListener(mTabClickInternalListener);32     setCurrentTab(mDefaultTab);33   }34   35   public interface OnTabClickListenser {36     public void onTabClick(int tab);37   }38   39   private class OnTabClickInternalListener implements View.OnClickListener {40     @Override41     public void onClick(View v) {42       if (v != null && v.getTag() instanceof Integer) {43         final int tab = (Integer)v.getTag();44         if (tab != mCurrentTab && mOnTabClickListenser != null) {45           setCurrentTab(tab);46           mOnTabClickListenser.onTabClick(tab);47         }48       }49     }50   }51   52   public void setOnTabClickListenser(OnTabClickListenser listenser) {53     mOnTabClickListenser = listenser;54   }55   56   public void setCurrentTab(int tab) {57     if (mTxtTabApp != null && mTxtTabHome != null && mTxtTabUser != null) {58       mCurrentTab = tab;59       mTxtTabApp.setTextColor(getResources().getColor(R.color.viewpager_fragment_tab_normal));60       mTxtTabHome.setTextColor(getResources().getColor(R.color.viewpager_fragment_tab_normal));61       mTxtTabUser.setTextColor(getResources().getColor(R.color.viewpager_fragment_tab_normal));62       switch (tab) {63         case 0:64           mTxtTabApp.setTextColor(getResources().getColor(R.color.viewpager_fragment_tab_current));65           break;66         case 1:67           mTxtTabHome.setTextColor(getResources().getColor(R.color.viewpager_fragment_tab_current));68           break;69         case 2:70           mTxtTabUser.setTextColor(getResources().getColor(R.color.viewpager_fragment_tab_current));71           break;72         default:73           break;74       }75     } else {76       //TabFragment在Activity的onResume之后才会onCreateView77       //setCurrentTab的时候控件还没初始化,存一下初始值在initView里再初始化78       mDefaultTab = tab;79     }80   }81 }

 1 public class TitleFragment extends Fragment { 2   private View mRootView; 3   private TextView mTxt; 4    5   @Override 6   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 7     Log.d("Snser", "TitleFragment onCreateView"); 8     mRootView = inflater.inflate(R.layout.viewpager_fragment_title, container, false); 9     initView(mRootView);10     return mRootView;11   }12   13   private void initView(View root) {14     mTxt = (TextView)root.findViewById(R.id.viewpager_fragment_title_txt);15   }16   17   public void setCurrentTab(int tab) {18     if (mTxt != null) {19       switch (tab) {20         case 0:21           mTxt.setText("#ClickFragment");22           break;23         case 1:24           mTxt.setText("#DateFragment");25           break;26         case 2:27           mTxt.setText("#AnimFragment");28           break;29         default:30           break;31       }32     }33   }34 }

3.3 Fragment的加载顺序

回归到3.1节中介绍到本文采用三种方式进行Fragment的载入,目的就是为了比较不同方式下Fragment的加载时机。

在 DEFAULT_PAGE = 1 时,各个Fragment的加载顺序为: 

//     ViewPagerFragmentActivity onCreate//     ViewPagerFragmentActivity onCreate setContentView//     TitleFragment onCreateView (声明在layout布局中的Fragment在setContentView之后就会被加载)//     ViewPagerFragmentActivity onCreate initView//     TabFragment onCreateView (动态replace的Fragment在replace之后、onResume之前被加载)//     ViewPagerFragmentActivity onResume//     DateFragment onCreateView (ViewPager最先加载默认页)//     ClickFragment onCreateView (ViewPager默认页左边的页面会被预加载)//     AnimFragment onCreateView (ViewPager默认页右边的页面会被预加载)

 在 DEFAULT_PAGE = 0 时,各个Fragment的加载顺序为: 

//     ViewPagerFragmentActivity onCreate//     ViewPagerFragmentActivity onCreate setContentView//     TitleFragment onCreateView (声明在layout布局中的Fragment在setContentView之后就会被加载)//     ViewPagerFragmentActivity onCreate initView//     TabFragment onCreateView (动态replace的Fragment在replace之后、onResume之前被加载)//     ViewPagerFragmentActivity onResume//     ClickFragment onCreateView (ViewPager最先加载默认页)//     DateFragment onCreateView(ViewPager默认页右边的页面会被预加载)//     AnimFragment onCreateView(ViewPager切换到DateFragment时才会预加载AnimFragment)

 [转载请保留本文地址:http://www.cnblogs.com/snser/p/5700754.html] 

四、demo工程

先晒一下本文的动图效果:

工程源码如下(下载下面的图片,扩展名改成zip即可):

 [转载请保留本文地址:http://www.cnblogs.com/snser/p/5700754.html]