Published by orzz.org(). (https://orzz.org/android-unlimited-viewpager/)
ViewPager是google SDK里用来做滑动式的视图翻页的控件。我们可以新建一个默认的Android Application,指定Navigation Type为“Swipe Views (ViewPager)”,可以很直观的看到ViewPager的用法。
向导会自动帮我们在activity_main.xml里写入如下内容:
1 2 3 4 5 6 |
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.testviewpager.MainActivity" /> |
另外fragment_main.xml里会自动写上一个TextView:
1 2 3 4 |
<TextView android:id="@+id/section_label" android:layout_width="wrap_content" android:layout_height="wrap_content" /> |
之后,MainActivity则是非常简单的,ViewPager里的每一个视图都是一个PlaceholderFragment:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
/** * A placeholder fragment containing a simple view. */ public static class PlaceholderFragment extends Fragment { /** * The fragment argument representing the section number for this * fragment. */ private static final String ARG_SECTION_NUMBER = "section_number"; /** * Returns a new instance of this fragment for the given section * number. */ public static PlaceholderFragment newInstance(int sectionNumber) { PlaceholderFragment fragment = new PlaceholderFragment(); Bundle args = new Bundle(); args.putInt(ARG_SECTION_NUMBER, sectionNumber); fragment.setArguments(args); return fragment; } public PlaceholderFragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_main, container, false); TextView textView = (TextView) rootView.findViewById(R.id.section_label); textView.setText(Integer.toString(getArguments().getInt(ARG_SECTION_NUMBER))); return rootView; } } |
然后通过一个自定义的FragmentPagerAdapter,为ViewPager提供视图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
/** * A {@link FragmentPagerAdapter} that returns a fragment corresponding to * one of the sections/tabs/pages. */ public class SectionsPagerAdapter extends FragmentPagerAdapter { public SectionsPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { // getItem is called to instantiate the fragment for the given page. // Return a PlaceholderFragment (defined as a static inner class below). return PlaceholderFragment.newInstance(position + 1); } @Override public int getCount() { // Show 3 total pages. return 3; } @Override public CharSequence getPageTitle(int position) { Locale l = Locale.getDefault(); switch (position) { case 0: return getString(R.string.title_section1).toUpperCase(l); case 1: return getString(R.string.title_section2).toUpperCase(l); case 2: return getString(R.string.title_section3).toUpperCase(l); } return null; } } |
最后,在MainActivity的onCreate函数中,如下代码负责把上面的PagerAdapter放到PagerAdapter里去:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/** * The {@link android.support.v4.view.PagerAdapter} that will provide * fragments for each of the sections. We use a * {@link FragmentPagerAdapter} derivative, which will keep every * loaded fragment in memory. If this becomes too memory intensive, it * may be best to switch to a * {@link android.support.v4.app.FragmentStatePagerAdapter}. */ SectionsPagerAdapter mSectionsPagerAdapter; /** * The {@link ViewPager} that will host the section contents. */ ViewPager mViewPager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Create the adapter that will return a fragment for each of the three // primary sections of the activity. mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); // Set up the ViewPager with the sections adapter. mViewPager = (ViewPager) findViewById(R.id.pager); mViewPager.setAdapter(mSectionsPagerAdapter); } |
最终实现出来的效果如下:
当然,上面是可以翻页的,一共3页,每一页上显示的都是当前的序号。
默认情况下,ViewPager可以显示的视图数目,和PagerAdapter的getCount方法的返回值有关,如果我们把getCount改成这样:
1 2 3 4 5 |
@Override public int getCount() { // Show 3 total pages. return 5; } |
选项卡也会变成5个。虽然在这个例子里,暂时用不到PageTitle,不过后续若需要Title显示文字,getPageTitle是必须要显示正确的。
对于本例,我们可以简单的把strings.xml改成这样:
1 2 3 4 5 6 7 8 |
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">TestViewPager</string> <string name="title_section">Section</string> <string name="action_settings">Settings</string> </resources> |
接下来,改一改getPageTitle:
1 2 3 4 5 |
@Override public CharSequence getPageTitle(int position) { Locale l = Locale.getDefault(); return getString(R.string.title_section).toUpperCase(l) + " " + (position + 1); } |
这样,通过一个position总能得到一个有效的PageTitle。为了看到修改的效果,我们可以让PlaceholderFragment显示PageTitle,而不止是一个数字:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
/** * A {@link FragmentPagerAdapter} that returns a fragment corresponding to * one of the sections/tabs/pages. */ public class SectionsPagerAdapter extends FragmentPagerAdapter { public SectionsPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { // getItem is called to instantiate the fragment for the given page. // Return a PlaceholderFragment (defined as a static inner class below). return PlaceholderFragment.newInstance(getPageTitle(position).toString()); } @Override public int getCount() { // Show 3 total pages. return 5; } @Override public CharSequence getPageTitle(int position) { Locale l = Locale.getDefault(); return getString(R.string.title_section).toUpperCase(l) + " " + (position + 1); } } /** * A placeholder fragment containing a simple view. */ public static class PlaceholderFragment extends Fragment { /** * The fragment argument representing the section number for this * fragment. */ private static final String ARG_SECTION_NUMBER = "section_number"; /** * Returns a new instance of this fragment for the given section * number. */ public static PlaceholderFragment newInstance(String section) { PlaceholderFragment fragment = new PlaceholderFragment(); Bundle args = new Bundle(); args.putString(ARG_SECTION_NUMBER, section); fragment.setArguments(args); return fragment; } public PlaceholderFragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_main, container, false); TextView textView = (TextView) rootView.findViewById(R.id.section_label); textView.setText(getArguments().getString(ARG_SECTION_NUMBER)); return rootView; } } |
这样我们就有了5页的ViewPager,每一页显示当然页的PageTitle:
下面我们来实现无限多的页面。
可以考虑这样一种方法:getCount永远返回3,当我们进行翻页操作时,先全部更新所有页面上应当显示的数据,之后PagerAdapter自动调用setCurrentItem(1, false)将页面重新固定回中间那一页。这样可以让我们看起来好像是翻页了,但实际上仅仅是数据发生了变化而已。
首先,我们来实现这样一个UnlimitedPagerAdapter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
public class UnlimitedPagerAdapter extends FragmentPagerAdapter { public interface UnlimitedPager { public void onRefresh(); public void onChanged(int offset); public Fragment getItem(int position); } private ViewPager mViewPager = null; private UnlimitedPager mPager = null; private boolean mIsChanged = false; public UnlimitedPagerAdapter(ViewPager viewPager, FragmentManager fm) { super(fm); mViewPager = viewPager; } public void setPage(UnlimitedPager pager) { mPager = pager; if (mPager != null) { mPager.onRefresh(); } } @Override public Fragment getItem(int position) { return mPager.getItem(position); } @Override public int getCount() { return 3; } @Override public void setPrimaryItem(ViewGroup container, int position, Object object) { super.setPrimaryItem(container, position, object); if (position == 1) { if (mIsChanged) { if (mPager != null) { mPager.onRefresh(); } mIsChanged = false; } } else { if (mPager != null) { mPager.onChanged(position - 1); mIsChanged = true; } mViewPager.setCurrentItem(1, false); } } } |
通过setPrimaryItem,在当前页面变化的时候更新数据,刷新显示内容,重新定位页面。同样的,PlaceholderFragment需要稍微调整一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
/** * A placeholder fragment containing a simple view. */ public static class PlaceholderFragment extends Fragment { /** * The fragment argument representing the section number for this * fragment. */ private static final String ARG_SECTION_NUMBER = "section_number"; /** * Returns a new instance of this fragment for the given section * number. */ public static PlaceholderFragment newInstance(int sectionNumber) { PlaceholderFragment fragment = new PlaceholderFragment(); Bundle args = new Bundle(); args.putInt(ARG_SECTION_NUMBER, sectionNumber); fragment.setArguments(args); return fragment; } public PlaceholderFragment() { } private View mRootView = null; public void setText(int sectionNumber) { getArguments().putInt(ARG_SECTION_NUMBER, sectionNumber); if (mRootView != null) { TextView textView = (TextView) mRootView.findViewById(R.id.section_label); textView.setText(getString(R.string.title_section).toUpperCase(Locale.getDefault()) + " " + sectionNumber); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mRootView = inflater.inflate(R.layout.fragment_main, container, false); setText(getArguments().getInt(ARG_SECTION_NUMBER)); return mRootView; } } |
这样我们就可以完成UnlimitedPagerAdapter.UnlimitedPager接口,通过PlaceholderFragment提供的setText方法修改页面上显示的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/** * A {@link FragmentPagerAdapter} that returns a fragment corresponding to * one of the sections/tabs/pages. */ public class SectionsPager implements UnlimitedPagerAdapter.UnlimitedPager { private final PlaceholderFragment[] sFragments = new PlaceholderFragment[] { PlaceholderFragment.newInstance(0), PlaceholderFragment.newInstance(1), PlaceholderFragment.newInstance(2) }; private int mData = 1; @Override public void onRefresh() { sFragments[0].setText(mData); sFragments[1].setText(mData + 1); sFragments[2].setText(mData + 2); } @Override public void onChanged(int offset) { mData += offset; } @Override public Fragment getItem(int position) { return sFragments[position]; } } |
如上,在需要的时候(每次ViewPager被翻页时),onChanged就会被调用,mData会被刷新掉。之后每次onRefresh被调用时,sFragments会根据当前的mData调整每个PlaceholderFragment视图应当如何显示。经过这样设计的类关系图如下所示:
之后,在MainActivity的onCreate里把SectionsPager、UnlimitedPagerAdapter和ViewPager连起来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/** * The {@link ViewPager} that will host the section contents. */ ViewPager mViewPager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Set up the ViewPager with the sections adapter. mViewPager = (ViewPager) findViewById(R.id.pager); UnlimitedPagerAdapter adapter = new UnlimitedPagerAdapter(mViewPager, getSupportFragmentManager()); adapter.setPage(new SectionsPager()); mViewPager.setAdapter(adapter); } |
好了,大功告成了。现在我们得到了一个可以“无限”翻页的ViewPager。不过需要注意的是,这样的ViewPager和Action Bar Tabs是不能直接联动的,除非再设计一个“无限”的Action Bar Tabs。
使用上面的UnlimitedPager接口配合UnlimitedPagerAdapter,实际上也可以很轻松的实现循环翻页的ViewPager,只需要修改派生出来的SectionsPager,让数据模型形成环状就可以了。
参考文章:
Published by orzz.org(). (https://orzz.org/android-unlimited-viewpager/)