Android Scrollview嵌套下listView动态加载数据,解决onScrollChanged执行多次数据重复问题

这一篇博客和上一篇讲的都是listView的动态加载,但有所不同的是,本篇的listView是嵌套在ScrollView下的,有时候在一个Activity中可能分为好几个模块,由于展示的需要(手机屏幕大小有限),我们需要在这些模块的外层嵌套ScrollView,这时候我们就不能根据listView的状态来监听是否需要加载数据啦,但是转念一想,我们可以监听scrollview,但scrollview滑动到最底部时,那listview肯定也滑动到底部啦。思路确定啦,但是在开发的过程中发现了一个bug,listView有时候会加载重复的数据,搞的莫名其妙,后来经过查资料得知在ScrollView滑动中,onScrollChanged总是在不停被调用,导致多次向服务器端请求同样的数据,这时候就需要我们自己做并发控制

ZdyListView

 1 package com.example.listview;
 2 
 3 import android.content.Context;
 4 import android.util.AttributeSet;
 5 import android.view.LayoutInflater;
 6 import android.view.View;
 7 import android.widget.ListView;
 8 import android.widget.ProgressBar;
 9 import android.widget.TextView;
10 
11 /**
12  * Created by keranbin on 2015/10/25.
13  */
14 public class ZdyListView extends ListView {
15     private Context context;
16     private View footer;
17     private ProgressBar progressBar; 
18     private TextView tv;
19 
20     public ZdyListView(Context context) {
21         super(context);
22         init(context);
23     }
24     public ZdyListView(Context context, AttributeSet attrs) {
25         super(context, attrs);
26         init(context);
27     }
28     public ZdyListView(Context context, AttributeSet attrs, int defStyle) {
29         super(context, attrs, defStyle);
30         init(context);
31     }
32     private void init(Context context) {
33         this.context=context;
34         footer=LayoutInflater.from(context).inflate(R.layout.activity_footer, null);
35         this.addFooterView(footer);
36         progressBar=(ProgressBar) footer.findViewById(R.id.progressBar);
37         tv=(TextView) footer.findViewById(R.id.tv);
38         tv.setText("上拉加载更多");
39     }
40 
41     //正在加载数据,将listview底部提示文字置为"正在加载中。。。。"
42     public void onLoading(){
43         progressBar.setVisibility(VISIBLE);
44         tv.setText("正在加载中。。。。");
45     }
46 
47     //加载完毕,将listView底部提示文字改为"上拉加载更多"
48     public void LoadingComplete(){
49         progressBar.setVisibility(GONE);
50         tv.setText("上拉加载更多");
51     }
52 
53     
54     //重写onMeasure,解决scrollview与listview冲突
55     @Override
56     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
57         int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
58                 MeasureSpec.AT_MOST);
59         super.onMeasure(widthMeasureSpec, expandSpec);
60     }
61 
62 
63 
64 }

ZdyScrollView

 1 package com.example.listview;
 2 
 3 import android.content.Context;
 4 import android.util.AttributeSet;
 5 import android.view.View;
 6 import android.widget.ScrollView;
 7 
 8 public class ZdyScrollView extends ScrollView{
 9     private int flag=0;    //并发控制标志位
10 
11     private OnZdyScrollViewListener onZdyScrollViewListener;
12 
13     public ZdyScrollView(Context context) {
14         super(context);
15     }
16 
17     public ZdyScrollView(Context context, AttributeSet attrs) {
18         super(context, attrs);
19     }
20 
21     public ZdyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
22         super(context, attrs, defStyleAttr);
23     }
24 
25     //listview加载完毕,将并发控制符置为0
26     public void loadingComponent(){
27         flag=0;
28     }
29 
30     @Override
31     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
32         super.onScrollChanged(l, t, oldl, oldt);
33         View view=this.getChildAt(0);
34         //如果scrollview滑动到底部并且并发控制符为0,回调接口向服务器端请求数据
35         if (this.getHeight() + this.getScrollY() == view.getHeight() && flag == 0) {
36             flag = 1;//一进来就将并发控制符置为1,虽然onScrollChanged执行多次,但是由于并发控制符的值为1,不满足条件就不会执行到这
37             onZdyScrollViewListener.ZdyScrollViewListener();
38         }
39     }
40 
41     public void setOnZdyScrollViewListener(OnZdyScrollViewListener onZdyScrollViewListener){
42         this.onZdyScrollViewListener=onZdyScrollViewListener;
43     }
44 
45     public interface OnZdyScrollViewListener{
46         public void ZdyScrollViewListener();
47     }
48 
49 }

