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从网上拉取数据时,手指还在滑动,这个时候数据状态会出现问题,因为是异步获取数据,具体解决办法可以参见代码。

总结:

遇到问题不可怕,多去网上找找,多想想,总能解决的!

走一段令人留恋的路,做一个不负自己的人。——共勉

 

猜你喜欢

转载自blog.csdn.net/Agg_bin/article/details/81876915
今日推荐