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()
}
}