آموزش استفاده از lazy loading listView (کامل)
من بررسی کردم دیدم اکثر تاپیک های مربوط به این موضوع ناقص پاسخ داده شده و چون چند نفر از دوستان خواستن که کامل یاد بگیرن و باهاش کار کنن من آموزش کامل استفاده از این روش رو بدون نیاز به لایبری و کاملا دست ساز و خانگی قرار میدم میدم .
اول از همه شما باید بدونید که برای ایجاد lazy listView ما نیاز داریم تا هم سرور و هم کلاینت رو طوری براش کد بنویسیم که کاملا داینامیک عمل کنه.
ابتدا از سمت سرور شروع میکنیم و نحوه پاسخ دادن سرور روبه کلاینت بهتون نشون میدم .
همه ریکوست هایی که برای پر کردن لیست ویو استفاده میکنید باید به شکل زیر باشه :
$part = $_GET['part'];
$start = $part * 10;
SELECT * FROM MyTable LIMIT start, 10;
خُب ، حالا part$ و start$ چیه ؟
همونطور که از اسمشون مشخصه part$ برابر قسمت نمایشی هست و start$ برای مدیریت نمایش آیتم هاست که شما در کلاینت با ارسال مقدار part$ تعیین میکنید که چندمین قسمت از لیست رو میخواید بگیرید ، عدد 10 نیز یعنی ما در هر درخواست فقط 10 آیتم رو نیاز داریم تا به کاربر نشون بدیم.
دیگه با سرور کاری نداریم و میریم سراغ کلاینت . در کلاینت شما برای اینکه بتونید از این سیستم استفاده کنید ابتدا باید آخرین وضعیت آیتم هارو ببینید .
با استفاده از کد زیر آخرین آیتمی که کاربر مشاهده میکنه رو شما میگیرید .
listView.setOnScrollListener(new OnScrollListener() { @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { int lastItem = firstVisibleItem + visibleItemCount; if (adapter.getCount() >= 10 && lastItem > adapter.getCount() - 3) { boolean isLoading = false; if (!isLoading) { if(readNetworkStatus(context)){ if(lastItem > lastItemPosition){ lastItemPosition = adapter.getCount(); receivedData(); } } isLoading = true; } } } public void onScrollStateChanged(AbsListView view, int scrollState) {} });
کد بالا شاید برای دوستانی که آشنا نیستن یکم وحشتناک باشه ولی من براتون قسمت های ختلف کد رو به صورت کامل توضیح میدم .
کاملا مشخص هست که ما یک لیست ویو داریم و براش یک لیسنر از نوع OnScrollListener تعریف میکنیم که برای ما چند 2 متد رو میاره که یکیش onScrollStateChanged که برای نمایش وضعیت اسکرول کردن هست که باهاش کاری نداریم و یکی دیگه onScroll هست که ما با اون سرو کار داریم .
داخل متد onScroll شما یه سری کد میبینید که نوشتم که به ترتیب توضیح میدم :
int lastItem = firstVisibleItem + visibleItemCount;
این قسمت از کد دقیقا آخرین آیتمی که کاربر مشاهده کرده رو به ما موقعیتش رو میده .
if (adapter.getCount() >= 10 && lastItem > adapter.getCount() - 3)
این کد مربوط به بررسی وضعیت آیتم ها هست که اگر شرط برقرار باشه میتونه درخواست جدید رو به سرور بفرسته
adapter.getCount() >= 10
ینی اگر تعداد آیتم های دریافت شده بزرگتر و مساوی 10 بود که همونطور که گفتیم این 10 هم همون 10 هست که داخل سرور وجود داشت .
lastItem > adapter.getCount() - 3
این کد هم به این معنی هست که اگر آخرین آیتمی که کاربر داره میبینه بزرگ تر از تعداد کل آیتم های موجود -3 بود شرط برقرار باشه .
نکته ای که هست -3 برای این هست که کاربر قبل از اینکه به آخر لیست برسه یعنی سومین آیتم از آخر رو که دید لیست جدید رو بگیره که کاربر معطل نشه .
boolean isLoading = false;
این هم برای اینه که اگر داشت لیست لود میکرد دوباره لیست جدیدی لد نکنه .
if(readNetworkStatus(context))
این کد هم که تقریبا باهاش آشنایی دارید برای این هست که اگر اینترنت وصل هست لیست جدید رو بگیره .
همونطور که گفتم به صورت کامل میخوام آموزش بدم که کسی سر یه باگ مشکل بر نخوره .
if(lastItem > lastItemPosition){
خُب از این کد هم فقط lastItemPosition نا مفهمومه که توضیح میدم این یه متغیر عددی هست که ما آخرین آیتمی که دیده شده رو ذخیره میکنیم این درواقع شاید بگید به چه دردی میخوره ما اون بالا داریمش که ، این در حالت عادی 0 هست و بعد از دریافت هر بار لیست جدید مقدارش تغیر میکنه ببینید شما در lastItem آخرین آیتمی که کاربر دیده رو میتونید ذخیره کنید ولی اگر کاربر دوباره برگرده بالا و بیاد پایین مجددا لیس ت میخواد آپدیت بشه و با استفاده از این متغیر ما جلو این کار رو میگیریم .
شاید یکم گنگ باشه برای اینکه متوجه بشید میتونید از این کد استفاده نکنید تا باگش رو خودتون ببینید :)
lastItemPosition = adapter.getCount(); receivedData(); } } isLoading = true;
در آخر هم مقدار آخرین آیتم مشاهده شده رو برابر تعداد کل آیتم ها میکنیم (همون داستان باگه) و در receivedData درخواست دریافت اطلاعات جدید رو میدیم و پایین تر هم میگیم داریم لیست رو لود میکنیم .
من سعی کردم خیلی ساده بگم شما میتونید بعضی از قسمت های کد رو هم یکجا بنویسید (شرط ها منظورمه) ولی من برای نمایش بهتر به شما به این شکل نوشتم .
فقط یک نکته اساسی میمونه شما باید part رو برای سرور بفرستید برای اینکار باید یه متغیر از جنس عدد بسازید که اولین بار مقدارش 0 هست و هر بار که ما درخواست به سرور میزنیم اینم براش میفرستیم و زمانی که لیست جدید دریافت شد یدونه به part اضافی میکنیم که این کار برای گرفتن قسمت بعدی لیست انجام میشه.
امیدوارم بتونید از این آموزش بهره کامل رو ببرید .
موفق باشید :)









