English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

تحقيق تحميل المزيد عند سحب ListView (PulmListView) في Android

الفكرة

اليوم سأقوم بتحقيق ListView للتحميل السحبي نحو الأعلى. رابط GitHub:PulmListView, مرحبًا بك في fork && star.

دعونا نبدأ في تنظيم الأفكار، إذا كنا نريد تحقيق ListView للتحميل السحبي نحو الأعلى، نحتاج إلى تنفيذ وظائف تشمل:
1. ListView مخصص، ويمكن لهذا ListView تحديد ما إذا كان يصل إلى الأسفل.
 2. FooterView مخصص، لعرض واجهة المستخدم أثناء عملية تحميل ListView.
 3. ربط FooterView و ListView، بما في ذلك تحديد وقت التحميل، عرض وتمثيل FooterView.
 4. توفير واجهة التحميل المزيد من البيانات، لسهولة تنفيذ وظيفة التحميل الفعلية للمستخدم.
 5. توفير طريقة استدعاء مخصصة لإنهاء التحميل، لاستخدام بيانات المستخدم الجديدة وتحديث العلامات الحالة والعرض الوجهي. 

لتحليل كل من هذه الخمس وظائف، سنقوم بتحليل طرق التنفيذ المخصصة لها.

الميزة 1(ListView مخصص)

يمكننا من خلال توريث ListView، تحقيق PulmListView مخصص.

public class PulmListView extends ListView {
  public PulmListView(Context context) {
    this(context, null);
  }
  public PulmListView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }
  public PulmListView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    // التحميل
    init();
  }
}

ليس من الضروري فقط تنفيذ ثلاثة بناء ListView، نحتاج إلى أن يكون ListView قادرًا على تحديد ما إذا كان يتم التمرير إلى العنصر الأخير.

لتحديد ما إذا كان يتم التمرير إلى العنصر الأخير، يمكننا إعداد OnScrollListener لـ ListView. الكود التالي هو:

private void init() {
  super.setOnScrollListener(new OnScrollListener() {
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
      // تنفيذ الاستدعاء المخصصة لـ OnScrollListener
      if (mUserOnScrollListener != null) {
        mUserOnScrollListener.onScrollStateChanged(view, scrollState);
      }
    }
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
      // تنفيذ الاستدعاء المخصصة لـ OnScrollListener
      if (mUserOnScrollListener != null) {
        mUserOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
      }
      // firstVisibleItem هو موقع العنصر الأول الذي يمكن عرضه على الشاشة الحالية
      // visibleItemCount هو عدد العناصر التي يمكن عرضها على الشاشة الحالية
      // totalItemCount هو إجمالي عدد العناصر في ListView
      int lastVisibleItem = firstVisibleItem + visibleItemCount;
      if (!mIsLoading && !mIsPageFinished && lastVisibleItem == totalItemCount) {
        if (mOnPullUpLoadMoreListener != null) {
          mIsLoading = true;
          mOnPullUpLoadMoreListener.onPullUpLoadMore();
        }
      }
    }
  });
}

من خلال (firstVisibleItem + visibleItemCount) يمكننا الحصول على عدد العناصر التي يتم عرضها على الشاشة حاليًا، إذا كان عدد العناصر التي يتم عرضها يساوي إجمالي عدد العناصر في ListView، فيمكننا اعتبار أن ListView قد وصل إلى أسفل.

功能2(نموذج FooterView مخصص)

في هذا يمكننا تنفيذ FooterView بسيط، أي نموذج واجهة المستخدم لتحميل المزيد. على سبيل المثال، يمكننا عرض ProgressBar وخط نصي، والكود المحدد هو:

/**
 * نموذج عرض لتحميل المزيد، يمكن تخصيصه.
 */
public class LoadMoreView extends LinearLayout {
  public LoadMoreView(Context context) {
    this(context, null);
  }
  public LoadMoreView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }
  public LoadMoreView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
  }
  private void init() {
    LayoutInflater.from(getContext()).inflate(R.layout.lv_load_more, this);
  }
}

ملف التخطيط:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/id_load_more_layout"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:orientation="horizontal"
  android:gravity="center"
  android:layout_margin="@dimen/loading_view_margin_layout">
  <ProgressBar
    android:id="@+id/id_loading_progressbar"
    android:layout_width="@dimen/loading_view_progress_size"
    android:layout_height="@dimen/loading_view_progress_size"
    android:indeterminate="true"
    style="?android:progressBarStyleSmall"/>
  <TextView
    android:id="@+id/id_loading_label"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/page_loading"/>
</LinearLayout>

الوظيفة 3(مرتبط مع ListView و FooterView)

