一、概述 ViewPager是android-support-v4中提供的類,它是一個容器類,常用於頁面之間的切換。 繼上篇文章《ViewPager之引導頁》之後,本文主要介紹ViewPager更為通用的實踐: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.xml(activity的佈局):
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="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.ViewPager 16 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 <FrameLayout 23 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 @Override 23 public void onDestroy() { 24 super.onDestroy(); 25 stopAnim(); 26 } 27 28 @Override 29 public void onPageIn() { 30 super.onPageIn(); 31 startAnim(); 32 } 33 34 @Override 35 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 @Override 17 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 @Override 28 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 @Override 37 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 @Override 15 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 @Override 41 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之後才會onCreateView 77 //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預設頁左邊的頁面會被預載入) //<