
ViewPager是一個常用的android組件,不過通常我們使用ViewPager的時候不能實現左右無限循環滑動,在滑到邊界的時候會看到一個不能翻頁的動畫,可能影響用戶體驗。此外,某些區域性的ViewPager(例如展示廣告或者公告之類的ViewPager),可能需要自動輪播的效果,即用戶在不用滑動的情況下就能夠看到其他頁面的信息。
循環滑動效果的實現:PagerAdapter 我們知道ViewPager自帶的滑動效果非常出色,因此我們基本不需要處理這個滑動,只處理內容的顯示。而內容的顯示是由Adapter控制的,因此這里重點就是這個Adapter了。為簡單起見,本例的每個View直接是一張圖片。下面是Adapter的代碼:
PRivate class ImageAdapter extends PagerAdapter{ private ArrayList<ImageView> viewlist; public ImageAdapter(ArrayList<ImageView> viewlist) { this.viewlist = viewlist; } @Override public int getCount() { //設置成最大,使用戶看不到邊界 return Integer.MAX_VALUE; } @Override public boolean isViewFromObject(View arg0, Object arg1) { return arg0==arg1; } @Override public void destroyItem(ViewGroup container, int position, Object object) { //Warning:不要在這里調用removeView } @Override public Object instantiateItem(ViewGroup container, int position) { //對ViewPager頁號求模取出View列表中要顯示的項 position %= viewlist.size(); if (position<0){ position = viewlist.size()+position; } ImageView view = viewlist.get(position); //如果View已經在之前添加到了一個父組件,則必須先remove,否則會拋出IllegalStateException。 ViewParent vp =view.getParent(); if (vp!=null){ ViewGroup parent = (ViewGroup)vp; parent.removeView(view); } container.addView(view); //add listeners here if necessary return view; } }這里有幾個地方需要注意:
getCount() 方法的返回值:這個值直接關系到ViewPager的“邊界”,因此當我們把它設置為Integer.MAX_VALUE之后,用戶基本就看不到這個邊界了(估計滑到這里的時候電池已經掛了吧o_O)。當然,通常情況下設置為100倍實際內容個數也是可以的,之前看的某個實現就是這么干的。
instantiateItem() 方法position的處理:由于我們設置了count為 Integer.MAX_VALUE,因此這個position的取值范圍很大很大,但我們實際要顯示的內容肯定沒這么多(往往只有幾項),所以這里肯定會有求模操作。但是,簡單的求模會出現問題:考慮用戶向左滑的情形,則position可能會出現負值。所以我們需要對負值再處理一次,使其落在正確的區間內。
destroyItem() 方法:由于我們在instantiateItem()方法中已經處理了remove的邏輯,因此這里并不需要處理。實際上,實驗表明這里如果加上了remove的調用,則會出現ViewPager的內容為空的情況。
輪播效果的實現:使用Handler進行更新
這里我定義了一個Handler來處理ViewPager的輪播。所謂的“輪播”效果實現起來是這樣的:每隔一定時間(這里是3秒)切換一次顯示的頁面。通過控制各頁面以一定順序循環播放,就達到了輪播的效果。為此,我們可以使用Handler的sendEmptyMessageDelayed()方法來實現定時更新,并 注意用戶也可能會對帶有輪播效果的ViewPager手動進行滑動操作,因此我認為用戶這時候是希望查看指定頁面的,這時候應該取消輪播。下面是這個Handler的實現:

一、ViewPager填充圖片 1.1 布局中申明 由于是顯示廣告條,所以高度要固定住
<android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="120dp"/>1.2 代碼中設置頁面數據 準備顯示圖片控件的集合
// 準備顯示的圖片集合 mList = new ArrayList<>(); for (int i = 0; i < mImages.length; i++) { ImageView imageView = new ImageView(this); // 將圖片設置到ImageView控件上 imageView.setImageResource(mImages[i]); // 將ImageView控件添加到集合 mList.add(imageView); }自定義類書寫適配器
@Override public Object instantiateItem(ViewGroup container, int position) { // return super.instantiateItem(container, position); // 將圖片控件添加到容器 container.addView(mList.get(position)); // 返回 return mList.get(position); }二、底部小圓點顯示邏輯 原理分析:底部的小圓點時浮動在ViewPager上面的的,所以應該是一個RelativeLayout布局。 ViewPager頁面切換時小圓點的顏色不一樣,所以需要對小圓點做選擇器,并且對ViewPager進行監聽。
2.1 布局申明
需要用一個RelativeLayout將ViewPager和包裹圓點的LinearLayout包裹起來
<RelativeLayout android:layout_width="match_parent" android:layout_height="120dp"> <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="120dp"/> <LinearLayout android:id="@+id/pointgroup" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="3dp" android:orientation="horizontal"> </LinearLayout> </RelativeLayout>2.2 制作小圓點顏色選擇器
選擇器的選中狀態應該設置為selected,因為對ViewPager監聽時可以設置selected的屬性
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/shape_point_normal" android:state_selected="false"/> <item android:drawable="@drawable/shape_point_selected" android:state_selected="true"/> </selector> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> <solid android:color="#66000000"/> </shape <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> <solid android:color="#FFFFFF"/> </shape>2.3 將小圓點添加到LinearLayout容器
小圓點其實就是一個ImageView,所以在做出ViewPager的頁面圖片時,一起把小圓點也做了 初始化ImageView添加到LinearLayout之前,需要設置小圓點的布局參數,包括位置和大小
LinearLayout pointGroup = (LinearLayout) findViewById(R.id.pointgroup); for (int i = 0; i < mImages.length; i++) { // 制作底部小圓點 ImageView pointImage = new ImageView(this); pointImage.setImageResource(R.drawable.shape_point_selector); // 設置小圓點的布局參數 int PointSize = getResources().getDimensionPixelSize(R.dimen.point_size); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(PointSize, PointSize); if (i > 0) { params.leftMargin = getResources().getDimensionPixelSize(R.dimen.point_margin); pointImage.setSelected(false); } else { pointImage.setSelected(true); } pointImage.setLayoutParams(params); // 添加到容器里 pointGroup.addView(pointImage); }三、小圓點隨著ViewPager切換移動
其實就是對ViewPager設置滑動監聽,當滑動到每一頁時就設置小圓點為選中狀態,這樣小圓點就顯示白色,其他頁面就設置為未選中狀態顯示灰色。
// 對ViewPager設置滑動監聽 viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } int lastPosition; @Override public void onPageSelected(int position) { // 頁面被選中 // 設置當前頁面選中 pointGroup.getChildAt(position).setSelected(true); // 設置前一頁不選中 pointGroup.getChildAt(lastPosition).setSelected(false); // 替換位置 lastPosition = position; } @Override public void onPageScrollStateChanged(int state) { } });經過前面的三步設置后就能顯示一個簡單的廣告條了,這里再對其添加一個滑動到最后一頁后再滑還能滑動到首頁的功能。 四、無限滑動的ViewPager 實現原理: ViewPager之所以滑動到左右能顯示頁面,其實是因為左右都存在即將要顯示的頁面。當左右有很多頁面時我們就能一直滑動,沒有時就不能滑動。所以原理就是讓ViewPager的左右都有很多的頁面。 4.1 修改getCount方法
ViewPager能顯示多少個頁面全由getCount方法說了算,所以我們首先要改造它。
@Override public int getCount() { // 返回整數的最大值 return Integer.MAX_VALUE; }4.2 修改instantiateItem方法
因為position變了,所以顯示的位置也變了,這里需要進行取%運算,來還原position。
// 修改position position = position % mList.size();4.3 修改ViewPager監聽器里的onPageSelected
用到了position就要修改。
position = position % mList.size();修改之后,ViewPager當前頁的右邊就有了無數的頁面,但是因為%了mList.size(),就只會顯示mList.size()的大小,這樣就實現了無限滑動輪播 五、無限自動輪播的廣告圖 實現原理:在前面四步的基礎上,在代碼里添加一個Handler,不斷的給自己發消息就好了。
mHandler.postDelayed(new Runnable() { @Override public void run() { int currentPosition = viewPager.getCurrentItem(); if(currentPosition == viewPager.getAdapter().getCount() - 1){ // 最后一頁 viewPager.setCurrentItem(0); }else{ viewPager.setCurrentItem(currentPosition + 1); } // 一直給自己發消息 mHandler.postDelayed(this,5000); } },5000);
ViewPager的界面點和文本的初始化操作
private void showMsg(NewBean newBean) { //1.ViewPager的數據 if (newBean.data.topnews.size() > 0) { ... //1.3.將viewpager和點的indicator關聯 mIndicator.setViewPager(mViewPager); mIndicator.setSnap(true);//快照,使用快照的方式顯示點 //1.4.設置默認顯示第一張圖片,第一個文本,第一個點 mTitle.setText(titles.get(0)); mIndicator.onPageSelected(0); mViewPager.setCurrentItem(0);//設置viewpager當前顯示的界面,item:條目的索引 } //2.ListView的數據}填充listview的數據將ViewPager的布局作為listView的頭條目展示
1.將ViewPager的布局作為listview的頭條目展示 private void showMsg(NewBean newBean) { //1.ViewPager的數據 if (newBean.data.topnews.size() > 0) { .... //1.5.將ViewPager所在的布局,添加到listView中 //獲取listview的頭條目的個數 if (mListView.getHeaderViewsCount()<1) { mListView.addHeaderView(mViewPagerView);//給listview添加頭條目 } } //2.ListView的數據 }2.填充listview數據 private void showMsg(NewBean newBean) { ..... //2.ListView的數據 if (newBean.data.news.size() > 0) { mNews = newBean.data.news; //設置listview的adapter展示數據 if (listViewAdapter == null) { listViewAdapter = new MyListViewAdapter(); mListView.setAdapter(listViewAdapter); }else{ listViewAdapter.notifyDataSetChanged(); } } }ViewPager自動滑動操作
核心理念:每個一段時間,viewpager切換到下一個界面1.通過handler設置viewpager的自動滑動操作 //因為showMsg方法實在processjson方法中調用的,而processJson是在緩存和獲取最新數據的時候都會調用,最終會造成發送兩個延遲消息,但是只需要一個延遲消息就可以了 if (handler == null) { handler = new Handler(){ public void handleMessage(android.os.Message msg) { //viewpager切換下一個界面的操作 //首先需要知道當前顯示的界面 int currentItem = mViewPager.getCurrentItem();//獲取當前顯示界面的索引 //然后計算下一個界面的索引 //判斷是否切換到最后一個界面,如果是最后一個界面了,切換回第一個界面 if (currentItem == imagerUrls.size()-1) { currentItem=0; }else{ currentItem++; } //設置viewpager顯示下一個界面 mViewPager.setCurrentItem(currentItem); //切換一次完成,還要緊接著切換第二次 handler.sendEmptyMessageDelayed(0, 3000); }; }; handler.sendEmptyMessageDelayed(0, 3000);//只有執行此方法,才會發送延遲消息,不執行就不發送 }2.設置viewpager的界面切換監聽,實現切換界面顯示界面對應的文本 //監聽viewpager的界面切換,實現切換一個界面顯示一個界面對應的文本 mViewPager.addOnPageChangeListener(new OnPageChangeListener() { @Override public void onPageSelected(int position) { mTitle.setText(titles.get(position)); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { // TODO Auto-generated method stub } @Override public void onPageScrollStateChanged(int state) { // TODO Auto-generated method stub } });viewpager的手動滑動
當滑動到小viewpager的最后一個界面的時候,外面的viewpager要將小的viewpager的觸摸事件攔截,當滑動小的viewpager的不是最后一個界面的時候,外面的viewpager不攔截小viewpager的觸摸事件讓小viewpager進行滑動操作創建自定義Viewpager進行操作//事件分發的@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { //請求父控件不要攔截事件,true:不攔截,false:攔截 //getParent().requestDisallowInterceptTouchEvent(disallowIntercept); //1.需要判斷是左右滑動還是上下滑動,因為只有左右才是viewpager手動滑動的操作 switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //getParent().requestDisallowInterceptTouchEvent(false); //獲取按下的x和y的坐標 downX = (int) ev.getX(); downY = (int) ev.getY(); break; case MotionEvent.ACTION_MOVE: //獲取移動的x和y的坐標 int moveX = (int) ev.getX(); int moveY = (int) ev.getY(); //判斷是上下還是左右滑動 if (Math.abs(moveX-downX) > Math.abs(moveY-downY)) { //左右 //從右往左,如果是最后一個條目,父控件攔截事件,實現切換界面的操作,如果不是最后一個條目,切換下一張圖片 //getAdapter() : 獲取ViewPager設置的adapter if (downX - moveX > 0 && getCurrentItem() == getAdapter().getCount()-1) { getParent().requestDisallowInterceptTouchEvent(false); }else if(downX - moveX > 0 && getCurrentItem() < getAdapter().getCount()-1){ getParent().requestDisallowInterceptTouchEvent(true); } //從左往右,如果是第一個條目,父控件攔截事件,打開側拉菜單,如果不是第一個條目,切換到上一張圖片 else if(downX - moveX < 0 && getCurrentItem() == 0){ getParent().requestDisallowInterceptTouchEvent(false); }else if(downX - moveX < 0 && getCurrentItem() > 0){ getParent().requestDisallowInterceptTouchEvent(true); } }else{ //上下 getParent().requestDisallowInterceptTouchEvent(false); } break; case MotionEvent.ACTION_UP: break; } return super.dispatchTouchEvent(ev);}ViewPager和View的事件響應規則
如果是緩慢的移動很短的距離,viewpager和view的事件都會執行如果是快速滑動很長的距離,view的事件會執行cancel事件,結束view的觸摸操作,只去viewpager的事件具體操作 //設置view的觸摸事件事件,實現按下viewpager停止自動滑動,抬起,viewpager重新進行自動滑動操作 rootView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //按下viewpager停止滑動 handler.removeCallbacksAndMessages(null);//取消handler發送延遲消息,如果是null,全部handler都會被取消發送消息 break; case MotionEvent.ACTION_UP: //抬起viewpager重新滑動 handler.sendEmptyMessageDelayed(0, 3000); break; case MotionEvent.ACTION_CANCEL: //view的事件取消執行的操作 handler.sendEmptyMessageDelayed(0, 3000); break; } //如果想要事件執行,返回true,返回事件不執行 return true; } });自定義RoolViewPager
public class RoolViewPager extends ViewPager { private int downX; private int downY; public RoolViewPager(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } public RoolViewPager(Context context) { super(context); // TODO Auto-generated constructor stub } //事件分發的 @Override public boolean dispatchTouchEvent(MotionEvent ev) { //請求父控件不要攔截事件,true:不攔截,false:攔截 //getParent().requestDisallowInterceptTouchEvent(disallowIntercept); //1.需要判斷是左右滑動還是上下滑動,因為只有左右才是viewpager手動滑動的操作 switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //getParent().requestDisallowInterceptTouchEvent(false); //獲取按下的x和y的坐標 downX = (int) ev.getX(); downY = (int) ev.getY(); break; case MotionEvent.ACTION_MOVE: //獲取移動的x和y的坐標 int moveX = (int) ev.getX(); int moveY = (int) ev.getY(); //判斷是上下還是左右滑動 if (Math.abs(moveX-downX) > Math.abs(moveY-downY)) { //左右 //從右往左,如果是最后一個條目,父控件攔截事件,實現切換界面的操作,如果不是最后一個條目,切換下一張圖片 //getAdapter() : 獲取ViewPager設置的adapter if (downX - moveX > 0 && getCurrentItem() == getAdapter().getCount()-1) { getParent().requestDisallowInterceptTouchEvent(false); }else if(downX - moveX > 0 && getCurrentItem() < getAdapter().getCount()-1){ getParent().requestDisallowInterceptTouchEvent(true); } //從左往右,如果是第一個條目,父控件攔截事件,打開側拉菜單,如果不是第一個條目,切換到上一張圖片 else if(downX - moveX < 0 && getCurrentItem() == 0){ getParent().requestDisallowInterceptTouchEvent(false); }else if(downX - moveX < 0 && getCurrentItem() > 0){ getParent().requestDisallowInterceptTouchEvent(true); } }else{ //上下 getParent().requestDisallowInterceptTouchEvent(false); } break; case MotionEvent.ACTION_UP: break; } return super.dispatchTouchEvent(ev); }}xml使用
布局文件中使用<com.itheima.zhbj97.ui.RoolViewPager android:id="@+id/menunewscenteritem_vp_viewpager" android:layout_width="match_parent" android:layout_height="185dp" ></com.itheima.zhbj97.ui.RoolViewPager>| 
 
 | 
新聞熱點
疑難解答