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

Androidで上拉してさらに読み込むListView(PulmListView)の実現

考え方

今日は上拉読み込み機能を持つListViewを実現する方法をお伝えします。GitHubのリンク:PulmListView, fork && starしていただければ幸いです。

まず、考え方を整理しましょう。上拉読み込み機能を持つListViewを実現するためには、以下のような機能を実装する必要があります:
1.カスタムのListViewで、現在が最下部にいるかどうかを判断できるListViewを提供します。
 2.ListViewの読み込み中のUI表示を行うためのカスタムのFooterViewを提供します。
 3.FooterViewとListViewを関連付け、読み込み時機の判定、FooterViewの表示と非表示を行います。
 4.ユーザーが実際にデータを読み込む機能を実現するための、データ読み込みのインターフェースを提供します。
 5.追加データをユーザーに追加し、関連する状態フラグとUI表示を更新するための、データ読み込み完了の回调メソッドを提供します。 

上記の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の3つの構造関数を実装するだけでなく、ListViewが最後の要素にスライドしているかどうかを判断できるようにする必要があります。

最後の要素にスライドしているかどうかを判断するには、ListViewにOnScrollListenerを設定することができます。以下のようになります:

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を実現できます。読み込むUIレイアウトです。例えば、ProgressBarと一行のテキストを表示できます。具体的なコードは以下の通りです:

/**
 * 読み込むViewのレイアウトを提供し、カスタマイズ可能。
 */
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の関連付け)

第一に、ListViewの中でFooterViewを変数で保存し、コンストラクタでインスタンスを作成する必要があります.

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

第二に、FooterViewの表示と非表示を制御する必要があります. FooterViewの表示と非表示の時機を考えてみましょう:
•表示される時機: ListViewが一番下にあり、まだ読み込むデータがある.
•隠された時機: ListViewが「もっと読み込む」の操作を完了した. 

現在データをロードする必要があるかどうかを判断するために、データのロードが終了したかどうかを示すboolean変数mIsPageFinishedを定義する必要があります。
一度にデータのロードプロセスが1回だけ行われるようにするために、現在データのロード状態かどうかを示す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<63;> 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を作成し、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();
  }
}

なぜaddMoreItemsメソッドにisFirstLoad変数を追加する必要があるのでしょうか?

これは、上拉読み込みが通常ドローダウンリフレッシュと組み合わせて使用されるためです。ドローダウンリフレッシュの過程では、ListViewのデータコレクションをclearし、addAllに巻き込まれます。isFirstLoadパラメータがない場合、ユーザーがドローダウンリフレッシュでListViewのデータコレクションを更新するには、以下のように2ステップに分ける必要があります:
1.removeAllItemsを実行し、notifyDataSetChangedを実行します。
 2.addMoreItemsを実行し、notifyDataSetChangedを実行します。 

同じ時間に連続して2回notifyDataSetChanged()を実行すると、スクリーンがフラッシュするため、ここではisFirstLoadメソッドを提案しました。データの読み込みが初めての場合、まずすべてのデータをclearし、addAllを実行し、最後にnotifyを実行します。

カスタムのadapterがあると、データの読み込みが完了したコールバック関数を書くことができます:

/**
 * データの読み込みが完了した後のListViewのコールバックメソッド。
 *
 * @param isPageFinished ページ読み込みが終了したかどうか
 * @param newItems ページごとの読み込みデータ
 * @param isFirstLoad データの最初の読み込みかどうか(ドローコンテンツフレームの使用に対応し、ページの閃きを避けるために使用)
 */
public void onFinishLoading(boolean isPageFinished, List<63;> 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が追加された後は、listview.getAdapter()を通じてカスタムのadapterを取得することができません。以下の手順に従ってください:

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

参照
 1.PagingListView

これで本文のすべてが終わり、皆様の学習に役立つことを願っています。また、呐喊チュートリアルを多くのサポートをお願いします。

声明:本文の内容はインターネットから取得しており、著作権者に帰属します。インターネットユーザーが自発的に貢献し、自己でアップロードしたものであり、当サイトは所有権を持ちません。また、人工編集は行われていません。著作権侵害を疑う内容があれば、メールでnotice#wまでお知らせください。3codebox.com(メール送信時、#を@に変更してください)で通報し、関連証拠を提供してください。一旦確認がとりあえず、当サイトは即座に侵害疑いのコンテンツを削除します。

おすすめ