ViewPager是什么?
实现在一个布局中,能够
进行多个内容界面的切换,
通过手指的滑动,可以让这些界面平滑滚动
ViewPager的使用
- ViewPager 是Android Support v4 包中存在的。
- ViewPager 是一个UI控件,可以直接在布局中使用
- ViewPager 需要Adapter来设置显示的内容。
- ViewPager 只能够左右滑动,用来放复杂的界面
PagerAdapter的使用
用于给ViewPager设置可以显示的控件、界面;
继承PagerAdapter,实现规定的方法。
ViewFlipper : 类似于ViewPager但不能够平滑滚动,可以直接放到布局中,在布局内部写控件,和ViewPager不同。
注意事项:1. 对于PagerAdapter直接子类,必须实现 instantiateItem, 以及 destroyItem 两个方法,instantiateItem 方法内部必须手动调用 container.addView,如果加载布局,要使用 类似ListView内部加载的方式
ViewPager页面加载的规则
- ViewPager当前页,左侧和右侧都已经加载,当向一个方向移动的时候,总是能够确保当前显示页面左侧右侧都存在,因此,每次移动的时候,都会销毁超出去的部分,加载当前页面左右的页面。
- 相应的PagerAdapter的调用,就会按照加载的顺序来调用;
- 当当前页面显示 0索引的时候,1代表的页面也存在,当页面显示最后一个的时候,倒数第二个也存在。
- 因为ViewPager需要平滑的移动左侧或者右侧的内容,所以,预先加载。
getPageTitle(int pos) 获取指定位置的标题
ViewPager复杂界面显示
- ViewPager 允许放置Fragment,当Fragment存在,功能可以实现非常复杂,完善的功能;现代Android客户端都会包含ViewPager + Fragment;
- ViewPager的Adapter 可以使用FragmentPagerAdapter,以及 FragmentStatePagerAdapter 两个适配器,来进行ViewPager包含Fragment的设置。
FragmentPagerAdapter
- 子类实现 getCount() 返回Fragment的总个数;
- 子类实现 Fragment getItem(int position) 获取第几个Fragment;
- 在Adapter之外创建List集合来存Fragment,这样,传递给Adapter的时候,就不需要判断索引了,直接获取Fragment就行了,由Activity来控制显示什么。
- Fragment生命周期管理:类似于PagerAdapter,每一个加载的时候,都会进入到resume()状态,当需要销毁的时候,并不是真正的销毁Fragment,而是进行视图的销毁,因此,每一个Fragment在移除的时候,都是调用到 onDestroyView() 这个状态;再次需要显示的时候,直接onCreateView() 回复状态。
- 对于数据的加载,再Fragmnet onCreate 当中进行的话,相当于只执行一次,不论ViewPager如何滑动,也只是执行一次。
- 如果需要每次切换回来,进行刷新,那么就不应该在onCreate进行获取。如果在onCreate和 onResume() 中都进行加载,那么也是可以的。 onCreate 用于初始化一次的数据加载。
-
- FragmentPagerAdapter 在切换Fragment的时候,当一个Fragment 需要移出布局的时候,会根据声明周期方法,调用到 onDestroyView,用于释放UI资源;但是不会再继续销毁了,Fragment的数据依然会保留;便于下一次ViewPager再显示这个Fragment的时候,直接恢复UI就可以了;
- 应用场景:通常都是应用程序最外层的Tab的管理,用于固定数量的、数据不需要大量刷新的场景;也就是软件主界面的Tab管理;
FragmentStatePagerAdapter
- FragmentStatePagerAdapter 作用:同样是在ViewPager中加载Fragment;
- FragmentStatePagerAdapter 在进行Fragment切换的时候,每次移除Fragment 的时候,都会将Fragment进行销毁,直接调用到生命周期方法: onDetach(),
每次重新加载Fragment ,都会进行 onAttach() -> onCreate() -> onCreateView() -> onActivityCreated()
每次销毁之前,都会调用Fragment的 onSaveInstanceState(), 每次加载Fragment的时候,都会自动的进行onCreate(Bundle),onCreateView(…,Bundle),参数中的Bundle都会包含之前 saveInstanceState 保存的数据,所以,如果需要保存用户动态数据,onSaveInstanceState() 保存数据, onCreateView() 重新从Bundle参数加载数据。
Fragment的参数传递
- Fragment 在 new之后,可以进行 setArguments的设置,设置参数,参数类型为 Bundle
- Fragment 不要创建 带有参数的构造方法,因为Fragment在销毁之后 ViewPager 如果再次加载的时候,参数就没有了
- 当使用setArguments(Bundle) 之后,在任意的Fragment生命周期中,都可以通过 getArguments() 来获取参数;
- 当Fragment 销毁之后再次加载,同样能够取到参数,通过这种方式来支持 ViewPager item的销毁和加载。
两个FragmentAdapter的区别
- FragmentPagerAdapter 会在切换的时候,会进行onDestroyView 的调用
- FragmentStatePagerAdapter,用于加载大量的Fragment用的,会减少一些内存问题,速度会慢一些;
- 对于每次销毁Fragment,再onCreate 这种,从运行效率上面,会慢一些;但是内存占用小一些。
- 官方的说明:
FragmentPagerAdapter 用于数量是固定的,并且少量的Fragment显示的时候,来使用。应用场景:应用程序的模块划分;
FragmentStatePagerAdapter: 用于数量多的,数量变化的Fragment显示;应用场景:
支持 notifyDataSetChanged()
ViewPager对于onResume影响
- 默认情况下,当前ViewPager如果显示 0 位置的,那么 1位置的Fragment 也会 onResume,如果进行进行网络加载,那么会同时加载两个Fragment的数据。
- 默认情况下,可以同时有3个Fragment会进入onResume的状态;
实例代码
PicturePagerAdapter的写法
/**
* Created by Lulu on 2016/9/14.
* 1. 必须要重写/实现 四个方法; 不能够只有两个!!!!!
*/
public class PicturePageAdapter extends PagerAdapter {
private static final String TAG = PicturePageAdapter.class.getSimpleName();
private Context mContext;
private List<Integer> mImageRes;
private View.OnClickListener mOnClickListener;
public void setOnClickListener(View.OnClickListener onClickListener){
mOnClickListener = onClickListener;
}
public PicturePageAdapter(Context context, List<Integer> imageRes) {
mContext = context;
mImageRes = imageRes;
}
/**
* 返回ViewPager一共有多少项
*/
@Override
public int getCount() {
int ret = 0;
if (mImageRes != null) {
ret = mImageRes.size();
}
Log.d(TAG, "getCount: ");
return ret;
}
/**
* View 参数 是否 和 Object 有关系
* <p>
* ViewPager 会先进行对象的管理, 利用对象来管理和定位View
* (通俗的讲: 看view和Object是否对应)
*
* @param view
* @param object : 这个参数就是 instantiateItem 方法的返回值对象!!!!
* @return
*/
@Override
public boolean isViewFromObject(View view, Object object) {
Log.d(TAG, "isViewFromObject: ");
return view == object;
}
/**
* 当ViewPager需要显示一页的时候, 会调用这个方法, 传递指定的页数
*
* @param container
* @param position
* @return
*/
@Override
public Object instantiateItem(ViewGroup container, int position) {
Log.d(TAG, "instantiateItem: ");
// 加载布局或者创建控件, 然后添加到 ViewGroup
View ret = null;
if (position == mImageRes.size() - 1) {
//最后一个
ret = LayoutInflater.from(mContext).inflate(R.layout.pic_last, container, false);
ImageView imageView = (ImageView) ret.findViewById(R.id.main_pager);
imageView.setImageResource(android.R.drawable.bottom_bar);
Button btn = (Button) ret.findViewById(R.id.btn_jump_next);
btn.setOnClickListener(mOnClickListener);
} else {
ImageView imageView = new ImageView(mContext);
imageView.setImageResource(mImageRes.get(position));
ret = imageView;
// !!!必须把创建/加载的视图, 手动添加到ViewGroup中!!!
}
container.addView(ret);
return ret;
}
/**
* 当ViewPager把一个页面左右移动的时候, 达到一个限制的位置之后, 就会删除这个页面
* 调用这个方法
*
* @param container
* @param position
* @param object
*/
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Log.d(TAG, "destroyItem: ");
//必须手动的把创建的View, 从ViewGroup删除
container.removeView((View) object);
}
}
MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewPager viewPager = (ViewPager) findViewById(R.id.main_pager);
List<Integer> ids = new ArrayList<>();
ids.add(R.mipmap.ic_launcher);
ids.add(android.R.drawable.ic_dialog_alert);
ids.add(android.R.drawable.btn_star);
PicturePageAdapter adapter = new PicturePageAdapter(this, ids);
//设置预加载页数,
//设置2代表 当页面索引为2的时候, 当前页面索引保留两个, 右侧保留两个
// viewPager.setOffscreenPageLimit(4);
adapter.setOnClickListener(this);
viewPager.setAdapter(adapter);
}
通用FragmentAdapter的写法和使用
/**
* Created by Lulu on 2016/9/14.
*/
public class CommonFragmentPagerAdapter extends FragmentPagerAdapter{
private static final String TAG = CommonFragmentPagerAdapter.class.getSimpleName();
private List<BaseFragment> mFragments;
public CommonFragmentPagerAdapter(FragmentManager fm, List<BaseFragment> fragments) {
super(fm);
mFragments = fragments;
}
/**
* 返回指定页码的 Fragment , 用于显示
* @param position
* @return
*/
@Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
/**
* 返回ViewPager的Fragment的个数
* @return
*/
@Override
public int getCount() {
int ret = 0;
if (mFragments != null) {
ret = mFragments.size();
}
Log.d(TAG, "getCount: " + ret);
return ret;
}
@Override
public CharSequence getPageTitle(int position) {
return mFragments.get(position).getFragmentTitle();
}
}
JDActivity.java
/**
* 使用ViewPager 显示 Fragment
* <p/>
* 在onResume中不要有什么提示操作
*/
public class JDActivity extends AppCompatActivity implements RadioGroup.OnCheckedChangeListener, ViewPager.OnPageChangeListener {
private ViewPager mPager;
private RadioGroup mTabBar;
private TextView mIndicator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_jd);
mPager = (ViewPager) findViewById(R.id.jb_pager);
mTabBar = (RadioGroup) findViewById(R.id.pager_tab_bar);
mIndicator = (TextView) findViewById(R.id.text_indicator);
List<BaseFragment> fragmentList = new ArrayList<>();
fragmentList.add(new FirstFragment());
fragmentList.add(new SecondFragment());
fragmentList.add(new ThirdFragment());
fragmentList.add(new FourthFragment());
//参数1: FragmentManager: 如果在Activity中, 创建的Adapter那么必须使用 getSupportFragment,
// 如果在Fragment内部, 创建Adapter, 那么必须使用getSupportFragmentManager
CommonFragmentPagerAdapter adapter = new CommonFragmentPagerAdapter(
getSupportFragmentManager(),
fragmentList
);
mPager.setAdapter(adapter);
//ViewPager 与 RadioGroup 联动/
mTabBar.setOnCheckedChangeListener(this);
mPager.addOnPageChangeListener(this);
}
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (checkedId) {
case R.id.pager_tab_item_0:
mPager.setCurrentItem(0);
break;
case R.id.pager_tab_item_1:
mPager.setCurrentItem(1);
break;
case R.id.pager_tab_item_2:
mPager.setCurrentItem(2);
break;
case R.id.pager_tab_item_3:
mPager.setCurrentItem(3);
}
}
///ViewPager滚动监听方法///
/**
* 当页面在持续滚动的时候, 会自动回调, 能够表示页面滚动的情况
*
* @param position
* @param positionOffset
* @param positionOffsetPixels
*/
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
//1. 获取RadioGroup中指定的Child, position对应的Child
View at = mTabBar.getChildAt(position);
//2. 获取指定指示器控件的坐标
float x = ViewCompat.getX(at);
//3. 计算指示器的位置
//positionOffset 代表一小格的百分比, 也就是当前RadioButton宽度的百分比距离
x = x + (at.getWidth() * positionOffset);
ViewCompat.setX(mIndicator, x);
}
/**
* 滚动完成, 切换新页面的时候, 回调
*/
@Override
public void onPageSelected(int position) {
switch (position) {
case 0:
mTabBar.check(R.id.pager_tab_item_0);
break;
case 1:
mTabBar.check(R.id.pager_tab_item_1);
break;
case 2:
mTabBar.check(R.id.pager_tab_item_2);
break;
case 3:
mTabBar.check(R.id.pager_tab_item_3);
break;
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
//end
}
使用Google的支持包实现滑动指示器
我们使用的是Google的com.android.support:design:24.1.1
第1种实现方式
public abstract class BaseFragment extends Fragment{
public BaseFragment(){}
public abstract String getFragmentTitle();
}
/**
* Android5.0 以后 TabLayout可以与ViewPager直接联动
*/
public class SettingsActivity extends AppCompatActivity implements TabLayout.OnTabSelectedListener {
private ViewPager mPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
//1. TabLayout 可以使用代码添加多个 Tab
//2. 可以与ViewPager直接联动
TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
mPager = (ViewPager) findViewById(R.id.settings_pager);
// 3.在ViewPager对象初始化之后, TabLayout可以设置一个TabListener
// 因为TabLayout的第一个Tab, 在添加的时候, 会自动调用Listener
tabLayout.addOnTabSelectedListener(this);
/*
Add a tab to this layout. The tab will be added at the end of the list.
If this is the first tab to be added it will become the selected tab.
*/
//创建Tab对象
TabLayout.Tab tab = tabLayout.newTab();
tab.setText("First");
tabLayout.addTab(tab);
tab = tabLayout.newTab();
tab.setText("Second");
tabLayout.addTab(tab);
tab = tabLayout.newTab();
tab.setText("Third");
tabLayout.addTab(tab);
tab = tabLayout.newTab();
tab.setText("Fourth");
tabLayout.addTab(tab);
List<BaseFragment> fragments = new ArrayList<>();
fragments.add(new FirstFragment());
fragments.add(new SecondFragment());
fragments.add(new ThirdFragment());
fragments.add(new FourthFragment());
CommonFragmentPagerAdapter adapter = new CommonFragmentPagerAdapter(
getSupportFragmentManager(),
fragments
);
mPager.setAdapter(adapter);
//TabLayout内部实现了一个OnPageChangerListener, 能够实现跟随
mPager.addOnPageChangeListener(
new TabLayout.TabLayoutOnPageChangeListener(tabLayout)
);
}
/OnTabSelectedListener
/**
* 当Tab从没有选中到选中的状态时, 调用这个方法
* @param tab
*/
@Override
public void onTabSelected(TabLayout.Tab tab) {
//切换
mPager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
/**
* 当前Tab已经是选中的情况下, 再次点击这个Tab会回调这个方法
* @param tab
*/
@Override
public void onTabReselected(TabLayout.Tab tab) {
//刷新数据
}
///OnTabSelectedListener/
}
第2种实现方式
public class NewsActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_news);
//1. TabLayout取出来
// ViewPager
TabLayout tabLayout = (TabLayout) findViewById(R.id.news_tab_layout);
ViewPager pager = (ViewPager) findViewById(R.id.news_pager);
//2. ViewPager Adapter设置
List<BaseFragment> fragments = new ArrayList<>();
fragments.add(new FirstFragment());
fragments.add(new SecondFragment());
fragments.add (new ThirdFragment());
fragments.add(new FourthFragment());
CommonFragmentPagerAdapter adapter = new CommonFragmentPagerAdapter(
getSupportFragmentManager(),
fragments
);
pager.setAdapter(adapter);
//3. setup
// setupWithViewPager 要求ViwPager的Adapter必须去实现
// getrPagerTitle(int position)
tabLayout.setupWithViewPager(pager);
}