1. 程式人生 > >ListView分頁載入,動態從網上拉取資料

ListView分頁載入,動態從網上拉取資料

最近做專案有個需求:

(1)從網上獲取分頁資料;

(2)在Android手機端顯示;

(3)載入的動畫和文字;

(4)資料超過40條時顯示滑動條等。

由於之前自己做的偏底層一點,所以這塊內容琢磨了蠻久,最後可以完美實現專案需求,內容見下面:

一、首先,需要有個佈局檔案:activity_story_category.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_below="@id/rl_title"
    android:background="#F0F3F7">

    <ListView
        android:id="@+id/lv_category_item"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginEnd="12dp"
        android:layout_marginStart="12dp"
        android:layout_marginTop="12dp"
        android:background="#FFFFFF"
        android:divider="#F0F3F7"
        android:dividerHeight="6dp" />
</RelativeLayout>

二、需要有個針對ListView的自定義Adapter:StoryCategoryAdapter.java

public class StoryCategoryAdapter extends BaseAdapter {

    private List<Map<String, Object>> data = new ArrayList<>();
    private Context context;

    public StoryCategoryAdapter(Context context) {
        this.context = context;
    }

    class Holder {
        ImageView icon;
        TextView name;
        ImageView more;
    }

    public void setData(List<Map<String, Object>> data) {
        this.data = data;
    }

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public Object getItem(int position) {
        return data.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @SuppressLint("InflateParams")
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Holder holder;
        if (convertView == null) {
            holder = new Holder();
            convertView = LayoutInflater.from(context).inflate(R.layout.listview_item_story_category, null);
            holder.icon = (ImageView) convertView.findViewById(R.id.iv_icon);
            holder.name = (TextView) convertView.findViewById(R.id.tv_name);
            holder.more = (ImageView) convertView.findViewById(R.id.iv_more);
            convertView.setTag(holder);
        } else {
            holder = (Holder) convertView.getTag();
        }

        if (position < getCount()) {
            Map<String, Object> map = data.get(position);
            if (map != null && map.size() > 0) {
                Glide.with(context)
                        .load(map.get("icon"))
                        .into(holder.icon);
                holder.name.setText((CharSequence) map.get("name"));
                holder.more.setImageResource(R.drawable.activity_home_me_more);
            }
        }
        return convertView;
    }

三、還需要載入時的動畫布局和全部資料載入完成後的文字提示佈局:

(1)activity_story_category_loading.java檔案:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="54dp"
    android:background="#fff0f3f7"
    android:minHeight="54dp">

    <ProgressBar
        android:id="@+id/progress_bar_loading"
        android:layout_width="29dp"
        android:layout_height="29dp"
        android:layout_centerInParent="true"
        android:clickable="false"
        android:focusable="false"
        android:indeterminateDrawable="@drawable/activity_story_category_progress_loading"
        android:indeterminateDuration="1500" />

</RelativeLayout>

(2)activity_story_category_loading_text檔案:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="54dp"
    android:background="#fff0f3f7"
    android:minHeight="54dp">

    <TextView
        android:id="@+id/tv_loading"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:gravity="center_horizontal"
        android:text="@string/activity_story_introduce_end" <!--已經到底了~-->
        android:textColor="#ff999999"
        android:textSize="12sp" />
</RelativeLayout>

前面三步準備工作做好了,就該正式開工咯。

四、實現ListView分頁載入:StoryCategoryActivity.java

為了讓讀者更有融入感,我決定全盤複製,更易看懂。(注:核心在addData()和onScrollListener )

public class StoryCategoryActivity extends BaseActivity {

    private ModuleBean.Data.Module module;
    private ModuleBean.Data.Module.Content content;
    private ListView lvCategoryItem;
    private StoryCategoryAdapter storyCategoryAdapter;
    private List<Map<String, Object>> listData = new ArrayList<>();
    private View footerView;
    private TextView tvTitle;
    private int page = 1;
    private boolean isScroll = false; // 還在滑動就不載入資料
    private boolean isLoadingAll = true;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (AppData.isKillBySystem) {
            finish();
            return;
        }
        setContentView(R.layout.activity_story_category);
        initView();
        initData();
    }

    @SuppressLint("InflateParams")
    private void initView() {
        findViewById(R.id.img_arrow_back).setOnClickListener(onClickListener);
        findViewById(R.id.iv_history).setOnClickListener(onClickListener);
        tvTitle = (TextView) findViewById(R.id.tv_title);

        lvCategoryItem = (ListView) findViewById(R.id.lv_category_item);
        storyCategoryAdapter = new StoryCategoryAdapter(this);
        lvCategoryItem.setAdapter(storyCategoryAdapter);
        footerView = LayoutInflater.from(this).inflate(R.layout.activity_story_category_loading, null); // 載入時轉圈動畫布局
        lvCategoryItem.addFooterView(footerView); // addFooterView瞭解一下
        lvCategoryItem.setOnScrollListener(onScrollListener);
    }

