1、简介
一个继承了ViewGroup的容器,用户向ViewPager提供若干个带数据的界面。ViewPager允许这些界面进行翻转。
翻转:随着用户手指在屏幕滑动,而切换用户提供的界面。
数据界面:用户提供View或者fragment,通过适配器设置给ViewPager
2、常用api
1、setAdapter(PagerAdapter adapter) 设置适配器
2、setOffscreenPageLimit(int limit) 设置缓存的页面个数,默认是 1,代表默认缓存下一页。
3、setCurrentItem(int item) 跳转到指定页面
4、addOnPageChangeListener(OnPageChangeListener listener)页面滑动监听
5、setPageTransformer(boolean reverseDrawingOrder,PageTransformer transformer, int pageLayerType) 设置页面滑动的动画效果。
6、setPageMargin(int marginPixels) 设置两页面之间的间隔Margin
7、setPageMarginDrawable(…) 设置两页面间隔之间的divide photo ,要想显示设置的图片,需要同时设置 setPageMargin()方法
3、常见的Adapter介绍及PagerAdapter探讨
- PagerAdapter:ViewPager的Adapter基类,此类为抽象类。
- FragmentPagerAdapter
- FragmentStatePagerAdapter
(1)PagerAdapter 🌰
public class PagerAdapterDemoActivity extends AppCompatActivity {
// 准备一组图片
private int[] imgs = new int[]{
R.drawable.img_yao, R.drawable.img_zhuge,
R.drawable.img_libai, R.drawable.img_mingren,
R.drawable.img_haizei, R.drawable.img_douluo};
private List<View> mList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pager_adapter_demo);
offerData();
initViewPager();
}
private void offerData() {
// adapter 提供数据
mList = new ArrayList<>();
// 为每个view视图内的图片填充数据
for (int img : imgs) {
@SuppressLint("InflateParams") View mView = LayoutInflater.from(this).inflate(R.layout.page_pageradapter, null, false);
AppCompatImageView imageView = mView.findViewById(R.id.img);
imageView.setImageResource(img);
mList.add(mView);
}
}
private void initViewPager() {
ViewPager viewPager = findViewById(R.id.vp_pager);
// 设置adapter
viewPager.setAdapter(new MyPagerAdapter(mList));
}
}
/**
* Created by sunnyDay on 2019/12/14 10:39
*/
public class MyPagerAdapter extends PagerAdapter {
private List<View> mList;
public MyPagerAdapter(List<View> mList) {
this.mList = mList;
}
@Override
public int getCount() {
return mList == null ? 0 : mList.size();
}
/* view:view 视图
object:键对象
返回值:标识页面视图是否与键对象相关联
true:当前view界面与给定的键对应
false:当前的view界面与给定的键不对应
一般我们直接view==object,通过布尔返回值自动判断
*/
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
/*
container:ViewPager的setAdapter源码可以看出container就是父容器也即Viewpager 对象
position:当前页面索引
返回值:页面对象所关联的键
此方法内部需要开发者手动add下view到容器。这时view就被添加到Viewpager了
*/
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
container.addView(mList.get(position));
return mList.get(position);
}
/*
container:viewpager对象
position:当前页面索引
object:页面对象所关联的键
*/
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View) object);
}
}
实现思路非常简单,activity中通过for 创建了6个带数据的View,然后通过MyPagerAdapter的构造吧数据传递给ViewPager的adapter。ViewPager 为ViewGroup的子类,所以也是个容器,adapter获得数据后Viewpager就会知道,便从adapter中获得子view。填充展示。
ViewPager本身没有直接提供子视图(View或者fragment)回收机制,而是采用Adapter的回调来展示页面更新过程,PagerAdapter可以实现视图的回收、管理,例如FragmentPagerAdapter。Fragment事务来管理每个视图。
(2) 有关PagerAdapter清除View的写法
使用PagerAdapter时为啥这两种写法都行?
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View) object); // 方式1
// container.removeView(mList.get(position)); // 方式2
}
其实二者都是删除指定的界面因为,一个非常简单的PagerAdapter可以选择将页面view本身作为键对象
4、结合TabLayout+fragment
adapter结合fragment时就要用到FragmentPagerAdapter或者FragmentStatePagerAdapter了。
PagerAdapter为抽象类,FragmentPagerAdapter就是PagerAdapter实现类。它将每一个页面表示为一个 Fragment,并且每一个Fragment都将会保存到fragment manager当中。
这种pager十分适用于有一些静态fragment,例如一组tabs,的时候使用。每个页面对应的Fragment当用户可以访问的时候会一直存在内存中,但是,当这个页面不可见的时候,view hierarchy将会被销毁。这样子会导致应用程序占有太多资源。当页面数量比较大的时候,建议使用 FragmentStatePagerAdapter。
FragmentStatePagerAdapter也是PagerAdapter的直接子类。
当使用FragmentStatePagerAdapter 时,实现将只保留当前页面,当页面离开视线后,就会被消除,释放其资源;而在页面需要显示时,生成新的页面。这么实现的好处就是当拥有大量的页面时,不必在内存中占用大量的内存。
FragmentStatePageAdapter 🌰
public class FragmentStatePageAdapterActivity extends AppCompatActivity {
private com.google.android.material.tabs.TabLayout tabLayout;
private ViewPager viewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment_state_page_adapter);
getSupportActionBar().hide();
initView();
initData();
}
private void initData() {
populateTabsData();
populateViewPagerData();
}
private void populateViewPagerData() {
List<Fragment> mList = new ArrayList<>();
mList.add(new AnimeFragment());
mList.add(new TvFragment());
mList.add(new LoLFragment());
mList.add(new KingGloryFragment());
mList.add(new PeaceFragment());
viewPager.setAdapter(new MyFragmentStateAdapter(getSupportFragmentManager(), FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, mList));
tabLayout.setupWithViewPager(viewPager, false);//自动绑定viewpager,实现联动效果
}
private void populateTabsData() {
String[] arr = new String[]{
"动漫", "电视剧", "LOL", "王者荣耀", "和平精英",
};
for (String s : arr) {
//注意这里的对象使用(不要直接new TableLayout,使用的对象要为上文findviewbyid那个)否则回报空指针
tabLayout.addTab(tabLayout.newTab().setText(s));
}
}
private void initView() {
tabLayout = findViewById(R.id.my_tab_layout);
viewPager = findViewById(R.id.vp_pager);
}
}
/**
* Created by sunnyDay on 2019/12/16 19:46
*/
public class MyFragmentStateAdapter extends FragmentStatePagerAdapter {
private List<Fragment> mList;
public MyFragmentStateAdapter(@NonNull FragmentManager fm, int behavior, List<Fragment> mlist) {
super(fm, behavior);
this.mList = mlist;
}
@NonNull
@Override
public Fragment getItem(int position) {
return mList.get(position);
}
@Override
public int getCount() {
return mList == null ? 0 : mList.size();
}
/**
* view pager 和 tab layout 联动时这个方法必须重写 否则 tab 标题会被全部置空
* */
// @Nullable
// @Override
// public CharSequence getPageTitle(int position) {
// String[] arr = new String[]{
// "动漫", "电视剧", "LOL", "王者荣耀", "和平精英",
// };
// return arr[position];
// }
}
behavior的值:
-
BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT:指示仅当前片段处于该Lifecycle.State.RESUMED 状态。
-
BEHAVIOR_SET_USER_VISIBLE_HINT:已经不建议使用,建议使用BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
ViewPager与TabLayout联动的坑
tabLayout.setupWithViewPager(viewPager, false);
如上这句代码后我们惊奇的发现tablayout的文字全部都是空了。这时我们分析下源码如下:
private void setupWithViewPager( @Nullable final ViewPager viewPager, boolean autoRefresh, boolean implicitSetup) {
...
...
... 忽略n行
if (viewPager != null) {
this.viewPager = viewPager;
if (pageChangeListener == null) {
pageChangeListener = new TabLayoutOnPageChangeListener(this);
}
pageChangeListener.reset();
viewPager.addOnPageChangeListener(pageChangeListener);
currentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager);
addOnTabSelectedListener(currentVpSelectedListener);
final PagerAdapter adapter = viewPager.getAdapter();
if (adapter != null) {
// 重要方法
setPagerAdapter(adapter, autoRefresh);
}
if (adapterChangeListener == null) {
adapterChangeListener = new AdapterChangeListener();
}
adapterChangeListener.setAutoRefresh(autoRefresh);
viewPager.addOnAdapterChangeListener(adapterChangeListener);
setScrollPosition(viewPager.getCurrentItem(), 0f, true);
} else {
this.viewPager = null;
// 重要方法
setPagerAdapter(null, false);
}
这个方法内部有个重要方法setPagerAdapter,这时我们进入setPagerAdapter方法看看。
void setPagerAdapter(@Nullable final PagerAdapter adapter, final boolean addObserver) {
... 省略n行
populateFromPagerAdapter(); // 重要方法
}
这里有个populateFromPagerAdapter方法:
看到这里就明白为啥联动时不重写adapter的getPageTitle时,tabs消失的坑。
The end
参考