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