Android ScrollView巢狀ListView正常分頁載入顯示解決方案
一般其他元件與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;
}
}