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>新闻热点
疑难解答