一、簡介
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做一個歡迎界面
先看效果圖:
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結合做一個主界面
效果圖:
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結合做一個主界面
效果圖:
結合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各生命週期
打印的日誌即可看到區別,這裏不做演示了。
現在,結合案例二和案例三就可以做一個現在很流行的主界面了。