1. 程式人生 > >Android ScrollView巢狀ListView正常分頁載入顯示解決方案

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;
    }
}