ViewPager使用技巧总结
2015-12-03 14:33
281 查看
在电子课本的实现中,使用了较多的ViewPager,这里对ViewPager做一个小结。
【一. 总体思路】
在电子课本的实现中,使用ViewPager实现翻书效果,在Activity的OnCreate时,向后台请求三篇课文,分别是点击课文的前一课(last)、当前课文(current)、当前课文的后一课(next),在滑动过程中,如果用户滑动到了前一课,则向后台请求前一课的前一课,成功返回后,调整本地数据,即刚刚请求下来的课文为last,之前的前一课则是当前课文(current),之前的当前课文是现在的后一课(last)。这样,用户在滑动课文时,不会有太多的迟钝感,用户体验较好。
【二. Adapter 】
【三. OnPageChangeListener】
通过监听ViewPager的变化,得到当前position。上一课课文页数为lastNum, 当前课文页数为currentNum, 下一个课课文页数为nextNum。当position < lastNum时,即当用户翻到上一篇课文时,向后台请求上一篇课文的上一课,last->last。当position >= lastNum + currentNum时,向后台请求下一课的下一课数据,即next->next.
onPageScrolled()在页面切换结束时调用,在这个方法中根据position判断是否需要向后台请求数据,并对每一页显示进行设置,例如ActionBar等。onPageScrollStateChanged()在页面切换的过程中调用,切换过程有三个状态:SCROLL_STATE_IDLE、 SCROLL_STATE_DRAGGING和SCROLL_STATE_SETTING。
为了在课文页数较多情况下,ViewPager也能够正常翻阅,所以给Adapter中的List<View>.size()设置的较大。因此,在大多数情况下,这个size都大于lastNum + currentNum + nextNum。所以,可以出现position比三者之和大的情况。通过position >= lastNum + currentNum + nextNum && nextNum == 0判断当前位置是否是超出了数据范围,给用户进行提示。而用position >= lastNum + currentNum
&& positionOffset == 0 && positionOffsetPixels == 0 && nextNum != 0来判断处于最后一篇课文的范围内。因为在翻阅到最后一篇课文时候,最后一篇课文的后面一课nextNum必然为0.当对每一页页面进行显示时,也需要判断position是否大于所有课文的页数,防止出现空指针或者数组下标越界的情况。
canJump用来判断当position < lastNum或者position >= lastNum + currenNum是否需要请求后台。后台请求数据较慢,可能在请求数据时候,用户已经翻阅了好几页下一课或者上一课。但是不能在买一次翻阅的时候都去请求一次后台。因此通过canJump判断在前一课、后一课范围内是否需要请求。当向后台请求的时候,这个字段设置为false,意思是在请求数据阶段不能多次请求。当请求成功之后,将该字段设置为true,可以再次请求。
【四. 请求后台成功,对数据进行处理】
【五. 坑】
1. 因为每一篇课文的长度不同,因此在Adapter实例化时,其List<View>的大小应该足够大,否则当三篇课文的图片都很多时,pageList将不够用,这样翻页的监听也不会起作用。
2. 在向后台请求数据成功后,应该Adapter.notifyDataSetChanged()来刷新ViewPager,而不是给ViewPager重新setAdpater()。因为在这个过程中,用户可能还在滑动ViewPager,OnPageChangeListener监听还在,而setAdapter()的瞬间,会出现数组下标越界的情况,因为对当时而言,ViewPager并没有绑定Adapter,没有数据,程序会崩溃。而且ViewPager的滑动是控件的属性,不能够设置在setAdpaer时不能够滑动。因此,在实例化Adapter后,Adapter需要的数据都设置成为public,之后都不在setAdapter。这也是一开始就将List<View>设置得足够大的原因,因为之后之歌大小都不改变了。
3. ViewPager翻阅的时候,Adapter先调用destroyItem(),再调用instantiateItem()。这里需要注意一下
【一. 总体思路】
在电子课本的实现中,使用ViewPager实现翻书效果,在Activity的OnCreate时,向后台请求三篇课文,分别是点击课文的前一课(last)、当前课文(current)、当前课文的后一课(next),在滑动过程中,如果用户滑动到了前一课,则向后台请求前一课的前一课,成功返回后,调整本地数据,即刚刚请求下来的课文为last,之前的前一课则是当前课文(current),之前的当前课文是现在的后一课(last)。这样,用户在滑动课文时,不会有太多的迟钝感,用户体验较好。
【二. Adapter 】
public class TextBookPageAdapter extends PagerAdapter{ private Context context; private List<View> pageList; public TextBookPageAdapter(Context context){ this.context = context; pageList = new ArrayList<View>(); //初始长度应该很大,这样才能够保证在课文图片多的情况下都能够显示 int size = 120; for(int i = 0; i< size; i++){ View item = LayoutInflater.from(this.context).inflate(R.layout.textbook_details_item ,null); pageList.add(item); } } @Override public int getCount() { // TODO Auto-generated method stub return pageList.size(); } @Override public boolean isViewFromObject(View arg0, Object arg1) { // TODO Auto-generated method stub return arg0 == arg1; } // PagerAdapter只缓存三张要显示的图片,如果滑动的图片超出了缓存的范围,就会调用这个方法,将图片销毁 @Override public void destroyItem(ViewGroup container, int position, Object object) { // TODO Auto-generated method stub ((ViewPager) container).removeView(pageList.get(position)); } @Override public void startUpdate(View arg0) { } // 当要显示的图片可以进行缓存的时候,会调用这个方法进行显示图片的初始化,我们将要显示的ImageView加入到ViewGroup中,然后作为返回值返回即可 @Override public Object instantiateItem(ViewGroup container, int position) { // TODO Auto-generated method stub // 友盟统计 if(position < TextBookDetailsActivity.TextBookDetailsInfoList.size()){ //当position超出课文详情时,不需要在setView,设置每一个ViewPager的Item View view = pageList.get(position); PhotoView image = ((PhotoView) view.findViewById(R.id.pv_textbook_details_img)); return pageList.get(position); } else{ if(position >= 1){ return pageList.get(position - 1); }else { return null; } } } @Override public int getItemPosition(Object object) {//加上这个,adsAdapter.notifyDataSetChanged()才可以刷新 return POSITION_NONE; } }翻阅的时候,先调用destroyItem(),再调用instantiateItem()。这里需要注意一下
【三. OnPageChangeListener】
通过监听ViewPager的变化,得到当前position。上一课课文页数为lastNum, 当前课文页数为currentNum, 下一个课课文页数为nextNum。当position < lastNum时,即当用户翻到上一篇课文时,向后台请求上一篇课文的上一课,last->last。当position >= lastNum + currentNum时,向后台请求下一课的下一课数据,即next->next.
public class FirstOrLastPageJumpListener implements OnPageChangeListener { private Runnable lastCommand; //翻到上一课执行的操作 private Runnable nextCommand; //翻到下一刻执行的操作 private int curPosition; //现在的位置 /** * * @param firstCommand * 第一页事件触发时操作 * @param firstCommand * 最后一页事件触发时操作 */ public FirstOrLastPageJumpListener(Runnable lastCommand, Runnable nextCommand) { this.lastCommand = lastCommand; this.nextCommand = nextCommand; } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { if (position < lastNum && positionOffset == 0 && positionOffsetPixels == 0) { //当翻到上一课时执行的操作 if (canJump) { lastCommand.run(); // 事件执行一次后进行重置,避免事件多次触发 canJump = false; } } else if (position >= lastNum + currentNum && positionOffset == 0 && positionOffsetPixels == 0 && nextNum != 0) { //当下一课时执行的操作 //nextNum!=0是为翻到最后一课后继续向后翻做保护,这种情况下不向后台请求数据 if (canJump) { nextCommand.run(); // 事件执行一次后进行重置,避免事件多次触发 canJump = false; } } else if(position >= lastNum + currentNum + nextNum && nextNum == 0){ GPUtils.toast(context, "已经是最后一课啦~请往前翻~"); } //ViewPager当前Position currentPosition = position; //做保护,防止最后一课看完之后还继续向后翻 if(! (position >= TextBookDetailsInfoList.size())){ //对于每一个页面显示的操作 if(position < lastNum) { //上一篇课文 } else if (position < lastNum + currentNum) {//当前课文 } else {//下一篇课文 } } } /* * (non-Javadoc) * * @see * android.support.v4.view.ViewPager.OnPageChangeListener#onPageSelected * (int) */ @Override public void onPageSelected(int position) { curPosition = position; } /** * public static final int SCROLL_STATE_IDLE = 0; * * public static final int SCROLL_STATE_DRAGGING = 1; * * public static final int SCROLL_STATE_SETTLING = 2; */ @Override public void onPageScrollStateChanged(int state) { //滑动状态 } }程序中,在监听实例化的时候,不需要传入lastNum, currentNum, nextNum这些变量。这些变量是在每次向后台请求数据后都会变化,如果实例化时候需要这些参数,那么每次向后台请求数据后,都需要重新实例化,会出现数据下标越界情况。程序crash。因此,这些变量都设置为全局的,Listener自己会去判断。
onPageScrolled()在页面切换结束时调用,在这个方法中根据position判断是否需要向后台请求数据,并对每一页显示进行设置,例如ActionBar等。onPageScrollStateChanged()在页面切换的过程中调用,切换过程有三个状态:SCROLL_STATE_IDLE、 SCROLL_STATE_DRAGGING和SCROLL_STATE_SETTING。
为了在课文页数较多情况下,ViewPager也能够正常翻阅,所以给Adapter中的List<View>.size()设置的较大。因此,在大多数情况下,这个size都大于lastNum + currentNum + nextNum。所以,可以出现position比三者之和大的情况。通过position >= lastNum + currentNum + nextNum && nextNum == 0判断当前位置是否是超出了数据范围,给用户进行提示。而用position >= lastNum + currentNum
&& positionOffset == 0 && positionOffsetPixels == 0 && nextNum != 0来判断处于最后一篇课文的范围内。因为在翻阅到最后一篇课文时候,最后一篇课文的后面一课nextNum必然为0.当对每一页页面进行显示时,也需要判断position是否大于所有课文的页数,防止出现空指针或者数组下标越界的情况。
canJump用来判断当position < lastNum或者position >= lastNum + currenNum是否需要请求后台。后台请求数据较慢,可能在请求数据时候,用户已经翻阅了好几页下一课或者上一课。但是不能在买一次翻阅的时候都去请求一次后台。因此通过canJump判断在前一课、后一课范围内是否需要请求。当向后台请求的时候,这个字段设置为false,意思是在请求数据阶段不能多次请求。当请求成功之后,将该字段设置为true,可以再次请求。
【四. 请求后台成功,对数据进行处理】
//网络请求成功 private void onSuccess(TextBookDetailsResponse response) { //不需要检查response.data为空的情况,因为第一课和最后一课的Last和Next均为空 if(isCreatNow){//请求三个课文,Activity刚OnCreat //后台返回的数据直接加入到List中,请求顺序为last,current,next //为上一课、当前课文、下一课的课文数量赋值 if(requestType.equals(LAST)){ }else if(requestType.equals(CURRENT)){ }else{ //创建时三次请求完毕 isCreatNow = false; } if(!isCreatNow){ //三次请求完毕再显示 currentPosition = lastNum; //设置Adapter等,仅在这里setAdapter pageAdapter = new TextBookPageAdapter(context); textViewPager.setAdapter(pageAdapter); textViewPager.setOnPageChangeListener(new FirstOrLastPageJumpListener(lastTextRunnable, nextTextRunnable)); pageAdapter.notifyDataSetChanged(); textViewPager.setCurrentItem(currentPosition); //if做保护 if(currentPosition < TextBookDetailsInfoList.size()){ //设置ActionBar显示 int posTemp = TextBookDetailsInfoList.get(currentPosition).currentIndex; titleTextView.setText(TextBookInfoList.get(1).class_name +"("+ posTemp +"/" + currentNum + ")"); } canJump = true;//此时可以再次请求后台数据 }else{//没有完成创建时三次请求 if(requestType.equals(LAST)) getTextBookDetailsData(requestType = CURRENT); else getTextBookDetailsData(requestType = NEXT); } }else if(requestType.equals(LAST)){//请求上一篇课文 ArrayList<TextBookDetailsInfo> detailsTemp = new ArrayList<TextBookDetailsInfo>(); int num = lastNum + currentNum; //修改课文详情的List for(int i = 0; i < num ; i++){ detailsTemp.add(TextBookDetailsInfoList.get(i)); } TextBookDetailsInfoList.clear(); int totalNum = response.data.size(); int currentIndex = 0; for(TextBookDetailsInfo info : response.data){ info.totalNum = totalNum; info.currentIndex = ++currentIndex; TextBookDetailsInfoList.add(info); } for(TextBookDetailsInfo info : detailsTemp){ TextBookDetailsInfoList.add(info); } //修改课文的List TextBookInfoList.set(2, TextBookInfoList.get(1)); TextBookInfoList.set(1, TextBookInfoList.get(0)); TextBookInfoList.set(0, response.text_book_data); //修改课文图片数量 nextNum = currentNum; currentNum = lastNum; lastNum = response.data.size(); //设置显示的Item textViewPager.setCurrentItem(currentPosition + lastNum); pageAdapter.notifyDataSetChanged(); currentPosition += lastNum; //if做保护 if(currentPosition < TextBookDetailsInfoList.size()){ //设置ActionBar显示 int posTemp = TextBookDetailsInfoList.get(currentPosition).currentIndex; titleTextView.setText(TextBookInfoList.get(1).class_name +"("+ posTemp + "/" + currentNum + ")"); } canJump = true; }else if(requestType.equals(NEXT)){ //修改课文详情的List,remove应该倒着减少 for(int i = lastNum - 1; i >= 0; i--){ if(TextBookDetailsInfoList.size() > i){ //这里的if是防止数组下标越界,做保护 TextBookDetailsInfoList.remove(i); } } int totalNum = response.data.size(); int currentIndex = 0; for(TextBookDetailsInfo info : response.data){ info.totalNum = totalNum; info.currentIndex = ++currentIndex; TextBookDetailsInfoList.add(info); } //修改课文的List TextBookInfoList.remove(0); TextBookInfoList.add(response.text_book_data); //当前位置索引改变 currentPosition -= lastNum; //修改课文图片数量 lastNum = currentNum; currentNum = nextNum; nextNum = response.data.size(); //设置显示的Item textViewPager.setCurrentItem(currentPosition); pageAdapter.notifyDataSetChanged(); //设置ActionBar显示 if(currentPosition < TextBookDetailsInfoList.size()){ //if语句目的是加保护 int posTemp = TextBookDetailsInfoList.get(currentPosition).currentIndex; titleTextView.setText(TextBookInfoList.get(1).class_name +"(" + posTemp + "/" + currentNum + ")"); } canJump = true; } }
【五. 坑】
1. 因为每一篇课文的长度不同,因此在Adapter实例化时,其List<View>的大小应该足够大,否则当三篇课文的图片都很多时,pageList将不够用,这样翻页的监听也不会起作用。
public TextBookPageAdapter(Context context){ this.context = context; pageList = new ArrayList<View>(); //初始长度应该很大,这样才能够保证在课文图片多的情况下都能够显示 int size = 120; for(int i = 0; i< size; i++){ View item = LayoutInflater.from(this.context).inflate(R.layout.textbook_details_item ,null); pageList.add(item); } }
2. 在向后台请求数据成功后,应该Adapter.notifyDataSetChanged()来刷新ViewPager,而不是给ViewPager重新setAdpater()。因为在这个过程中,用户可能还在滑动ViewPager,OnPageChangeListener监听还在,而setAdapter()的瞬间,会出现数组下标越界的情况,因为对当时而言,ViewPager并没有绑定Adapter,没有数据,程序会崩溃。而且ViewPager的滑动是控件的属性,不能够设置在setAdpaer时不能够滑动。因此,在实例化Adapter后,Adapter需要的数据都设置成为public,之后都不在setAdapter。这也是一开始就将List<View>设置得足够大的原因,因为之后之歌大小都不改变了。
3. ViewPager翻阅的时候,Adapter先调用destroyItem(),再调用instantiateItem()。这里需要注意一下
相关文章推荐
- mysql 常用函数
- windows下安装ubuntu
- LoadRunner添加负载机和安装总结
- ORA-12541:TNS:no listener
- 比editplus好用的编辑器sublime text3
- spring源码解析-Aop
- Android动画笔记
- iOS开发多线程篇—GCD的常见用法(二)
- Masnory的简单使用方法
- hadoop-2.6.2所有配置文件参数
- jquery给input标签赋值时出现value有值页面没显示的情况
- 【资源汇总】Android应用解决方案全攻略
- ubuntu安装ssh服务
- 复杂结构数据提交
- mysql 中 LIMIT的简单用法
- python 怎么模拟加header(如User-Agent、Content-Type等等)
- 【iOS 开发】ios app加速审核服务有吗
- 代码到Redis之间的中间层操作|Redis的增删改查
- 【风马一族_Android】代码英语之二 布局文件的Android各个参数
- DB2 fp11补丁安装遇到的问题