如何在列表视图中正确地进行无限加载

How to properly do infinite loading in listview

主题: 我正在从我的外部数据库下载一堆数据,我想一次只显示其中的几个 (10)。当用户向下滚动时,我想自动下载并显示另外 10 个(并在列表视图下方显示加载进度条作为其页脚视图,同时项目正在下载并加载到列表视图中)。我想知道这样做是否正确,因为我遇到了一些问题:

问题

1.Sometimes 它很滞后,就像最后一个可见的项目是项目 #40 一样,我无法再向下滚动,除非我先向上滚动一点然后再向下滚动。 (FooterView 未显示)

2.Sometimes 加载进度条一直亮着,我必须先向上滚动然后向下滚动,这样它才会消失,更多的项目会出现。 (白等了,下载完FooterView也没去掉)

它应该如何工作: 首先下载 10 个项目,其中 6 个是可见的(取决于屏幕大小)。当我向下滚动并到达第 8 项时,会下载另外 10 个项目,因此我可以继续向下滚动,或者如果尚未下载这些项目,加载进度条将作为 ListView 的页脚显示在屏幕底部,直到项目被下载。项目加载后,FooterView 将被删除,我可以继续向下滚动。

boolean all_items_downloaded = false;

    num_loadstart = 0;
    num_loadfinish = 10;

    new DownloadUsers3().execute(String.valueOf(num_loadstart), String.valueOf(num_loadfinish));

    lv.setOnScrollListener(new OnScrollListener() {

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

            int lastInScreen = firstVisibleItem + visibleItemCount;
            Log.i("FOLLOW Onscroll totalItemCount", totalItemCount + "");
            Log.i("FOLLOW Onscroll visibleItemCount", visibleItemCount + "");
            Log.i("FOLLOW Onscroll lastInScreen", lastInScreen + "");

            if (lastInScreen == totalItemCount) {     
                if (all_items_downloaded) {
                    lv.removeFooterView(loadMoreView);
                }
            }

            //when the last visible item is near the last downloaded item then start loading more
            if ((totalItemCount - lastInScreen == 2) && !(loadingMore) && (totalItemCount != 0)) {
                Log.i("FOLLOW LOADMORE", "NOW");
                if (!all_items_downloaded) {
                    new DownloadUsers3().execute(String.valueOf(totalItemCount), "10");
                }
            }

            lastPos = lv.getLastVisiblePosition();
        }

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {


        }

    });

    return view;
}


public class DownloadUsers3 extends AsyncTask<String, Void, Integer> {

    @Override
    protected void onPostExecute(Integer result) {
        Log.i("DownloadUsers3 onPost result", result + "");
        pb.setVisibility(View.GONE);
        loading_ll.setVisibility(View.GONE);
        llMain.setVisibility(View.VISIBLE);

        if (result != null) {

            if (result < 1) tv_noone.setVisibility(View.VISIBLE);
            else tv_noone.setVisibility(View.GONE);

            //set adapter only when items are downloaded the first time aka. no scrolling has been made
            if (result < 11) {
                myadapter = new MyAdapter(ctx, arr_users_id, arr_users_username, arr_users_photo, arr_users_followed, arr_users_numadded, arr_users_numcompleted, arr_users_fbuserid, arr_users_imagetype, arr_users_twuserid, arr_users_twphoto);
                lv.setAdapter(myadapter);
            } else {
                myadapter.notifyDataSetChanged();
            }
        }
        loadingMore = false;

    }


    @Override
    protected void onPreExecute() {
        //show progress dialog only the first time when items are being downloaded
//this is just an overlay progress dialog to show the users when they first open the fragment
        if (arr_users_id.size() < 10) {
            pb.setVisibility(View.VISIBLE);
            loading_ll.setVisibility(View.VISIBLE);
            llMain.setVisibility(View.GONE);
        }
    }

    @Override
    protected Integer doInBackground(String... params) {

        loadingMore = true;

        try{
            HttpClient httpclient = new DefaultHttpClient();
            HttpPost httppost = null;
            httppost = new HttpPost(list_all_users);

            List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(1);
            String start = params[0];
            String finish = params[1];

            Log.i("At session_userid", session_userid + "");
            nameValuePairs.add(new BasicNameValuePair("session_userid", session_userid));
            nameValuePairs.add(new BasicNameValuePair("start", start));
            nameValuePairs.add(new BasicNameValuePair("finish", finish));
            httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
            HttpResponse response = httpclient.execute(httppost);
            HttpEntity entity = response.getEntity();
            is = entity.getContent();
            }catch(Exception e){
                    Log.e("error", "Error in http connection "+e.toString());
            }

            try{
                BufferedReader reader = new BufferedReader(new InputStreamReader(is,"iso-8859-1"),8);
                StringBuilder sb = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null) {
                     sb.append(line + "\n");
                }
                is.close();
                Bresult=sb.toString();
                Log.i("Bresult", Bresult + "");
            } catch(Exception e){
                    Log.e("error", "Error converting result "+e.toString());
            }

