一般其他组件与ListView嵌合在一起滚动的方案有如下几种:
- 1.整个页面变为一个ListView,其他组件(如顶部)成为ListView的一个Item或者Header;
- 2.使用ScrollView嵌套ListView;
开发场景
某一app在1.0版本ActivityA页面已经包裹了一些内容组件,之后到了2.0版本,需要在当前页面下加一个可以滑动的ListView。这个时候当然首先想到的是,使用ScrollView嵌套ListView来实现。但在ScrollView嵌套ListView一般会有些问题出现,比如无法滑动、ListView只能显示一行等问题。本文将对在已有的页面中添加一个ListView进行分页加载的处理办法来进行阐述。
效果示意图
事例结构图
案例解析
根据示意图可以大概推测出实现方案:在一个ScrollView中包裹着ListView,当ListView加载完一组数据后再分页加载另外一组数据。
那么问题来了,为啥将ListView放进ScrollView后却只能显示一行呢?这个就需要在ListView中做些处理,自定义ListView代码如下:
public class ImbeddedListView extends ListView {
private View footer;// 底部布局
private boolean isLoading;// 判断是否正在加载中
public ImbeddedListView(Context context) {
super(context);
initView(context);
}
public ImbeddedListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public ImbeddedListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView(context);
}
private void initView(Context context) {
LayoutInflater inflater = LayoutInflater.from(context);
footer = inflater.inflate(R.layout.listview_footer_view, null);
footer.findViewById(R.id.loading_layout).setVisibility(View.GONE);
this.addFooterView(footer);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);//Measure specification mode: The child can be as large as it wants up to the specified size.——>处理ScrollView嵌套ListView只显示一行的问题,此处让ListView所占的大小与要求的大小一样大
super.onMeasure(widthMeasureSpec, expandSpec);
}
/**
* 加载完成,1.设置标志;2.隐藏footer
*/
public void loadComplete() {
if (footer == null) {
return;
}
isLoading = false;
footer.findViewById(R.id.loading_layout).setVisibility(View.GONE);
}
/**
* 开始加载,1.设置标志;2.显示footer
*/
public void startLoading() {
if (footer == null) {
return;
}
isLoading = true;
footer.findViewById(R.id.loading_layout).setVisibility(VISIBLE);
}
public boolean isLoading() {
return isLoading;
}
}
- 要点1:在onMeasure方法中将ListView所占的大小设置为最大。至于为什么是这个值,可以参考:详解嵌套ListView、ScrollView布局显示不全的问题
-
要点2:分页加载的显示、隐藏的时机。
- a.添加一个footer的布局,通过控制显示与隐藏来表示加载中与否的状态;
- b.提供公开的startLoading与loadComplete方法,供外部在适当时机时调用显示加载与否的状态。
所以,问题来了,什么时候是适当时机?
- 1.开始网络请求时(也就是页面滑动到最底部且还有数据需要加载时),就调用startLoading显示加载中的状态;
- 2.当网络请求加载完毕时,就调用loadComplete隐藏加载中的状态;
额,所以问题又来了,知道数据是否需要加载这个好判断(返回的接口一般会有个数据项的总数,对比下当前已经分页加载的数目就可以知道),但如何知道页面滑动至最底部了呢?
这个时候,就是需要自定义ScrollView来处理事件了:
public class PageListScrollView extends ScrollView {
private OnScrollToBottomListener mOnScrollToBottomListener;
public PageListScrollView(Context context) {
super(context);
}
public PageListScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PageListScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//滚动到底部时,clampedY变为true,此时将回调将状态传出去
@Override
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
if (scrollY != 0 && mOnScrollToBottomListener != null) {
mOnScrollToBottomListener.onScrollBottomListener(clampedY);
}
}
public void setOnScrollToBottomListener(OnScrollToBottomListener listener) {
mOnScrollToBottomListener = listener;
}
public interface OnScrollToBottomListener {
void onScrollBottomListener(boolean isBottom);
}
}
从以上可以看出,通过在方法onOverScrolled方法中获取参数clampedY来判断当前ScrollView是否滑动至底部,然后借用自定义的接口将该状态传递出去(之后用于处理网络数据请求等等)。
主界面的代码
public class MainActivity extends AppCompatActivity implements PageListScrollView.OnScrollToBottomListener {
private PageListScrollView scrollView;
private ImbeddedListView commentLv;
private CommentListViewAdapter commentListViewAdapter;
private ArrayList<CommentEntity> commentEntityList;
private int pagesize = 10;
private int currentpage = 0;
private boolean judgeCanLoadMore = true;
private int totalCount = 50;//设置本次加载的数据的总数
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Fresco.initialize(this);
setContentView(R.layout.activity_main);
initViews();
}
private void initViews() {
scrollView = (PageListScrollView) findViewById(R.id.scrollView);
commentLv = (ImbeddedListView) findViewById(R.id.commentLv);
commentLv.setFocusable(false);
scrollView.setOnScrollToBottomListener(this);
initData();
}
private void initData() {
initAdapter(getCommentList());
}
private void initAdapter(ArrayList<CommentEntity> dataList) {
if (commentListViewAdapter == null) {
if (commentEntityList == null) {
commentEntityList = new ArrayList<>();
}
commentEntityList.addAll(dataList);
commentListViewAdapter = new CommentListViewAdapter(this, commentEntityList);
commentLv.setAdapter(commentListViewAdapter);
return;
}
if (dataList == null || dataList.size() == 0) {
return;
}
commentEntityList.addAll(dataList);
if (commentListViewAdapter != null) {
commentListViewAdapter.notifyDataSetChanged();
}
}
private void initLoadMoreTagOp() {
if (totalCount == 0 || totalCount <= currentpage * pagesize) {//当前获取的数目大于等于总共的数目时表示数据加载完毕,禁止滑动
judgeCanLoadMore = false;
commentLv.loadComplete();
return;
}
}
//当ScrollView滑动至底部后,会回调此方法
@Override
public void onScrollBottomListener(boolean isBottom) {
//模拟进行数据的分页加载
if (judgeCanLoadMore && isBottom && !commentLv.isLoading()) {
commentLv.startLoading();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
initAdapter(getCommentList());
commentLv.loadComplete();
initLoadMoreTagOp();
}
}, 2000);//模拟网络请求,延缓2秒钟
}
}
/**
* 模拟网络请求后返回的数据
* @return
*/
private ArrayList<CommentEntity> getCommentList() {
int currentpageCount = currentpage * pagesize;
if (currentpageCount >= totalCount) {
return null;
}
ArrayList<CommentEntity> list = new ArrayList<>();
for (int i = currentpageCount + 1; i <= pagesize + currentpageCount; i++) {
CommentEntity commentEntity = new CommentEntity(i + "", i + "位", "", "这是内容" + i, "2017年7月12日19:20", "2017年7月12日19:20");
list.add(commentEntity);
}
currentpage++;
return list;
}
}
最后,GitHub地址>>