android 5.0 新出了下拉刷新控件 SwipeRefreshLayout,现在被更多公司使用。
但是控件不支持上拉加载,现在也有第三方框架支持,自己有兴趣写了一点测试代码支持SwipeRefreshLayout 嵌套 listView 第一页手动点击加载,其他页滑动到底自动加载数据。
首先要获取到 listView (目前支持listView 后续可能会增加 多其他控件的支持)
int childCount = getChildCount(); log(childCount); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); if (childView instanceof ListView) { listView = (ListView) childView; break; } }
初始化 footerView 并设置点击事件的回调
if (footer == null) { int footerViewsCount = listView.getFooterViewsCount(); log("footerViewsCount = " + footerViewsCount); if (footerViewsCount == 0) { LayoutInflater inflater = LayoutInflater.from(getContext()); footer = inflater.inflate(R.layout.list_footer, null); listView.addFooterView(footer); footerProgress = footer.findViewById(R.id.footer_progress_bar); footerText = footer.findViewById(R.id.footer_content); footerText.setText(R.string.click_load_update); footer.setVisibility(View.GONE); } footerText.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { if (type == 2) { loadDataCallBack(); } } }); }
重点就是对 listView 的滑动监听
listView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView absListView, int i) { } /** * 滑动监听 * @param absListView listView 的父类 * @param firstVisibleItem 屏幕可见第一个 item 位置(不一定是 0) * @param visibleItemCount 屏幕可见的最后一个 item 与 firstVisibleItem 相隔多少个 item * @param totalCount listView 的 item 总数包括添加的 headView、footerView */ @Override public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalCount) { // log("onScroll firstVisibleItem = " + firstVisibleItem + "visibleItemCount = " + visibleItemCount + "totalCount = " + totalCount); //数据不在加载中 int countChange = totalCount - historyTotalCount; if (!loadDataNow && countChange != 0) { //如果是第一页刷新后加载数据回调加载数据接口 if (removeFooter && countChange == 1) { removeFooter = false; return; } //有新数据加载 log("countChange= " + countChange); if (countChange > 0 && firstAutoLoadData) {//获取到更多数据 if (countChange >= onePageTotalCount) {//还有更过数据 type = 0; } else if (countChange < onePageTotalCount) {//没有更多数据 type = 1; } } else {//获取第一页数据 type = 2; } historyTotalCount = totalCount; //滑动到最后一行 if (firstVisibleItem + visibleItemCount > totalCount - 1 && !loadDataNow) { addFooter(type); } else if (footer != null) { footer.setVisibility(View.GONE); } } } });
以下是自定义全部代码
public class LoadSwipeRefreshLayout extends SwipeRefreshLayout { private final String TAG = "tag_" + LoadSwipeRefreshLayout.this.getClass().getSimpleName(); private ListView listView = null; private View footer; private TextView footerText; private ProgressBar footerProgress; /** * 记录是否移除 footerView 防止上拉刷新时第一页手动加载失效 */ private boolean removeFooter = false; //是否正在处理加载数据回调,正在加载数据时不再回调加载数据 private boolean loadDataNow = false; /** * 第二页是否自动加载更多数据 * firstAutoLoadData true 自动加载 false 点击加载 */ private boolean firstAutoLoadData = true; /** * 底部 footerView 显示状态 * 0 正在加载 1 没有更多数据 2 点击加载更多数据 */ private int type = 0; //每页数据条数 private int onePageTotalCount = 9; //历史条数 private int historyTotalCount = 0; //状态回调 private IRefreshCallBack refreshCallBack; public LoadSwipeRefreshLayout(Context context) { this(context, null); } public LoadSwipeRefreshLayout(Context context, AttributeSet attrs) { super(context, attrs); } public void setFirstAutoLoadData(boolean firstAutoLoadData) { this.firstAutoLoadData = firstAutoLoadData; } public void setOnePageTotalCount(int onePageTotalCount) { this.onePageTotalCount = onePageTotalCount; } public void setRefreshCallBack(IRefreshCallBack refreshCallBack) { this.refreshCallBack = refreshCallBack; } /** * 初始化获取到 listView 控件 */ private void init() { log("init()"); int childCount = getChildCount(); log(childCount); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); if (childView instanceof ListView) { listView = (ListView) childView; break; } } initFooter(); if (listView != null) { listView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView absListView, int i) { } /** * 滑动监听 * @param absListView listView 的父类 * @param firstVisibleItem 屏幕可见第一个 item 位置(不一定是 0) * @param visibleItemCount 屏幕可见的最后一个 item 与 firstVisibleItem 相隔多少个 item * @param totalCount listView 的 item 总数包括添加的 headView、footerView */ @Override public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalCount) { // log("onScroll firstVisibleItem = " + firstVisibleItem + "visibleItemCount = " + visibleItemCount + "totalCount = " + totalCount); //数据不在加载中 int countChange = totalCount - historyTotalCount; if (!loadDataNow && countChange != 0) { //如果是第一页刷新后加载数据回调加载数据接口 if (removeFooter && countChange == 1) { removeFooter = false; return; } //有新数据加载 log("countChange= " + countChange); if (countChange > 0 && firstAutoLoadData) {//获取到更多数据 if (countChange >= onePageTotalCount) {//还有更过数据 type = 0; } else if (countChange < onePageTotalCount) {//没有更多数据 type = 1; } } else {//获取第一页数据 type = 2; } historyTotalCount = totalCount; //滑动到最后一行 if (firstVisibleItem + visibleItemCount > totalCount - 1 && !loadDataNow) { addFooter(type); } else if (footer != null) { footer.setVisibility(View.GONE); } } } }); } } /** * 初始化 footerView */ private boolean initFooter() { if (footer == null) { int footerViewsCount = listView.getFooterViewsCount(); log("footerViewsCount = " + footerViewsCount); if (footerViewsCount == 0) { LayoutInflater inflater = LayoutInflater.from(getContext()); footer = inflater.inflate(R.layout.list_footer, null); listView.addFooterView(footer); footerProgress = footer.findViewById(R.id.footer_progress_bar); footerText = footer.findViewById(R.id.footer_content); footerText.setText(R.string.click_load_update); footer.setVisibility(View.GONE); } footerText.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { if (type == 2) { loadDataCallBack(); } } }); } return footer != null; } /** * 显示底部footerView * * @param type 0 立即加载 1 没有更多数据 2 点击加载更多数据 */ private void addFooter(@IntRange(from = 0, to = 2) final int type) { log("addFooter = type = " + type); loadDataNow = true; firstAutoLoadData = true; if (initFooter()) footer.setVisibility(VISIBLE); if (listView != null) { switch (type) { case 1: case 2: footerText.setText(type == 1 ? R.string.no_more_url_date : R.string.click_load_update); footerProgress.setVisibility(View.GONE); break; } if (type != 2) { loadDataCallBack(); } } } /** * 没有更多数据 */ public void noMoreData() { addFooter(1); } /** * 加载数据回调 */ private void loadDataCallBack() { if (refreshCallBack != null) { footerText.setText(R.string.load_now); footerProgress.setVisibility(View.VISIBLE); refreshCallBack.refreshCallBack(LoadSwipeRefreshLayout.this); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); //onLayout 方法会被多次调用,但初始化只要执行一次就好 if (listView == null && footer == null) init(); } /** * 隐藏底部 footerView */ public void hideFooterView() { if (footer != null) footer.setVisibility(GONE); loadDataNow = false;//释放加载数据的状态 } /** * 上拉刷新移除 footerView 否则有时候会报数组越界异常 */ public void removeFooterView() { listView.removeFooterView(footer); footer = null; removeFooter = true; } /** * 打印日志 * * @param s 打印内容 */ private void log(String s) { Log.e(TAG, s); } private void log(int s) { Log.e(TAG, String.valueOf(s)); } }
activity 调用代码(使用的 google 推荐的官方编写 android 的语言 kotlin )
class MainActivity : AppCompatActivity(),Thread.UncaughtExceptionHandler { override fun uncaughtException(p0: Thread?, p1: Throwable?) { if(listData != null){ Log.e("tag_MainActivity","listData.size= ${listData.size}") } finish() } var listData = ArrayList<String>() var page = 1 var adapter : OnTextAdapter? = null var handler : Handler? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Log.e("tag_fun", "onCreate") setContentView(R.layout.activity_main) content.text = "你好" handler = Handler(mainLooper, Handler.Callback { Log.e("tag_system_time_handler", "load_data${System.currentTimeMillis()/1000}thread.name= ${Thread.currentThread().name}") swipeRefresh.isRefreshing = false swipeRefresh.hideFooterView() if(adapter == null){ adapter = OnTextAdapter(context = this@MainActivity, listDate = listData) list.adapter = adapter }else{ adapter?.notifyDataSetChanged() Log.e("tag_MainActivity","adapter.notifyDataSetChanged()") } false }) swipeRefresh.setFirstAutoLoadData(false) swipeRefresh.isRefreshing = true addData(page) swipeRefresh.setRefreshCallBack { addData(++ page) } swipeRefresh.setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener { page = 1 listData.removeAll(listData) swipeRefresh.removeFooterView() addData(page) }) } private fun addData(page : Int) { Log.e("tag_addData", "addData(page= $page)") Thread { Log.e("tag_addData", "Thread.name=${Thread.currentThread().name}") for (i in (page - 1) * 10 + 1 .. page * 10){ listData.add("text_$i") } handler?.sendEmptyMessageDelayed(1, 1 * 1000) }.start() } }