            if (Bresult.length() > 10) {
                try {
                    jArray = new JSONArray(Bresult);
                    for(int i=0;i<jArray.length();i++){
                        JSONArray innerJsonArray = jArray.getJSONArray(i);
                        for(int j=0;j<innerJsonArray.length();j++){  
                            JSONObject jsonObject = innerJsonArray.getJSONObject(j);

                            arr_users_id.add(jsonObject.getString("ID"));
                            arr_users_username.add(jsonObject.getString("USERNAME"));
                            arr_users_photo.add(jsonObject.getString("PHOTO"));
                            arr_users_followed.add(jsonObject.getString("DO_I_FOLLOW_HIM"));
                            arr_users_numadded.add(jsonObject.getString("NUM_ALL"));
                            arr_users_numcompleted.add(jsonObject.getString("NUM_DONE"));
                            arr_users_recentbucket.add(jsonObject.getString("REC"));
                            arr_users_fbuserid.add(jsonObject.getString("FB_USERID"));
                            arr_users_imagetype.add(jsonObject.getString("IMAGE_TYPE"));
                            arr_users_twuserid.add(jsonObject.getString("TW_USERID"));
                            arr_users_twphoto.add(jsonObject.getString("TW_PHOTO"));

                            Log.i("arr_users_username.get(" + j + ")", arr_users_username.get(j));

                        }
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }

            } else {
                all_items_downloaded = true;
                return null;
            }
        Log.i("arr_users_username.size()", arr_users_username.size() + "");
        return arr_users_id.size();
    }
}

查询的PHP代码是这样的:

SELECT ... FROM ... GROUP BY ... ORDER BY ... LIMIT $start, $finish

其中 $start 和 $finish 是从 Java.

传递的变量

完成 Volley

首先,我将 volley 添加为库项目,然后在 this 教程中添加了 类。