در صورتیکه شما یه لیست ویو داشته باشید و بخواین به آیتم هاش دسترسی پیدا کنید مجبورید آیتم های هدر و فوتر رو از مقدار پوزیشن کلی کم کنید برای اینکار از کد زیر استفاده کنید :
int pos = position - listView.getHeaderViewsCount();

سلام من این کد رو زدم ولی وقتی اسکرول میکنم مقدار part هر بار زیاد و زیادتر میشه ! به طوری که لیستمو یک کوچولو هم که اسکرول میکنم این تابع جواب نمیده : کد رو میزارم ببین محمد حسین جان :
لازم به ذکره که اون showloadingDialog() یک تابعی که داخلش از یک متغیر async برای انحانم عملیات ارسال به سرور استفاده شده است ... نود درصد راهو رفتم ! ده درصد دیگه رو کمک کننن همه به خصوص محمد عزیز مرصی
1- اگه کسی اسکرول رو بده بالا جی !؟ این تابع فقط به part اضافه میکنه و تکه های قبلی رو از دیتابیس نمیخونه که !
2- من تا میام اسکرول کنم یک کوچولو که اسکرول میکنم میپره مقدار بعدی رو لود میکنه ! اصلا نمیزاره من برسم ... لازم به ذکره که من الیست ویوم وزنش رو 1 قرار دادم از نظر ارتفاع چون زیرش یک imageview خورده ... چکا کنممممممممممم خدااااااااا
lstContent.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView arg0, int arg1) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
int lastItemPosition = 0;
int lastItem = firstVisibleItem + visibleItemCount;
if (G.adapter.getCount() >= 10 && lastItem > G.adapter.getCount() - 3) {
boolean isLoading = false;
if ( !isLoading) {
if (lastItem > lastItemPosition) {
lastItemPosition = G.adapter.getCount();
Log.i(G.LOG_TAG, "hooooooooooooo");
showLoadingDialog();
//execute((Void[]) null);
}
isLoading = true;
}
}
}
});
اینم کدعای ارسال به سرور :
private void showLoadingDialog() {
AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
ProgressDialog dialog = new ProgressDialog(ActivityMain.this);
@Override
protected void onPreExecute() {
super.onPreExecute();
// lstContent = (ListView) findViewById(R.id.LstContent);
// G.adapter = new AdapterNote(G.notes);
// lstContent.setAdapter(G.adapter);
//lstContent.setOnScrollListener(new OnScrollListener
dialog.setMessage("درحال بارگزاری");
dialog.show();
}
@Override
protected Void doInBackground(Void... arg0) {
// if (G.notes.size() < 1) {
params.add(new BasicNameValuePair("part", "" + part));
params.add(new BasicNameValuePair("user_id", G.cellcode));
Commands.read(params, prgListener);
// }
return null;
}
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
if (G.getJsonString != null) {
try {
G.notes.clear();
JSONArray jsonnews = new JSONArray(G.getJsonString);
for (int j = 0; j < jsonnews.length(); j++) {
JSONObject object = jsonnews.getJSONObject(j);
StructNews note = new StructNews();
note.id = object.getInt("news_id");
note.pagenumber = object.getInt("page_number");
note.Title = object.getString("news_title");
note.description = object.getString("news_desc");
note.isfav = object.getInt("is_fav");
Log.i(G.LOG_TAG, "sss " + object.getString("news_title"));
G.notes.add(note);
}
G.adapter.notifyDataSetChanged();
part += 1;
Log.i(G.LOG_TAG, "part : " + part);
}
catch (JSONException e) {
e.printStackTrace();
}
}
}
};
task.execute((Void) null);
}

