欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android实现上拉加载更多ListView(PulmListView)

程序员文章站 2024-03-06 13:46:56
思路 今天带大家实现一个上拉加载更多的listview.github传送门:pulmlistview, 欢迎大家fork&&star. 先带大家理一下思路, 如果我们要...

思路

今天带大家实现一个上拉加载更多的listview.github传送门:pulmlistview, 欢迎大家fork&&star.

先带大家理一下思路, 如果我们要实现一个上拉加载更多的listview, 我们需要实现的功能包括:
1.一个自定义的listview, 并且该listview能够判断当前是否已经处于最底部.
 2.一个自定义的footerview, 用于在listview加载更多的过程中进行ui展示.
 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的三个构造函数还不够, 我们需要listview能够判断当前的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, 表示数据加载是否结束.
为了保证同一时间只进行一次数据加载过程, 因此我们还需要定义一个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, 在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的数据集合就必须分为两步:
1.removeallitems并进行notifydatasetchanged.
 2.addmoreitems并进行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之后, 我们无法通过listview.getadapter拿到我们自定义的adapter, 必须按照如下步骤:

复制代码 代码如下:
pulmbaseadapter adapter = (pulmbaseadapter) ((headerviewlistadapter) getadapter()).getwrappedadapter();

参考
 1.paginglistview

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。