ViewPager使用技巧总结

在电子课本的实现中,使用了较多的ViewPager,这里对ViewPager做一个小结。


【一.  总体思路】

在电子课本的实现中,使用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()。这里需要注意一下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值