أولاً، نحتاج إلى حفظ FooterView في متغير في ListView، ونؤسس نموذجها في بناء الجهاز.

private View mLoadMoreView;
private void init() {
  mLoadMoreView = new LoadMoreView(getContext());
}

ثانيًا، نحتاج إلى التحكم في عرض وتخفيض FooterView. لنفكر في وقت عرض وتخفيض FooterView:
• وقت العرض: عندما يكون ListView في أسفل الصفحة ويجب تحميل المزيد من البيانات.
• وقت التخفيض: عند انتهاء ListView من عملية تحميل المزيد. 

للتحقق مما إذا كان هناك بيانات تحتاج إلى تحميل، لذلك نحتاج إلى تعريف متغير boolean mIsPageFinished، ليعبر عن ما إذا كانت عملية تحميل البيانات قد انتهت.
لضمان تنفيذ عملية تحميل البيانات مرة واحدة في نفس الوقت، لذلك نحتاج أيضًا إلى تعريف متغير boolean mIsLoading، ليعبر عن ما إذا كانت التحميلات قيد التنفيذ حاليًا.

أوضح وقت عرض وتخفيض FooterView، كما تم تعريف متغيرات التحكم في الحالة، مما يجعل الكود أكثر سهولة في التنفيذ.

عرض الفرصة:

private void init() {
  mIsLoading = false; // عند التشغيل الأول لا يكون هناك حالة تحميل
  mIsPageFinished = false; // عند التشغيل الأول افتراضيًا لا يزال هناك بيانات أكثر تحتاج إلى تحميل
  mLoadMoreView = new LoadMoreView(getContext()); // إنشاء FooterView
  super.setOnScrollListener(new OnScrollListener() {
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
      // تنفيذ الاستدعاء المخصصة لـ OnScrollListener
      if (mUserOnScrollListener != null) {
        mUserOnScrollListener.onScrollStateChanged(view, scrollState);
      }
    }
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
      // تنفيذ الاستدعاء المخصصة لـ OnScrollListener
      if (mUserOnScrollListener != null) {
        mUserOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
      }
      int lastVisibleItem = firstVisibleItem + visibleItemCount;
      // عند الوصول إلى نهاية ListView ووجود بيانات أكثر تحتاج إلى تحميل وليس هناك عملية تحميل قيد التنفيذ حاليًا، يتم تنفيذ عملية تحميل إضافي
      if (!mIsLoading && !mIsPageFinished && lastVisibleItem == totalItemCount) {
        if (mOnPullUpLoadMoreListener != null) {
          mIsLoading = true; // ضع حالة التحميل الإضافي قيد التنفيذ على true
          showLoadMoreView(); // عرض تصميم التحميل الإضافي
          mOnPullUpLoadMoreListener.onPullUpLoadMore(); // تنفيذ واجهة الاستدعاء المخصصة للتحميل الإضافي
        }
      }
    }
  });
}
private void showLoadMoreView() {
  // هنا يتم تعيين id للجذر لتحميل المزيد كـ id_load_more_layout، مما يسهل تخصيص نموذج تحميل المزيد من قبل المستخدم.
  if (findViewById(R.id.id_load_more_layout) == null) {
    addFooterView(mLoadMoreView);
  }
}

وقت التخفي:

/**
 * طريقة إرجاع ListView بعد إكمال تحميل المزيد
 *
 * @param isPageFinished هل انتهت صفحة التصفح
 * @param newItems    البيانات المحمولة بصفحة
 * @param isFirstLoad  هل يتم تحميل البيانات للمرة الأولى (لإعداد إطار تحديث السحب الأسفل، لتجنب ظهور صفحة الظل)
 */
public void onFinishLoading(boolean isPageFinished, List<?> newItems, boolean isFirstLoad) {
  mIsLoading = false; // علامة على أن لا يوجد حاليًا عملية تحميل المزيد قيد التنفيذ
  setIsPageFinished(isPageFinished); // تعيين علامة نهاية الصفحة وتنظيف FooterView
}
private void setIsPageFinished(boolean isPageFinished) {
  mIsPageFinished = isPageFinished;
  removeFooterView(mLoadMoreView);
}

الميزة 4(واجهة إعادة التوجيه إلى تحميل المزيد بالسحب لأعلى)

هذا بسيط نسبيًا، نعرف interface، مما يسهل إعادة التوجيه إلى إجراء تحميل المزيد الفعلي للمستخدم.

/**
 * واجهة الإشارة إلى تحميل المزيد بالسحب لأعلى
 */