    private void initData() {
        if (getIntent().getExtras() != null) {
            switch (getIntent().getIntExtra("tag", 1)) {
                case GlobalVar.STORY_MODULE:
                    module = new Gson().fromJson(getIntent().getStringExtra("bean"), ModuleBean.Data.Module.class);
                    String name1 = module.name;
                    if ("".equals(name1) || name1 == null) tvTitle.setText("");
                    else tvTitle.setText(name1);
                    getPlayList(page);
                    break;
                case GlobalVar.STORY_SUB_MODULE:
                    module = new Gson().fromJson(getIntent().getStringExtra("bean"), ModuleBean.Data.Module.class);
                    String name2 = module.name;
                    if ("".equals(name2) || name2 == null) tvTitle.setText("");
                    else tvTitle.setText(name2);
                    getPlayList(page);
                    break;
                case GlobalVar.STORY_SUB_MODULE_CONTENT:
                    content = new Gson().fromJson(getIntent().getStringExtra("bean"), ModuleBean.Data.Module.Content.class);
                    String name3 = content.name;
                    if ("".equals(name3) || name3 == null) tvTitle.setText("");
                    else tvTitle.setText(name3);
                    break;
            }

        }
    }

    private void getPlayList(int page) {
        new Handler().postDelayed(() -> {
            if (listData.size() == 0) {
                FuncUtils.toast("網路較差,請重試!");
            }
        }, 10000);

        WebAPIUtils.getPlayList("ZmQ3M2Q1MmFmYWZl", "400010C800000001", "f7109feead0ada9c3f5639a867c788f37858", module.id, page, 20,
                this::addData
        );
    }

    private void addData(List<PlayListBean.Data.Content> playList, int total) {
        lvCategoryItem.setOnScrollListener(onScrollListener); // 再次註冊監聽
        if (listData.size() < 40) { // 設定當資料低於40條時不顯示滑動條
            lvCategoryItem.setVerticalScrollBarEnabled(false);
        } else {
            lvCategoryItem.setVerticalScrollBarEnabled(true); // 設定當資料有40條或者超過40條時顯示滑動條
        }
        if (!isScroll) {
            for (int i = 0; i < playList.size(); i++) {
//                LogUtils.i(TAG, "總數量:" + total + "個," + "單次獲取:" + playList.size() + "個," + playList.get(i).id + playList.get(i).name + playList.get(i).imgSmall);
                Map<String, Object> map = new HashMap<>();
                map.put("icon", playList.get(i).imgSmall);
                map.put("name", (this.page - 1) * 20 + i + 1 + "." + playList.get(i).name);
                listData.add(map);
            }

            if ((total - playList.size() - 20 * (this.page - 1)) > 0) { // 分段獲取
                this.page++;
            } else {
                this.page = -1;
            }
            storyCategoryAdapter.setData(listData);
            storyCategoryAdapter.notifyDataSetChanged();
        }
    }

    private AbsListView.OnScrollListener onScrollListener = new AbsListView.OnScrollListener() {
        int visibleLastIndex = 0; // 最後的可視項索引
        int visibleItemCounts; // 當前視窗可見項總數

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            switch (scrollState) {
                case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
                    isScroll = true;
                    break;
                case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
                    isScroll = true;
                    break;
                case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
                    int itemsLastIndex = storyCategoryAdapter.getCount() - 1; // 資料集最後一項的索引
                    int lastIndex = itemsLastIndex + 1; // 加上底部的loadMoreView項
                    if (visibleLastIndex == lastIndex) {
                        isScroll = false;
                        LogUtils.i(TAG, "##### 滾動到底部 ######");
                        if (page >= 1) {
                            lvCategoryItem.setOnScrollListener(null); // 訪問網路時,設定滑動監聽無效,解決非同步獲取資料時產生的資料重複問題
                            getPlayList(page);
                        } else {
                            LogUtils.i(TAG, "沒有更多資料了!");
                            if (isLoadingAll) {
                                lvCategoryItem.removeFooterView(footerView);
                                lvCategoryItem.addFooterView(LayoutInflater.from(StoryCategoryActivity.this).inflate(R.layout.activity_story_category_loading_text, null));
                                isLoadingAll = false;
                            }
                        }
                    }
                    break;
            }
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            this.visibleItemCounts = visibleItemCount;
            visibleLastIndex = firstVisibleItem + visibleItemCount - 1;
        }
    };

    private View.OnClickListener onClickListener = v -> {
        switch (v.getId()) {
            case R.id.img_arrow_back:
                finish();
                break;
            case R.id.iv_history:
                startActivity(new Intent(this, StoryHistoryActivity.class));
                break;
        }
    };

}

當然,我也遇到了蠻多問題,比如:

(1)如果給listview設定了setOnItemClickListener()監聽,一定注意,點選底部載入動畫和非底部item時處理不一樣。像我不想處理點選底部,try-catch一下避免app崩掉就可以了!

(2)底部載入動畫和載入文字的切換標準,我的內容主要是用了兩個變數來判斷,你可以看看程式碼。

(3)listview從網上拉取資料時,手指還在滑動,這個時候資料狀態會出現問題,因為是非同步獲取資料,具體解決辦法可以參見程式碼。

總結:

遇到問題不可怕,多去網上找找,多想想,總能解決的!

走一段令人留戀的路,做一個不負自己的人。——共勉