ViewPager使用详解1
最后更新于:2022-04-01 11:18:20
## 一、简介
**1.特点:**可以左右滑动的控件,需要PagerAdapter配合使用,由v4包提供
类全名: android.support.v4.view.ViewPager
**2.作用**:ViewPager作用主要是能使界面左右滑动。比如最常用的使用是做一个引导界面;多张图片的预览或自动变换的图片展示(如淘宝首页面上的广告);viewpager还可以结合fragment作为主界面框架(如微信主界面)。。。
**3.viewpager中的几个重要的方法**
1)setAdapter()设置ViewPager的适配器。
2)setOnPageChangeListener()设置页面改变事件监听器
3)setCurrentItem(int position) 显示第几页
4)setCurrentItem(int position,boolean smoothScroll) 显示第几页,是否执行滚动动画
5)setOffscreenPageLimit(int limit) 设置脱离屏幕的页面限制--最多同时加载的页面数,limit默认是1
-这个方法解释一下:默认情况下,viewpager在显示页面时,会加载当前显示页和它左右的页面,这也是为什么移动页面时可以显示下一页面一部分。如果想设置其他数,viewpager会加载对应值的页面,比如设置2,则当前显示页和它左两页和右两页都会加载。
## 二、相关类介绍
**1.viewpager相关的几个重要的类**
**1.1OnPageChangeListener:**
–onPageScrollStateChanged(int state) 页面滚动发生或停止时
–onPageScrolled(int position, float offset, int offsetPixes) 滚动时
–onPageSelected(int position) 页面位置确定时
**1.2 PagerAdapter**
–作用:主要配合ViewPager显示相关的View
–用法:
1)创建类,并继承PagerAdatper
2) 必须实现的方法
–getCount() 获取View的数量
–instantiateItem(ViewGroup, int poistion) 实例化指定位置的View对象
–destroyItem(ViewGroup, int poistion, Object) 删除指定位置的View
–isViewFromObject(View, Object) 判断当前的View是否为Object
**1.3FragmentPagerAdaper**
–作用:与PagerAdapter的功能相同,不过显示的View改为Fragment
–FragmentStatePagerAdapter: 如果需要处理有很多页,并且数据动态性较大、占用内存较多的情况,应该使用FragmentStatePagerAdapter 保存当前界面,以及下一个界面和上一个界面(如果有),最多保存3个,其他会被销毁掉。
–FragmentPagerAdaper和FragmentStatePagerAdapter的区别:
FragmentPagerAdaper销毁的只是视图,数据没有销毁,FragmentStatePagerAdapter则是全部销毁。拿fragment生命周期来说,使用FragmentPagerAdaper,当滑动页面时,被销毁的fragment会执行到onDestyoyView()但没有执行onDestroy()。而使用FragmentStatePagerAdapter时,会执行onDestroy()方法。后面实例会看到。
## 三、使用
**1.使用步骤:**
1) 在布局文件中使用标签
~~~
<android.support.v4.view.ViewPager/>
~~~
2) 代码中增加显示的页面
3) 在Activity里实例化ViewPager组件,并设置它的Adapter
需要注意的是:根据不同的场景,选择不同的适配器父类,不结合fragment使用时使用继承PagerAdapter的适配器,结合Fragment使用时,根据需求,可以选择父类为FragmentPagerAdapter和FragmentStatePagerAdapter,这两个的区别在后面前面已经讲过了。
**2.案例一:用viewpager做一个欢迎界面**
先看效果图:
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_5717152a6d732.jpg "")
**1) 布局文件**
~~~
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${relativePackage}.${activityClass}" >
<!-- 其显示的页面需要通过PagerAdapter适配器增加或移除 -->
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- 导航布局 ,小点点 -->
<LinearLayout
android:id="@+id/ll_dots"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginBottom="10dp"
android:gravity="center"
android:orientation="horizontal"
android:padding="10dp" >
</LinearLayout>
</FrameLayout>
~~~
**2) 然后看看几个主要的方法体:**
**第一个是自定义的适配器**,各种方法都有注释。
~~~
// 声明PageAdapter子类,用于管理viewpager中显示的控件
class WelComePageAdapter extends PagerAdapter {
@Override
public int getCount() {
// TODO 返回页面的数量
return views.size();
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
// TODO 获取指定位置的View(UI),并增加到ViewPager中,同时作为当前页面的数据返回
Log.i("--", "instantiateItem--" + position);
View view = views.get(position);
container.addView(view);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
// TODO 当前位置与VIewPager中显示页面的位置的间隔超出一页面,需要将当前位置的页面移除
Log.i("--", "destroyItem--" + position);
container.removeView(views.get(position));
}
@Override
public boolean isViewFromObject(View view, Object obj) {
// TODO 判断当前显示的页面的UI和数据对象是否一致
return view == obj;
}
}
~~~
**第二个是viewpager滑动监听事件**,这里自定义一个类看着清楚点,继承OnPageChangeListener,然后事项相应的方法,特别注意onPageScrolled和onPageSelected这两个方法,onPageScrolled在滑动时会一直回调,根据回调的三个参数,在这里可以进行相应的操作,比如微信那个,当你滑动时,相邻两个标签会有颜色的渐变,就在这里处理的。onPageSelected方法在滑动一个页面停止后回调。
~~~
class WelcomPageChangeListner implements OnPageChangeListener {
@Override
public void onPageScrollStateChanged(int state) {
Log.i("--", "onPageScrollStateChanged" + state);
// TODO 页面滚动状态发生变化事件:开始滚动、停止滚动、正在设置页面
// ViewPager.SCROLL_STATE_DRAGGING 开始滚动
// ViewPager.SCROLL_STATE_IDLE 停止滚动
// ViewPager.SCROLL_STATE_SETTLING 正在设置页面,即将要停止,并且设置当前显示的页面
// setDot(position);
// switch (state) {
// case ViewPager.SCROLL_STATE_DRAGGING:
// Log.i("--", "-SCROLL_STATE_DRAGGING-");
// break;
// case ViewPager.SCROLL_STATE_IDLE:
// Log.i("--", "-SCROLL_STATE_IDLE-");
// break;
// case ViewPager.SCROLL_STATE_SETTLING:
// Log.i("--", "-SCROLL_STATE_SETTLING-");
// break;
// }
}
~~~
~~~
/**
* 第一个参数:滚动页面开始的位置 <br>
* 第二个参数:两个页面之间滚动的偏移量,范围:0-1<br>
* 第三个页面:两个页面之间的滚动的像素偏移量<br>
*/
@Override
public void onPageScrolled(int position, float offset, int offsetPixwls) {
// TODO 从当前页面位置开始滚动事件
// Log.i("--", "-onPageScrolled-" + position + ",[" + offset + "],"
// + "[" + offsetPixwls + "]");
}
@Override
public void onPageSelected(int position) {
// TODO 指定位置的页面被选择
setDot(position);
}
}
~~~
**第三个是动态改变导航栏小点点的背景图。**
~~~
/**
* 选择指定位置的导航图片为选择图片,之前选择的导航图片重置为未选择图片
*
* @param position
*/
private void setDot(int position) {
ImageView imageView = null;
// 遍历导航布局张所有的子控件,判断子控件的位置是否未选择位置,若是则设置为选择图片
for (int i = 0; i < LlDot.getChildCount(); i++) {
imageView = (ImageView) LlDot.getChildAt(i);// 获取布局中指定位置的子控件
if (i == position) {
imageView.setImageResource(R.drawable.page_now);
} else {
imageView.setImageResource(R.drawable.page);
}
}
}
~~~
其他还有小点点的点击事件,具体看源代码。
**3.案例二:viewpager和view结合做一个主界面**
效果图:
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-23_571af4d4d8d7f.jpg "")
1)布局:最上面的导航栏是一个HorizonalScrollView,里面有一个LinearLayout(因为HorizonalScrollView里只能有一个子视图),然后在LinearLayout定义你想要的导航模块,TextView即可。
~~~
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${relativePackage}.${activityClass}" >
<HorizontalScrollView
android:id="@+id/hScrollView"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginBottom="2dp"
android:scrollbars="none" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical" >
<!-- 顶部模块的布局 -->
<LinearLayout
android:id="@+id/navLayout"
android:layout_width="wrap_content"
android:layout_height="45dp"
android:orientation="horizontal" >
<TextView
android:id="@+id/tv_model1"
android:layout_width="150dp"
android:layout_height="match_parent"
android:gravity="center"
android:text="第一"
android:textColor="#000"
android:textSize="20sp" />
<TextView
android:id="@+id/tv_model2"
android:layout_width="150dp"
android:layout_height="match_parent"
android:gravity="center"
android:text="第二"
android:textColor="#000"
android:textSize="20sp" />
<TextView
android:id="@+id/tv_model3"
android:layout_width="150dp"
android:layout_height="match_parent"
android:gravity="center"
android:text="第三"
android:textColor="#000"
android:textSize="20sp" />
<TextView
android:id="@+id/tv_model4"
android:layout_width="150dp"
android:layout_height="match_parent"
android:gravity="center"
android:text="第四"
android:textColor="#000"
android:textSize="20sp" />
<TextView
android:id="@+id/tv_model5"
android:layout_width="150dp"
android:layout_height="match_parent"
android:gravity="center"
android:text="第五"
android:textColor="#000"
android:textSize="20sp" />
</LinearLayout>
<!-- 指示器控件 -->
<View
android:id="@+id/view_nav"
android:layout_width="150dp"
android:layout_height="4dp"
android:background="#0cf" />
<!-- 基准线 -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#0cf" />
</LinearLayout>
</HorizontalScrollView>
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_below="@id/hScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
~~~
**还需要几个fragment页,这里只展示一个,其他类似:**
~~~
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:background="#80f0">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="第一个页面"
android:textSize="30sp"
/>
</RelativeLayout>
~~~
**2)代码中需要做的事情有:**
2).1 指示器的移动,也就是模块下那个粗线条的移动,viewpager本身就是可滑动的控件,这里需要指示器随着手指的滑动进行滑动
2).2选择导航模块的位置,将水平滚动到当前模块位置的中心点,
2).3模块点击事件,点击模块,显示相应的page
那么来一步步实现上面的工作:
2.1指示器的移动
要求是指示器随着手指的滑动,起始也就是动态的改变指示器的leftMargin
~~~
/**
* 指示器移动
* @param position
* @param offset
*/
private void navIndicateMove(int position, float offset){
int leftMargin = (int) (indicateParams.width * (position + offset));
indicateParams.leftMargin = leftMargin;
navIndicate.setLayoutParams(indicateParams);
}
~~~
2.2选择导航模块的位置,将水平滚动到当前模块位置的中心点
因为整个导航模块都在一个HorizonalScrollView中,HorizonalScrollView中有两个方法可以指定的移动位置,一个是**hScrollView.scrollTo(x, y);//非平滑移动**
**hScrollView.smoothScrollTo(x, y);//平滑移动**
所以根据选择的模块得到移动值即可。
~~~
/**
* 选择导航模块的位置,将水平滚动到当前模块位置的中心点
*
* @param position
*/
private void selectNav(int position) {
TextView modelTv = (TextView) navLayout.getChildAt(position);
int left = modelTv.getLeft();// 获取当前控件的左边位置
// 怎么放到中间?
// int scWidth = (getResources().getDisplayMetrics().widthPixels / 2);
int offset = left
- (getResources().getDisplayMetrics().widthPixels / 2)
+ modelTv.getWidth() / 2;
hScrollView.smoothScrollTo(offset, 0);// 水平滚动到指定位置
//设置被选中的模块
for(int i = 0; i < navLayout.getChildCount(); i++){
modelTv = (TextView) navLayout.getChildAt(i);
if(i == position){
modelTv.setTextColor(Color.argb(100, 255, 0, 0));
}else{
modelTv.setTextColor(Color.argb(255, 0, 0, 0));
}
}
}
~~~
**3.模块点击事件,点击模块,显示相应的page**
给每个模块添加点击事件,然后点击一个模块,只用调用viewPager的
viewPager.setCurrentItem(item);//非平滑滑动
viewPager.setCurrentItem(item, smoothScroll);//平滑滑动
~~~
/**
* 模块点击事件
*/
private void modelTvClickEvent() {
for(int i = 0; i < navLayout.getChildCount(); i++){
final TextView tv = (TextView) navLayout.getChildAt(i);
tv.setTag(i);
tv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = (Integer)tv.getTag();
// selectNav(position);
// navIndicateMove(position, 0);
viewPager.setCurrentItem(position, true);
}
});
}
}
~~~
**这样,viewpager结合导航栏都可以同步滑动了。**
**4.案例三:viewpager和fragment结合做一个主界面**
效果图:
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-23_571af4d52e586.jpg "")
结合fragment使用和结合view使用很相似,只是继承的适配器不同。
上面继承PagerAdapter,这里继承FragmentPagerAdapter或者FragmentStatePagerAdapter。
1)定义一个ListFragment
2)自定义适配器
3)初始化数据源
ListFragment的使用前面有一篇讲过,这里就不附代码了。
来看看自定义适配器:很简单就完事
~~~
class InfoFragmentAdapter extends FragmentStatePagerAdapter{
public InfoFragmentAdapter(FragmentManager fm){
super(fm);
}
@Override
public int getCount() {
return fragments.size();
}
@Override
public Fragment getItem(int position) {
//返回指定位置的碎片
return fragments.get(position);
}
}
~~~
**最后可以借这个案例看一看FragmentPagerAdapter和FragmentStatePagerAdapter区别:**
ViewPager:默认创建自己和左右两页,当某个页面与显示的页面间放大点隔大于1,则销毁UI界面(注意,只是UI,里面的数据则不会销毁)
FragmentPagerAdapter:管理fragment时销毁的是UI界面,(fragment生命周期只会执行到onDestroyView)
FragmentStatePagerAdapter:管理fragment时,完全销毁(fragment生命周期会执行onDestroy)
只用把自定义适配器类的父类改一改,然后看fragment各生命周期 打印的日志即可看到区别,这里不做演示了。
现在,结合案例二和案例三就可以做一个现在很流行的主界面了。
[ 源码下载](http://download.csdn.net/detail/u011102153/9120819)