MainActivity

  1 package com.example.listview;
  2 
  3 import java.util.ArrayList;
  4 
  5 import com.example.listview.ZdyScrollView.OnZdyScrollViewListener;
  6 
  7 import android.app.Activity;
  8 import android.os.Bundle;
  9 import android.os.Handler;
 10 import android.os.Message;
 11 
 12 
 13 public class MainActivity extends Activity {
 14 
 15     private ZdyScrollView scrollView;
 16     private ZdyListView listView;
 17     private ListViewAdapter adapter;
 18     
 19     //一次从服务器端请求十条数据
 20     private int current=1;  
 21     private int number=9;
 22     
 23     ArrayList<String> listDatas;
 24 
 25     private Handler handler=new Handler(){
 26         public void handleMessage(Message msg) {
 27             setListView((ArrayList<String>)msg.obj);
 28         }
 29     };
 30 
 31 
 32     @Override
 33     protected void onCreate(Bundle savedInstanceState) {
 34         super.onCreate(savedInstanceState);
 35         setContentView(R.layout.activity_main);
 36         initView();
 37         initListener();
 38     }
 39 
 40     
 41     @Override
 42     protected void onStart() {
 43         super.onStart();
 44         //第一次请求数据
 45         getDataThread(current,number);
 46     }
 47     
 48     
 49 
 50     private void initView() {
 51         listView=(ZdyListView) this.findViewById(R.id.listView);
 52         scrollView=(ZdyScrollView) this.findViewById(R.id.scrollView);
 53 
 54     }
 55 
 56     private void initListener() {
 57         scrollView.setOnZdyScrollViewListener(new OnZdyScrollViewListener() {
 58             @Override
 59             public void ZdyScrollViewListener() {
 60                 //上拉加载更多数据
 61                 getDataThread(current,number);
 62             }
 63         });
 64 
 65     }
 66 
 67     
 68     private void setListView(ArrayList<String> datas) {
 69         if(listDatas==null){//第一次加载数据,为listview设置适配器
 70             listDatas=new ArrayList<String>();
 71             listDatas.addAll(datas);
 72             adapter=new ListViewAdapter(MainActivity.this,listDatas);
 73             listView.setAdapter(adapter);
 74             current=adapter.getCount()+1; //记录当前listview中的最后一个数据的下标
 75             listView.LoadingComplete();   //告诉listview已经加载完毕,重置提示文字
 76             scrollView.loadingComponent();//告示scrollview已经加载完毕,重置并发控制符的值
 77         }else{//下拉加载更多数据,只需要告诉adapter刷新就行
 78             listDatas.addAll(datas);
 79             adapter.notifyDataSetChanged();
 80             current=adapter.getCount()+1;
 81             listView.LoadingComplete();
 82             scrollView.loadingComponent();
 83         }
 84         
 85     };
 86     
 87     private void getDataThread(final int current, final int number){
 88         final Message msg=new Message();
 89         listView.onLoading();
 90         new Thread(){
 91             public void run() {
 92                 try {//模拟向服务器请求数据耗时
 93                     sleep(10000);
 94                 } catch (InterruptedException e) {
 95                     e.printStackTrace();
 96                 }
 97                 ArrayList<String> datas=ListViewDatas.returnNum(current, current+number);
 98                 if(datas!=null&&datas.size()>0){
 99                     msg.obj=datas;
100                     handler.sendMessage(msg);
101                 }
102             };
103         }.start();
104     }
105 
106 }

模拟服务器端发回数据ListViewDatas

 1 package com.example.listview;
 2 
 3 import java.util.ArrayList;
 4 
 5 public class ListViewDatas {
 6     //模拟服务器端数据库中的数据,假设现在只有3条数据
 7     public static int NUM=53;
 8     public static ArrayList<String> returnNum(int startNum,int endNum){
 9         ArrayList<String> list=new ArrayList<String>();
10         if(endNum<=NUM){//客户端请求的数据在数据库数据范围之内
11             for(int i=startNum;i<=endNum;i++){
12                 list.add(String.valueOf(i));
13             }
14             return list;
15         }else if(endNum>=NUM&&startNum<=NUM){//客户端请求的数据不全在数据库数据范围之内
16             for(int i=startNum;i<=NUM;i++){
17                 list.add(String.valueOf(i));
18             }
19             return list;            
20         }else if(startNum>NUM){//客户端请求的数据超出数据库数据范围之内
21             return null;
22         }
23         return null;
24     }
25 }

页面布局activity_main.xml

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     android:layout_width="fill_parent"
 3     android:layout_height="fill_parent"
 4     android:orientation="vertical" >
 5 
 6     <com.example.listview.ZdyScrollView
 7         android:id="@+id/scrollView"
 8         android:layout_width="match_parent"
 9         android:layout_height="match_parent" >
10 
11         <com.example.listview.ZdyListView
12             android:id="@+id/listView"
13             android:layout_width="match_parent"
14             android:layout_height="match_parent" >
15         </com.example.listview.ZdyListView>
16     </com.example.listview.ZdyScrollView>
17 
18 </LinearLayout>

listView的footView  activity_footer.xml

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:orientation="vertical" >
 6 
 7     <LinearLayout
 8         android:id="@+id/load_layout"
 9         android:layout_width="match_parent"
10         android:layout_height="wrap_content"
11         android:gravity="center"
12         android:orientation="horizontal"
13         android:paddingBottom="10dip"
14         android:paddingTop="10dip" >
15 
16         <ProgressBar
17             style="?android:attr/progressBarStyleSmall"
18             android:layout_width="wrap_content"
19             android:layout_height="wrap_content"
20             android:id="@+id/progressBar" 
21             android:visibility="gone"/>
22 
23         <TextView
24             android:layout_width="wrap_content"
25             android:layout_height="wrap_content"
26             android:text="正在加载..." 
27             android:id="@+id/tv"/>
28 
29     </LinearLayout>
30 
31 </LinearLayout>
原文地址:https://www.cnblogs.com/bdsdkrb/p/5070891.html