با تشکر از زحمات دوست عزیزمون محمد حسین جان :)
اگر دوستانی مثله من داخل لیست ویو از Header یا Footer استفاده میکنن، همینطور که محمد حسین اشاره کردن باید موقعیت (Postion) هدر و فوتر رو از مقدار کل موقعیت ها رو کم کنید.
و همچنین میشه با در نظر گرفتن مقدار کل آیتم ها، این مشکل رو برطرف کرد.
برای پیاده سازی این سیستم، به این شکل عمل میکنیم:
//مقادیر های اولیه رو تعریف میکنیم
private int visibleThreshold = 5; //که قراره هر صفحه 5 آیتم داشته باشیم private int currentPage = 0; private int previousTotal = 0; private boolean loading = true;
و سپس در رویداد setOnScrollListener قرار میدهیم:
listView.setOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView arg0, int arg1) {} @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (loading) { if (totalItemCount > previousTotal) { loading = false; previousTotal = totalItemCount; currentPage++; } } if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) { new RunMyNewTask().execute(currentPage + 1); //که با هر بار اجرا، مقدار صفحه فعلی یکی اضافه میشه loading = true; } } });
اومیدوارم مفید واقع بشه :)

احمد جان اسدی من این کدی که شما دادی رو نوشتم به این صورت ولی در رویداد اسکرول اتفاقی نمیفته هرجی بالا پایینش میکنم لود نمیکنه پارت دوم رو از سرور یکبار نگاه کنین به کدم لطفا part همون بازه ای که از دیتا رو از سرور میخونم و jsondownloader هم که دانلود کننده با استفاده از سیستم asynce هستش
lstContent.setOnScrollListener(new OnScrollListener() {
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
part++;
}
}
if ( !loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
new JsonDownloader().execute();
loading = true;
}
}

سلام دوستان
اگر برای اولین قراره این رو تست کنید پیشنهاد میکنم برای فهمیدن قضیه کد های زیر رو تست گنید < به راحتی میتونید لیست ویو رو هر موقع که نیاز داشتید آپدیت کنید <
lstContent.setOnScrollListener(new OnScrollListener() {
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
Log.i("firstVisibleItem", "" + firstVisibleItem);
Log.i("visibleItemCount", "" + visibleItemCount);
Log.i("totalItemCount", "" + totalItemCount);
int lastItem = firstVisibleItem + visibleItemCount;
if (lastItem > adapter.getCount() - 5) {
Log.e("LOG", "Need Update");
for (int i = 0; i < 5; i++) {
StructTask task = new StructTask();
task.desc = G.tasks.get(i).desc + i; //random data
G.tasks.add(task);
adapter = new AdapterNote(G.tasks);
lstContent.setAdapter(adapter);
lstContent.setSelection(firstVisibleItem + 1); // میتونید + 1 رو بردارید
}
Log.e("LOG", "New Data");
Toast.makeText(getApplicationContext(), "New Data", 250).show();
}
}
@Override
public void onScrollStateChanged(AbsListView arg0, int arg1) {
//Log.i("Header", "" + lstContent.getHeaderViewsCount());
//Log.i("adapter.Count", "" + adapter.getCount());
}
});

سلام و با سپاس بابت آموزشی که قرار دادید
ولی ایکاش یک فیلم آموزشی در این زمینه تهیه میکردید و در اختیار اعضا قرار میدادید
البته از وقتی که میذارید و جواب تک تک سوالها را میدین تشکر میکنم
با سپاس

سلام ببخشید یه سوال داشتم درمورد این آموزش
من تو پروژم تقریبا از همین روش استفاده کردم به طوری که وقتی تعداد ایتم های داخل گرید ویو به مقدار مشخصی رسید میام دوباره داده هارو از سرور دریافت میکنم به صورت paging
الان مشکلم اینجاست که تو پروژه هم دارم از ASYNKTASK استفاده میکنم و وقتی که دوباره میخام متد رو فراخونی کنم برای مابقی داده ها از سرور از همین TASK ایراد میگیره خواستم بگم میشه همزمان از هردوتاش استفاده کرد :
هم بحث PAGING (که خودم کدهاشو رو نوشتم برای فراخوانی از سرور )
هم استفاده از ASYNKTASK برای عدم کند شدن برنامه در حین گرفتن اطلاعات از سرور
پاسخگویی و مشاهده پاسخ های این سوال تنها برای اعضای ویژه سایت امکان پذیر است .
چنانچه تمایل دارید به همه بخش ها دسترسی داشته باشید میتوانید از این بخش لایسنس این آموزش را خریداری نمایید .