private void makeJsonArrayRequest(final String start, final String finish) {

    loadingMore = true;
    StringRequest postReq = new StringRequest(Method.POST, list_all_users2, new Response.Listener<String>() {

        @Override
        public void onResponse(String response) {

            Log.i("VOLLEYY", response);
            Log.i("VOLLEYY.length()", response.length() + "");
            if (response.length() > 10) {
                try {
                    jArray = new JSONArray(response);
                    for(int i=0;i<jArray.length();i++){
                        JSONArray innerJsonArray = jArray.getJSONArray(i);
                        for(int j=0;j<innerJsonArray.length();j++){  
                            JSONObject jsonObject = innerJsonArray.getJSONObject(j);

                            arr_users_id.add(jsonObject.getString("ID"));
                            arr_users_username.add(jsonObject.getString("USERNAME"));
                            arr_users_photo.add(jsonObject.getString("PHOTO"));
                            arr_users_followed.add(jsonObject.getString("DO_I_FOLLOW_HIM"));
                            arr_users_numadded.add(jsonObject.getString("NUM_ALL"));
                            arr_users_numcompleted.add(jsonObject.getString("NUM_DONE"));
                            arr_users_recentbucket.add(jsonObject.getString("REC"));
                            arr_users_fbuserid.add(jsonObject.getString("FB_USERID"));
                            arr_users_imagetype.add(jsonObject.getString("IMAGE_TYPE"));
                            arr_users_twuserid.add(jsonObject.getString("TW_USERID"));
                            arr_users_twphoto.add(jsonObject.getString("TW_PHOTO"));

                            Log.i("VOLLEY arr_users_username.get(" + j + ")", arr_users_username.get(j));
                            Log.i("VOLLEY arr_users_followed.get(" + j + ")", arr_users_followed.get(j));
                            Log.i("VOLLEY size", arr_users_followed.size() + "");


                            pb.setVisibility(View.GONE);
                            loading_ll.setVisibility(View.GONE);
                            llMain.setVisibility(View.VISIBLE);

                            if (arr_users_id.size() < 1) tv_noone.setVisibility(View.VISIBLE);
                            else tv_noone.setVisibility(View.GONE);

                            //set adapter only when users are download the first time aka. no scrolling has been made
                            if (arr_users_id.size() < 11) {
                                myadapter = new MyAdapter(ctx, arr_users_id, arr_users_username, arr_users_photo, arr_users_followed, arr_users_numadded, arr_users_numcompleted, arr_users_fbuserid, arr_users_imagetype, arr_users_twuserid, arr_users_twphoto);
                                lv.setAdapter(myadapter);
                            } else {
                                myadapter.notifyDataSetChanged();
                            }


                        }
                    }
                    loadingMore = false;
                    Log.i("TIMER", "FINISH");
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            } else {
                all_items_downloaded = true;
                lv.removeFooterView(loadMoreView);
            }

        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
           // System.out.println("Error ["+error+"]");
            Log.i("VOLLEY_ERROR", error.toString());
        }
    }) {

        @Override
        protected Map<String, String> getParams() {
            Map<String, String> params = new HashMap<String, String>();
            params.put("session_userid", session_userid);
            params.put("start", start);
            params.put("finish", finish);
            return params;
        }

    };

    postReq.setShouldCache(false);
    AppController.getInstance().addToRequestQueue(postReq);
}

加载速度变快了一点,但问题依然存在。有时加载进度条(FooterView)没有消失,所以我需要向上滚动一点然后再向下滚动。我发现当我以某种方式跳过适当的项目(应该启动下一个包的加载的项目)时发生了这种情况:

01-17 18:15:57.809: I/FOLLOW Onscroll totalItemCount(3230): 11
01-17 18:15:57.809: I/FOLLOW Onscroll lastInScreen(3230): 6
01-17 18:15:57.859: I/FOLLOW Onscroll totalItemCount(3230): 11
01-17 18:15:57.859: I/FOLLOW Onscroll lastInScreen(3230): 7
01-17 18:15:57.869: I/FOLLOW Onscroll totalItemCount(3230): 11
01-17 18:15:57.869: I/FOLLOW Onscroll lastInScreen(3230): 8
01-17 18:15:57.869: I/FOLLOW Onscroll totalItemCount(3230): 11
01-17 18:15:57.869: I/FOLLOW Onscroll lastInScreen(3230): 8
01-17 18:15:57.899: I/FOLLOW Onscroll totalItemCount(3230): 11
01-17 18:15:57.899: I/FOLLOW Onscroll lastInScreen(3230): 10
01-17 18:15:57.929: I/FOLLOW Onscroll totalItemCount(3230): 11
01-17 18:15:57.929: I/FOLLOW Onscroll lastInScreen(3230): 11

根据此块,新加载应在最后一个可见项目为 9 时开始,但不知何故我在滚动过程中跳过了它。

if ((totalItemCount - lastInScreen == 2) && !(loadingMore) && (totalItemCount != 0)) {
    if (!all_items_downloaded) {
    makeJsonArrayRequest(String.valueOf(totalItemCount), "10");
    }
}

我通过修改if (lastInScreen == totalItemCount) { }块解决了

if (lastInScreen == totalItemCount) {     
                    if (all_items_downloaded) {
                        //Log.i("all_items_downloaded", "yes");
                        lv.removeFooterView(loadMoreView);
                    } else {
                        Log.i("FOLLOW loadingMore", loadingMore + "");
                        if (!loadingMore) {
                            Log.i("TAG", "NOW IT STOPS SO I QUERY AGAIN");
                            makeJsonArrayRequest(String.valueOf(totalItemCount), "10");
                        }
                    }
                }

万一有人需要...

更新 自从我将大部分 Asynctasks 更改为 Volley 以来已经过去了几周。我刚刚做了一个小实验,检查 Volley 与 Async 相比的下载时间。我从 VPS 下载了一堆数据,这是结果(以毫秒为单位):

您的第一个错误是使用了 AsyncTask。你应该使用 Volley。每个人都应该使用 Volley。如果你不知道我在说什么,check out the lecture at I/O 2013 here。只是 google 它找到了官方教程。这本身就可以解决你的滞后问题。它还将修复由于 BG 中的 AsyncTask 完成而在应用程序的这一部分之外滞后,除非你真的正确取消它,我怀疑你这样做(不是因为我认为你是一个糟糕的程序员,而是因为实际上没有人我曾经合作过正确取消它们)。

除此之外,我认为您的伪代码还不错。您正在以正确的方式处理它以保存用户数据和加载时间。

看来,在你的最后一个 AsyncTask 中,你正在 doInBackground() 中进行所有数据解析,这不是它应该处理的方式(如果我正确阅读你的代码并且它之后不会对这些数据做一些奇怪的事情)。您应该在 onPostExecute() 中解析数据,然后在您的 ListView 上调用 notifyDataSetChanged(),否则它不知道数据是新的。还要确保您有回调,以便您的自定义视图知道新数据何时到达。

老实说,如果你切换到 Volley(你真的应该),它会让一切变得更容易,包括你的调试,因为样板代码少了很多。