您的位置:首页 > 其它

ViewPager使用技巧总结

2015-12-03 14:33 281 查看
在电子课本的实现中,使用了较多的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()。这里需要注意一下
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: