前言
项目地址
我们已经把首页列表的Item
布局完成,接下来我们就开始加载首页数据,我们这一篇主要是做封装,具体网络请求放在下一篇
引入依赖
//刷新分页组件
api 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0'
api 'com.scwang.smartrefresh:SmartRefreshHeader:1.1.0'
//viewmodel and livedata
api 'androidx.lifecycle:lifecycle-extensions:2.1.0'
//paging分页组件
api 'androidx.paging:paging-runtime:2.1.0'
我们首页有下拉刷新和上拉加载,我们使用SmartRefreshLayout
实现,分页使用paging
,viewmodel
和livedata
我们导入了一个库lifecycle-extensions
,当然你也可以分开导入
封装刷新组件
我们知道列表在加载的时候,如果没有数据,我们一般会展示一个空的布局,所以我们先自定义一个EmptyView
,布局很简单,
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_horizontal"
android:orientation="vertical">
<ImageView
android:id="@+id/empty_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon_empty_no_data" />
<TextView
android:id="@+id/empty_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="虽然什么也没有,要不下拉刷新试试"
android:textColor="#999999"
android:textSize="16sp"
android:visibility="visible"
tools:text="虽然什么也没有,要不下拉刷新看看"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/empty_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:textColor="#ffffff"
android:textSize="14sp"
android:visibility="gone"
app:backgroundTint="#ff678f"
app:cornerRadius="20dp"
tools:text="朕知道了">
</com.google.android.material.button.MaterialButton>
</LinearLayout>
然后我们创建一个EmptyView
来加载这个xml,同时添加几个set
方法
/**
* 空布局
*/
public class EmptyView extends LinearLayout {
private ImageView icon;
private TextView title;
private Button action;
public EmptyView(@NonNull Context context) {
this(context, null);
}
public EmptyView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public EmptyView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public EmptyView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int style) {
super(context, attrs, defStyleAttr, style);
setOrientation(VERTICAL);
setGravity(Gravity.CENTER);
LayoutInflater.from(context).inflate(R.layout.layout_empty_view, this, true);
icon = findViewById(R.id.empty_icon);
title = findViewById(R.id.empty_text);
action = findViewById(R.id.empty_action);
}
public void setEmptyIcon(@DrawableRes int iconRes) {
icon.setImageResource(iconRes);
}
public void setTitle(String text) {
if (TextUtils.isEmpty(text)) {
title.setVisibility(GONE);
} else {
title.setText(text);
title.setVisibility(VISIBLE);
}
}
public void setButton(String text, OnClickListener listener) {
if (TextUtils.isEmpty(text)) {
action.setVisibility(GONE);
} else {
action.setText(text);
action.setVisibility(VISIBLE);
action.setOnClickListener(listener);
}
}
}
接着就是编写我们的刷新布局了,由三部分组成,从上往下分别是头部刷新布局,列表,底部刷新布局,SmartRefreshLayout
都给我们提供了,我们只需要拿来直接使用即可
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.scwang.smartrefresh.layout.SmartRefreshLayout
android:id="@+id/refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.scwang.smartrefresh.layout.header.ClassicsHeader
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.scwang.smartrefresh.layout.footer.ClassicsFooter
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</com.scwang.smartrefresh.layout.SmartRefreshLayout>
<com.hfs.libcommon.view.EmptyView
android:id="@+id/empty_view"
android:layout_width="match_parent"
android:layout_marginTop="100dp"
android:layout_height="match_parent"/>
</FrameLayout>
</layout>
封装列表Fragment
接下来我们就封装一个AbsListFragment
,这个是所有列表数据Fragment
的基类,里面包括上拉加载/下拉刷新等操作,
/**
* 列表Fragment,下拉刷新/上拉加载
*/
public abstract class AbsListFragment<T> extends Fragment implements OnRefreshListener, OnLoadMoreListener {
private LayoutRefreshViewBinding mBinding;
protected RecyclerView mRecyclerView;
protected SmartRefreshLayout mRefreshLayout;
protected EmptyView mEmptyView;
protected PagedListAdapter<T, RecyclerView.ViewHolder> mAdapter;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mBinding = LayoutRefreshViewBinding.inflate(inflater, container, false);
mRecyclerView = mBinding.recyclerView;
mRefreshLayout = mBinding.refreshLayout;
mEmptyView = mBinding.emptyView;
mRefreshLayout.setEnableRefresh(true);
mRefreshLayout.setEnableLoadMore(true);
mRefreshLayout.setOnRefreshListener(this);
mRefreshLayout.setOnLoadMoreListener(this);
mAdapter = getAdapter();
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
mRecyclerView.setItemAnimator(null);
//默认给列表中的Item 一个 10dp的ItemDecoration
DividerItemDecoration decoration = new DividerItemDecoration(getContext(), LinearLayoutManager.VERTICAL);
decoration.setDrawable(ContextCompat.getDrawable(getContext(), R.drawable.list_divider));
mRecyclerView.addItemDecoration(decoration);
return mBinding.getRoot();
}
@Override
public void onRefresh(@NonNull RefreshLayout refreshLayout) {
}
@Override
public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
}
public abstract PagedListAdapter<T, RecyclerView.ViewHolder> getAdapter();
}
我们先设置一些基本属性方法,包括上拉加载监听,下拉刷新监听,RecyclerView
设置适配器等
不管是下拉刷新还是上拉加载都有回调,我们把这些回调的处理也写在这个Fragment
中,方便我们后续使用
public void finishRefresh(boolean hasData) {
//当前列表数据
PagedList<T> currentList = mAdapter.getCurrentList();
//是否有数据
hasData = hasData || currentList != null && currentList.size() > 0;
RefreshState state = mRefreshLayout.getState();
if (state.isFooter && state.isOpening) {
mRefreshLayout.finishLoadMore();
} else if (state.isHeader && state.isOpening) {
mRefreshLayout.finishRefresh();
}
if (hasData) {
mEmptyView.setVisibility(View.GONE);
} else {
mEmptyView.setVisibility(View.VISIBLE);
}
}
我们这里还需要把数据展示在我们的列表上,通过PagedListAdapter
的submitList
方法可以实现
public void submitList(PagedList<T> pagedList) {
if (pagedList.size() > 0) {
mAdapter.submitList(pagedList);
}
finishRefresh(pagedList.size()>0);
}
OK,到目前我们这个AbsListFragment
基本写完了,接下来我们让我们的HomeFragment
实现它即可
首页列表适配器
我们实现了AbsListFragment
后,会实现一个getAdapter
方法,所以我们还需要创建一个Adaper
,这个Adapter
继承自PagedListAdapter
public class FeedAdapter extends PagedListAdapter<Feed,FeedAdapter.ViewHolder> {
protected FeedAdapter() {
super(new DiffUtil.ItemCallback<Feed>() {
@Override
public boolean areItemsTheSame(@NonNull Feed oldItem, @NonNull Feed newItem) {
return false;
}
@Override
public boolean areContentsTheSame(@NonNull Feed oldItem, @NonNull Feed newItem) {
return false;
}
});
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return null;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
}
public class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(@NonNull View itemView) {
super(itemView);
}
}
}
我们在继承PagedListAdapter
的时候,我们的构造方法必须有一个DiffUtil.ItemCallback
,这个是新老数据对比时用的,areItemsTheSame
判断对象是否相同,areContentsTheSame
判断内容是否相同,我们看下源码注释
第一个方法建议使用id
,第二个方法建议使用equals
方法,所以我们重写下我们每个Bean
的equals
方法,然后我们这里就可以这样写
protected FeedAdapter() {
super(new DiffUtil.ItemCallback<Feed>() {
@Override
public boolean areItemsTheSame(@NonNull Feed oldItem, @NonNull Feed newItem) {
return oldItem.id==newItem.id;
}
@Override
public boolean areContentsTheSame(@NonNull Feed oldItem, @NonNull Feed newItem) {
return oldItem.equals(newItem);
}
});
}
接下来这个Adapter
就跟我们平时使用的Adapter
差不多了,这里因为有可能是图片,也有可能是视频,所以需要根据itemType
做下区分,完整代码如下
public class FeedAdapter extends PagedListAdapter<Feed, FeedAdapter.ViewHolder> {
private final LayoutInflater mLayoutInflater;
protected String mCategory;
protected FeedAdapter(Context context,String category) {
super(new DiffUtil.ItemCallback<Feed>() {
@Override
public boolean areItemsTheSame(@NonNull Feed oldItem, @NonNull Feed newItem) {
return oldItem.id == newItem.id;
}
@Override
public boolean areContentsTheSame(@NonNull Feed oldItem, @NonNull Feed newItem) {
return oldItem.equals(newItem);
}
});
mLayoutInflater = LayoutInflater.from(context);
mCategory = category;
}
@Override
public int getItemViewType(int position) {
Feed feed = getItem(position);
return feed.itemType;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ViewDataBinding binding = null;
if (viewType == Feed.TYPE_IMAGE_TEXT) {
binding = LayoutFeedTypeImageBinding.inflate(mLayoutInflater, parent, false);
} else {
binding = LayoutFeedTypeVideoBinding.inflate(mLayoutInflater, parent, false);
}
return new ViewHolder(binding.getRoot(),binding);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.bindData(getItem(position));
}
public class ViewHolder extends RecyclerView.ViewHolder {
private ViewDataBinding mBinding;
public ViewHolder(@NonNull View itemView, ViewDataBinding binding) {
super(itemView);
mBinding = binding;
}
public void bindData(Feed item) {
if (mBinding instanceof LayoutFeedTypeImageBinding) {
LayoutFeedTypeImageBinding imageBinding = (LayoutFeedTypeImageBinding) mBinding;
imageBinding.setFeed(item);
imageBinding.feedImage.bindData(item.width, item.height, 16, item.cover);
} else if (mBinding instanceof LayoutFeedTypeVideoBinding) {
LayoutFeedTypeVideoBinding videoBinding = (LayoutFeedTypeVideoBinding) mBinding;
videoBinding.setFeed(item);
videoBinding.listPlayerView.bindData(mCategory, item.width, item.height, item.cover, item.url);
}
}
}
}
下一篇我们将使用Paging
分页加载和网络请求数据展示