public interface OnPullUpLoadMoreListener {
  void onPullUpLoadMore();
}
private OnPullUpLoadMoreListener mOnPullUpLoadMoreListener;
/**
 * تعيين واجهة الإشارة إلى تحميل المزيد بالسحب لأعلى.
 * @param l واجهة الإشارة إلى تحميل المزيد بالسحب لأعلى
 */
public void setOnPullUpLoadMoreListener(OnPullUpLoadMoreListener l) {
  this.mOnPullUpLoadMoreListener = l;
}

الميزة 5(الإشارة إلى إكمال تحميل المزيد)

للحفاظ على مجموعة البيانات في PulmListView، يجب تخصيص Adapter، حيث يتم استخدام List لتخزين مجموعة البيانات، وإدخال طرق الإضافة والإزالة.

Adapter مخصص:

/**
 * Adapter تعريفي.
 */
public abstract class PulmBaseAdapter<T> extends BaseAdapter {
  protected List<T> items;
  public PulmBaseAdapter() {
    this.items = new ArrayList<>();
  }
  public PulmBaseAdapter(List<T> items) {
    this.items = items;
  }
  public void addMoreItems(List<T> newItems, boolean isFirstLoad) {
    if (isFirstLoad) {
      this.items.clear();
    }
    this.items.addAll(newItems);
    notifyDataSetChanged();
  }
  public void removeAllItems() {
    this.items.clear();
    notifyDataSetChanged();
  }
}

لماذا يجب إضافة متغير isFirstLoad في دالة addMoreItems؟

لأن تحميل المزيد عادةً يجب أن يتم بشكل متزامن مع تحديث السحب الأسفل. وفي عملية تحديث السحب الأسفل، يتم牵ير مجموعة بيانات ListView لتحديثها باستخدام clear ثم addAll. إذا لم يكن هناك متغير isFirstLoad، فإن على المستخدم تحديث مجموعة بيانات ListView باستخدام خطوتين:
1. إزالة جميع العناصر وإرسال notifyDataSetChanged.
 2. إضافة المزيد من العناصر وإرسال notifyDataSetChanged. 

إذا تم إرسال notifyDataSetChanged متتالية في نفس الوقت، قد يؤدي إلى تعرض الشاشة لشاشة الظل، لذا قمنا بتقديم دالة isFirstLoad. عند تحميل البيانات للمرة الأولى، سيتم clear جميع البيانات أولاً، ثم addAll، وأخيرًا notify.

باستخدام adapter مخصص، يمكنك كتابة دالة إرجاع بعد إكمال تحميل المزيد:

/**
 * طريقة إرجاع ListView بعد إكمال تحميل المزيد
 *
 * @param isPageFinished هل انتهت صفحة التصفح
 * @param newItems    البيانات المحمولة بصفحة
 * @param isFirstLoad  هل يتم تحميل البيانات للمرة الأولى (لإعداد إطار تحديث السحب الأسفل، لتجنب ظهور صفحة الظل)
 */
public void onFinishLoading(boolean isPageFinished, List<?> newItems, boolean isFirstLoad) {
  mIsLoading = false;
  setIsPageFinished(isPageFinished);
  // إضافة بيانات التحديث
  if (newItems != null && newItems.size() > 0) {
    PulmBaseAdapter adapter = (PulmBaseAdapter) ((HeaderViewListAdapter) getAdapter()).getWrappedAdapter();
    adapter.addMoreItems(newItems, isFirstLoad);
  }
}

هنا يجب الانتباه، عند إضافة FooterView أو HeaderView، لا يمكننا الحصول على adapter المخصص لدينا من خلال listview.getAdapter، يجب اتباع الخطوات التالية:

PulmBaseAdapter adapter = (PulmBaseAdapter) ((HeaderViewListAdapter) getAdapter()).getWrappedAdapter();

مرجع
 1.PagingListView

هذا هو نهاية محتوى هذا المقال، آمل أن يساعدكم هذا في تعلمكم، وأتمنى أن تدعموا كتالوغ النداء.

بيان: محتوى هذا المقال تم جمعه من الإنترنت، يحق لصاحب الحقوق الحصول عليه، يتم جمع المحتوى من قبل المستخدمين عبر الإنترنت، ويستخدم هذا الموقع ليس لديه حقوق الملكية، لم يتم تعديل المحتوى يدويًا، ولا يتحمل هذا الموقع أي مسؤولية قانونية. إذا كنت قد وجدت محتوى يشتبه في انتهاك حقوق النسخ، فلا تتردد في إرسال بريد إلكتروني إلى: notice#oldtoolbag.com (عند إرسال البريد الإلكتروني، يرجى استبدال # بـ @) لإبلاغنا، وقدم الدليل المتعلق، إذا تم التحقق من ذلك، سيتم حذف المحتوى المزعوم فورًا.

أعجبك هذا