ViewPager 显示图片,轮播

一:动态添加View出现的问题

最近遇到一个很让人头疼的问题,使用viewpager动态添加页面或者删除页面时出现了问题(java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first),在stackoverflow上找到了解决办法。(http://stackoverflow.com/questions/22936886/java-lang-illegalstateexception-while-using-viewpager-in-android

原文是:

the problem is that in your adapters method instantiateItem
you call container.addView(v);

but every View can have only one parent,
so it can be added only one time to a container via addView(...).

When you open the popup the first time, everything works, because v
doesn't have a parent that time. But when you open your popupwinow the second time,
it adds the view again to the container. That cerates the error.

Try to destroy the view if you close the popup view or remove all children views from it with
container.removeAllViews()

解决办法是在instantiateItem中使用如下方式:

ViewGroup parent = (ViewGroup) v.getParent();

 if (parent != null) {

parent.removeAllViews();

 } 

container.addView(v);

中间很多次尝试已经接近答案,但是习惯性的去把v.getParent()强制转化为view,view没有removeView()方法,以至于放弃了这种方法,以后要多思考。

二:ViewPager 实现 “轮播”图片

ViewPager是一个常用的android组件,不过通常我们使用ViewPager的时候不能实现左右无限循环滑动,在滑到边界的时候会看到一个不能翻页的动画,可能影响用户体验。此外,某些区域性的ViewPager(例如展示广告或者公告之类的ViewPager),可能需要自动轮播的效果,即用户在不用滑动的情况下就能够看到其他页面的信息。

为此我查阅了网络上现有的一些关于实现这样效果的例子,但都不是很满意,经过反复实验,在这里总结并分享给大家,希望能有所帮助。


循环滑动效果的实现:PagerAdapter

我们知道ViewPager自带的滑动效果非常出色,因此我们基本不需要处理这个滑动,只处理内容的显示。而内容的显示是由Adapter控制的,因此这里重点就是这个Adapter了。为简单起见,本例的每个View直接是一张图片。下面是Adapter的代码:

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. private class ImageAdapter extends PagerAdapter{  
  2.            
  3.         private ArrayList<ImageView> viewlist;  
  4.    
  5.         public ImageAdapter(ArrayList<ImageView> viewlist) {  
  6.             this.viewlist = viewlist;  
  7.         }  
  8.    
  9.         @Override  
  10.         public int getCount() {  
  11.             //设置成最大,使用户看不到边界  
  12.             return Integer.MAX_VALUE;  
  13.         }  
  14.    
  15.         @Override  
  16.         public boolean isViewFromObject(View arg0, Object arg1) {  
  17.             return arg0==arg1;  
  18.         }  
  19.          @Override    
  20.          public void destroyItem(ViewGroup container, int position,    
  21.                  Object object) {    
  22.              //Warning:不要在这里调用removeView  
  23.          }    
  24.          @Override    
  25.          public Object instantiateItem(ViewGroup container, int position) {  
  26.              //对ViewPager页号求模取出View列表中要显示的项  
  27.              position %= viewlist.size();  
  28.              if (position<0){  
  29.                  position = viewlist.size()+position;  
  30.              }  
  31.              ImageView view = viewlist.get(position);  
  32.              //如果View已经在之前添加到了一个父组件,则必须先remove,否则会抛出IllegalStateException。  
  33.              ViewParent vp =view.getParent();  
  34.              if (vp!=null){  
  35.                  ViewGroup parent = (ViewGroup)vp;  
  36.                  parent.removeView(view);  
  37.              }  
  38.              container.addView(view);    
  39.              //add listeners here if necessary  
  40.              return view;    
  41.          }    
  42.     }  


这里有几个地方需要注意:

  • getCount() 方法的返回值:这个值直接关系到ViewPager的“边界”,因此当我们把它设置为Integer.MAX_VALUE之后,用户基本就看不到这个边界了(估计滑到这里的时候电池已经挂了吧o_O)。当然,通常情况下设置为100倍实际内容个数也是可以的,之前看的某个实现就是这么干的。

  • instantiateItem() 方法position的处理:由于我们设置了count为 Integer.MAX_VALUE,因此这个position的取值范围很大很大,但我们实际要显示的内容肯定没这么多(往往只有几项),所以这里肯定会有求模操作。但是,简单的求模会出现问题:考虑用户向左滑的情形,则position可能会出现负值。所以我们需要对负值再处理一次,使其落在正确的区间内。

  • instantiateItem() 方法父组件的处理:通常我们会直接addView,但这里如果直接这样写,则会抛出IllegalStateException假设一共有三个view,则当用户滑到第四个的时候就会触发这个异常,原因是我们试图把一个有父组件的View添加到另一个组件。但是,如果直接写成下面这样:

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. (ViewGroup)view.getParent().removeView(view);  


则又会因为一开始的时候组件并没有父组件而抛出NullPointerException。因此,需要进行一次判断。也就是上面的代码。

  • destroyItem() 方法:由于我们在instantiateItem()方法中已经处理了remove的逻辑,因此这里并不需要处理。实际上,实验表明这里如果加上了remove的调用,则会出现ViewPager的内容为空的情况。


轮播效果的实现:使用Handler进行更新

这里我定义了一个Handler来处理ViewPager的轮播。所谓的“轮播”效果实现起来是这样的:每隔一定时间(这里是3秒)切换一次显示的页面。通过控制各页面以一定顺序循环播放,就达到了轮播的效果。为此,我们可以使用Handler的sendEmptyMessageDelayed()方法来实现定时更新,并

注意用户也可能会对带有轮播效果的ViewPager手动进行滑动操作,因此我认为用户这时候是希望查看指定页面的,这时候应该取消轮播。下面是这个Handler的实现:

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. private static class ImageHandler extends Handler{  
  2.            
  3.         /** 
  4.          * 请求更新显示的View。 
  5.          */  
  6.         protected static final int MSG_UPDATE_IMAGE  = 1;  
  7.         /** 
  8.          * 请求暂停轮播。 
  9.          */  
  10.         protected static final int MSG_KEEP_SILENT   = 2;  
  11.         /** 
  12.          * 请求恢复轮播。 
  13.          */  
  14.         protected static final int MSG_BREAK_SILENT  = 3;  
  15.         /** 
  16.          * 记录最新的页号,当用户手动滑动时需要记录新页号,否则会使轮播的页面出错。 
  17.          * 例如当前如果在第一页,本来准备播放的是第二页,而这时候用户滑动到了末页, 
  18.          * 则应该播放的是第一页,如果继续按照原来的第二页播放,则逻辑上有问题。 
  19.          */  
  20.         protected static final int MSG_PAGE_CHANGED  = 4;  
  21.            
  22.         //轮播间隔时间  
  23.         protected static final long MSG_DELAY = 3000;  
  24.            
  25.         //使用弱引用避免Handler泄露.这里的泛型参数可以不是Activity,也可以是Fragment等  
  26.         private WeakReference<MainActivity> weakReference;  
  27.         private int currentItem = 0;  
  28.            
  29.         protected ImageHandler(WeakReference<MainActivity> wk){  
  30.             weakReference = wk;  
  31.         }  
  32.            
  33.         @Override  
  34.         public void handleMessage(Message msg) {  
  35.             super.handleMessage(msg);  
  36.             Log.d(LOG_TAG, "receive message " + msg.what);  
  37.             MainActivity activity = weakReference.get();  
  38.             if (activity==null){  
  39.                 //Activity已经回收,无需再处理UI了  
  40.                 return ;  
  41.             }  
  42.             //检查消息队列并移除未发送的消息,这主要是避免在复杂环境下消息出现重复等问题。  
  43.             if (activity.handler.hasMessages(MSG_UPDATE_IMAGE)){  
  44.                 activity.handler.removeMessages(MSG_UPDATE_IMAGE);  
  45.             }  
  46.             switch (msg.what) {  
  47.             case MSG_UPDATE_IMAGE:  
  48.                 currentItem++;  
  49.                 activity.viewPager.setCurrentItem(currentItem);  
  50.                 //准备下次播放  
  51.                 activity.handler.sendEmptyMessageDelayed(MSG_UPDATE_IMAGE, MSG_DELAY);  
  52.                 break;  
  53.             case MSG_KEEP_SILENT:  
  54.                 //只要不发送消息就暂停了  
  55.                 break;  
  56.             case MSG_BREAK_SILENT:  
  57.                 activity.handler.sendEmptyMessageDelayed(MSG_UPDATE_IMAGE, MSG_DELAY);  
  58.                 break;  
  59.             case MSG_PAGE_CHANGED:  
  60.                 //记录当前的页号,避免播放的时候页面显示不正确。  
  61.                 currentItem = msg.arg1;  
  62.                 break;  
  63.             default:  
  64.                 break;  
  65.             }   
  66.         }  
  67.     }  



集成代码:MainActivity

下面是MainActivity的代码,主要是加载View和对ViewPager进行初始化设置。因为代码量比较少,重要的部分已经加了注释,就不赘述了

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. public class MainActivity extends Activity {  
  2.        
  3.     private static final String LOG_TAG = "MainActivity";  
  4.     private ImageHandler handler = new ImageHandler(new WeakReference<MainActivity>(this));  
  5.     private ViewPager viewPager;  
  6.    
  7.     @Override  
  8.     protected void onCreate(Bundle savedInstanceState) {  
  9.         super.onCreate(savedInstanceState);  
  10.         setContentView(R.layout.activity_main);  
  11.         //初始化iewPager的内容  
  12.         viewPager = (ViewPager) findViewById(R.id.main_viewpager);  
  13.         LayoutInflater inflater = LayoutInflater.from(this);  
  14.         ImageView view1 = (ImageView) inflater.inflate(R.layout.item, null);  
  15.         ImageView view2 = (ImageView) inflater.inflate(R.layout.item, null);  
  16.         ImageView view3 = (ImageView) inflater.inflate(R.layout.item, null);  
  17.         view1.setImageResource(R.drawable.ics);  
  18.         view2.setImageResource(R.drawable.jellybean);  
  19.         view3.setImageResource(R.drawable.kitkat);  
  20.         ArrayList<ImageView> views = new ArrayList<ImageView>();  
  21.         views.add(view1);  
  22.         views.add(view2);  
  23.         views.add(view3);  
  24.         viewPager.setAdapter(new ImageAdapter(views));  
  25.         viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {  
  26.                
  27.             //配合Adapter的currentItem字段进行设置。  
  28.             @Override  
  29.             public void onPageSelected(int arg0) {  
  30.                 handler.sendMessage(Message.obtain(handler, ImageHandler.MSG_PAGE_CHANGED, arg0, 0));  
  31.             }  
  32.                
  33.             @Override  
  34.             public void onPageScrolled(int arg0, float arg1, int arg2) {  
  35.             }  
  36.                
  37.             //覆写该方法实现轮播效果的暂停和恢复  
  38.             @Override  
  39.             public void onPageScrollStateChanged(int arg0) {  
  40.                 switch (arg0) {  
  41.                 case ViewPager.SCROLL_STATE_DRAGGING:  
  42.                     handler.sendEmptyMessage(ImageHandler.MSG_KEEP_SILENT);  
  43.                     break;  
  44.                 case ViewPager.SCROLL_STATE_IDLE:  
  45.                     handler.sendEmptyMessageDelayed(ImageHandler.MSG_UPDATE_IMAGE, ImageHandler.MSG_DELAY);  
  46.                     break;  
  47.                 default:  
  48.                     break;  
  49.                 }  
  50.             }  
  51.         });  
  52.         viewPager.setCurrentItem(Integer.MAX_VALUE/2);//默认在中间,使用户看不到边界  
  53.         //开始轮播效果  
  54.         handler.sendEmptyMessageDelayed(ImageHandler.MSG_UPDATE_IMAGE, ImageHandler.MSG_DELAY);  
  55.     }//end of onCreate  
  56. }//end of MainActivity  

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值