android横向加载更多,Android 实现自己的RecyclerView加载更多

很多时候,项目中都会有列表加载更多的场景,这次我们让RecyclerView轻松拥有加载更多的功能。虽然已有许多类似的轮子,但有的功能过于复杂,其实很多都用不到,所以不妨打造更适合自己的轮子。

我们的RecyclerView加载更多是通过其Adapter子类实现的,接下来我们一步步的构建Adapter吧!

1、编写通用的Adapter、ViewHolder

一般情况下使用Adapter都要为其创建一个ViewHolder,既然要编写通用的Adapter,首先要有一个通用的ViewHolder:

public class ViewHolder extends RecyclerView.ViewHolder {

private SparseArray mViews;

private View mConvertView;

private ViewHolder(View itemView) {

super(itemView);

mConvertView = itemView;

mViews = new SparseArray<>();

}

public static ViewHolder create(Context context, int layoutId, ViewGroup parent) {

View itemView = LayoutInflater.from(context).inflate(layoutId, parent, false);

return new ViewHolder(itemView);

}

public static ViewHolder create(View itemView) {

return new ViewHolder(itemView);

}

public T getView(int viewId) {

View view = mViews.get(viewId);

if (view == null) {

view = mConvertView.findViewById(viewId);

mViews.put(viewId, view);

}

return (T) view;

}

public View getConvertView() {

return mConvertView;

}

public void setText(int viewId, String text) {

TextView textView = getView(viewId);

textView.setText(text);

}

.......省略其它辅助方法.........

}

我们自定义的ViewHolder类可以根据布局文件的id或具体的itemView返回一个ViewHolder对象,并用SparseArray来缓存我们itemView中的子View,避免每次都要去解析子View,同时提供相关辅助方法设置itemView的内容。有了ViewHolder,接下来编写Adapter就简单了:

public abstract class BaseAdapter extends RecyclerView.Adapter {

public static final int TYPE_COMMON_VIEW = 100001;

private OnItemClickListeners mItemClickListener;

protected Context mContext;

protected List mDatas;

protected abstract void convert(ViewHolder holder, T data);

protected abstract int getItemLayoutId();

public BaseAdapter(Context context, List datas) {

mContext = context;

mDatas = datas == null ? new ArrayList() : datas;

}

@Override

public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

ViewHolder viewHolder = null;

switch (viewType) {

case TYPE_COMMON_VIEW:

viewHolder = ViewHolder.create(mContext, getItemLayoutId(), parent);

break;

}

return viewHolder;

}

@Override

public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

switch (holder.getItemViewType()) {

case TYPE_COMMON_VIEW:

bindCommonItem(holder, position);

break;

}

}

private void bindCommonItem(RecyclerView.ViewHolder holder, final int position) {

final ViewHolder viewHolder = (ViewHolder) holder;

convert(viewHolder, mDatas.get(position));

viewHolder.getConvertView().setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

mItemClickListener.onItemClick(viewHolder, mDatas.get(position), position);

}

});

}

@Override

public int getItemCount() {

return mDatas.size();

}

@Override

public int getItemViewType(int position) {

return TYPE_COMMON_VIEW;

}

public T getItem(int position) {

if (mDatas.isEmpty()) {

return null;

}

return mDatas.get(position);

}

public void setOnItemClickListener(OnItemClickListeners itemClickListener) {

mItemClickListener = itemClickListener;

}

}

很简单,继承RecyclerView.Adapter,重写相关方法,提供了getItemLayoutId()、convert()两个抽象方法供BaseAdapter的子类实现,来初始化item的布局id,以及item内容,同时通过OnItemClickListeners接口为item绑定点击事件。

编写好了Adapter,我们在其构造方法中添加一个参数isOpenLoadMore,来表示是否开启加载更多:

public BaseAdapter(Context context, List datas, boolean isOpenLoadMore) {

mContext = context;

mDatas = datas == null ? new ArrayList() : datas;

mOpenLoadMore = isOpenLoadMore;

}

这样初级版本的Adapter就完成了。

2、添加Footer View

接下来就要添加Footer View,这样才能有加载更多的视觉效果么。其实很简单,如果当前item的position满足如下条件:

private boolean isFooterView(int position) {

return mOpenLoadMore && position >= getItemCount() - 1;

}

即已经开启加载更多、当前position在列表的尾部,则在getItemViewType()返回

@Override

public int getItemViewType(int position) {

if (isFooterView(position)) {

return TYPE_FOOTER_VIEW;

}

}

之后会创建Footer View对应的ViewHolder:

@Override

public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

ViewHolder viewHolder = null;

switch (viewType) {

case TYPE_FOOTER_VIEW:

if (mFooterLayout == null) {

mFooterLayout = new RelativeLayout(mContext);

}

viewHolder = ViewHolder.create(mFooterLayout);

break;

}

return viewHolder;

}

可以看到mFooterLayout是一个空的Container,因为要根据加载更多对应的状态来更新mFooterLayout,这个稍后再说。

这样Footer View就添加完了吗?当然没有,我们需要针对StaggeredGridLayoutManager、GridLayoutManager模式分别重写onViewAttachedToWindow()、onAttachedToRecyclerView()方法,否则会出现Footer View不能在列表底部占据一行的问题:

@Override

public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {

super.onViewAttachedToWindow(holder);

if (isFooterView(holder.getLayoutPosition())) {

ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();

if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) {

StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;

p.setFullSpan(true);

}

}

}

@Override

public void onAttachedToRecyclerView(RecyclerView recyclerView) {

super.onAttachedToRecyclerView(recyclerView);

final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();

if (layoutManager instanceof GridLayoutManager) {

final GridLayoutManager gridManager = ((GridLayoutManager) layoutManager);

gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {

@Override

public int getSpanSize(int position) {

if (isFooterView(position)) {

return gridManager.getSpanCount();

}

return 1;

}

});

}

}

到此无论是那种形式的列表都能正常添加Footer View了。

3、判断列表是否滚动到了底部

按照常理,只有滑动到列表的底部才会触发加载更多的操作,之前提到了onAttachedToRecyclerView()方法,通过该方法可以得到Adapter所绑定的RecyclerView,这样就能监听RecyclerView的滚动事件,进而判断列表是否滚动了底部:

private void startLoadMore(RecyclerView recyclerView, final RecyclerView.LayoutManager layoutManager) {

if (!mOpenLoadMore || mLoadMoreListener == null) {

return;

}

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

@Override

public void onScrollStateChanged(RecyclerView recyclerView, int newState) {

super.onScrollStateChanged(recyclerView, newState);

if (newState == RecyclerView.SCROLL_STATE_IDLE) {

if (!isAutoLoadMore && findLastVisibleItemPosition(layoutManager) + 1 == getItemCount()) {

scrollLoadMore();

}

}

}

@Override

public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

super.onScrolled(recyclerView, dx, dy);

if (isAutoLoadMore && findLastVisibleItemPosition(layoutManager) + 1 == getItemCount()) {

scrollLoadMore();

} else if (isAutoLoadMore) {

isAutoLoadMore = false;

}

}

});

}

我们单独封装了startLoadMore()方法,当列表滚动状态改变会回调onScrollStateChanged()方法,如果状态为SCROLL_STATE_IDLE,并且当前可见的item位置为列表最后一项,则开始加载更多数据。这里还重写了onScrolled()方法,当列表滚动结束后会回调,重写该方法有什么用呢?如果初始item不满一屏幕,则可在该方法中加载更多数据,直到item占满一屏幕,也就自动加载更多。我们用isAutoLoadMore来区分这种情况,如果isAutoLoadMore为true,则Footer View可见则自动加载更多。

再看一下scrollLoadMore()方法:

private void scrollLoadMore() {

if (mFooterLayout.getChildAt(0) == mLoadingView) {

mLoadMoreListener.onLoadMore(false);

}

}

如果当前的Footer View 是正在加载的状态,则调用OnLoadMoreListener接口的onLoadMore()方法进行具体的加载操作,该方法有一个boolean类型的参数,表示是否重新加载,因为存在加载失败的情况,这样可方便使用。

4、更新Footer View布局样式

到这里,我们已经明确了加载更多操作的触发时机,接下来就是在加载更多的时候来更新Footer View,我们定义了三种状态:加载中、加载失败、加载结束,通过如下方法将对应状态的View或布局id添加到Footer View中:

public void setLoadingView(int loadingId) {

setLoadingView(Util.inflate(mContext, loadingId));

}

public void setLoadFailedView(int loadFailedId) {

setLoadFailedView(Util.inflate(mContext, loadFailedId));

}

public void setLoadEndView(int loadEndId) {

setLoadEndView(Util.inflate(mContext, loadEndId));

}

这三个方法时是通过布局id来给Footer View设置新样式,当然还有通过View来设置的重载方法。在初始化Adapter时可以调用setLoadingView()来设置加载中的Footer View样式,如果加载失败了可调用setLoadFailedView()、如果加载结束没有更多数据则可以调用setLoadEndView()设对应的布局样式。其实就是先移除mFooterLayout的子View,然后将新的布局添加进去。

5、添加EmptyView

考虑一种情况,如果初始化时,需要先从网络请求数据,然后再更新列表,则一般需要有一个加载提示,所以我们有必要将这个小功能也封装到Adapter中,这样就省去了修改界面布局或者手动显示、隐藏加载提示的步骤。

实现也很简单,先看如下代码:

@Override

public int getItemCount() {

if (mDatas.isEmpty() && mEmptyView != null) {

return 1;

}

}

如果mData为空,且设置了EmptyView则getItemCount()直接返回1。同理返回的item类型为TYPE_EMPTY_VIEW,代表EmptyView:

@Override

public int getItemViewType(int position) {

if (mDatas.isEmpty() && mEmptyView != null) {

return TYPE_EMPTY_VIEW;

}

}

在onCreateViewHolder()方法中会创建对应的ViewHolder。

@Override

public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

ViewHolder viewHolder = null;

switch (viewType) {

case TYPE_EMPTY_VIEW:

viewHolder = ViewHolder.create(mEmptyView);

break;

}

return viewHolder;

}

同时提供方法在初始化Adapter时设置EmptyView:

public void setEmptyView(View emptyView) {

mEmptyView = emptyView;

}

6、具体使用

完成了封装,来看看具体的使用,首先创建一个RefreshAdapter继承我们的BaseAdapter:

public class RefreshAdapter extends BaseAdapter {

public RefreshAdapter(Context context, List datas, boolean isLoadMore) {

super(context, datas, isLoadMore);

}

@Override

protected void convert(ViewHolder holder, final String data) {

holder.setText(R.id.item_title, data);

holder.setOnClickListener(R.id.item_btn, new View.OnClickListener() {

@Override

public void onClick(View view) {

Toast.makeText(mContext, "我是" + data + "的button", Toast.LENGTH_SHORT).show();

}

});

}

@Override

protected int getItemLayoutId() {

return R.layout.item_layout;

}

}

在getItemLayoutId()中返回item布局id,在convert()中初始化item的内容。有了RefreshAdapter,接下来看Activity的操作:

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);

//初始化adapter

mAdapter = new RefreshAdapter(this, null, true);

//初始化EmptyView

View emptyView = LayoutInflater.from(this).inflate(R.layout.empty_layout, (ViewGroup) mRecyclerView.getParent(), false);

mAdapter.setEmptyView(emptyView);

//初始化 开始加载更多的loading View

mAdapter.setLoadingView(R.layout.load_loading_layout);

//设置加载更多触发的事件监听

mAdapter.setOnLoadMoreListener(new OnLoadMoreListener() {

@Override

public void onLoadMore(boolean isReload) {

loadMore();

}

});

//设置item点击事件监听

mAdapter.setOnItemClickListener(new OnItemClickListeners() {

@Override

public void onItemClick(ViewHolder viewHolder, String data, int position) {

Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show();

}

});

LinearLayoutManager layoutManager = new LinearLayoutManager(this);

layoutManager.setOrientation(LinearLayoutManager.VERTICAL);

mRecyclerView.setLayoutManager(layoutManager);

mRecyclerView.setAdapter(mAdapter);

//延时3s刷新列表

new Handler().postDelayed(new Runnable() {

@Override

public void run() {

List data = new ArrayList<>();

for (int i = 0; i < 12; i++) {

data.add("item--" + i);

}

//刷新数据

mAdapter.setNewData(data);

}

}, 3000);

}

注释已经很详细了,就不多说了。其中loadMore()方法如下:

private void loadMore() {

new Handler().postDelayed(new Runnable() {

@Override

public void run() {

if (mAdapter.getItemCount() > 15 && isFailed) {

isFailed = false;

//加载失败,更新footer view提示

mAdapter.setLoadFailedView(R.layout.load_failed_layout);

} else if (mAdapter.getItemCount() > 17) {

//加载完成,更新footer view提示

mAdapter.setLoadEndView(R.layout.load_end_layout);

} else {

final List data = new ArrayList<>();

for (int i = 0; i < 2; i++) {

data.add("item--" + (mAdapter.getItemCount() + i - 1));

}

//刷新数据

mAdapter.setLoadMoreData(data);

}

}

}, 2000);

}

就是延时2s更新列表数据,同时人为模拟加载失败和结束的情况。

7、效果

运行后,看具体的效果:

66c065874848

EmptyView

66c065874848

loading

66c065874848

load_failed

66c065874848

load_end

66c065874848

auto_load

PS:更新

(1)重构基类继承关系

(2)支持多种类型的Item View

创建只有一种类型的Item View的Adapter时,直接继承CommonBaseAdapter类即可,其它操作不变。

创建有多种类型的Item View的Adapter时时,继承MultiBaseAdapter即可,实例如下:

public class MultiRefreshAdapter extends MultiBaseAdapter {

public MultiRefreshAdapter(Context context, List datas, boolean isOpenLoadMore) {

super(context, datas, isOpenLoadMore);

}

@Override

protected void convert(ViewHolder holder, final String data, int viewType) {

if (viewType == 0) {

holder.setText(R.id.item_title, data);

holder.setOnClickListener(R.id.item_btn, new View.OnClickListener() {

@Override

public void onClick(View view) {

Toast.makeText(mContext, "我是" + data + "的button", Toast.LENGTH_SHORT).show();

}

});

} else {

holder.setText(R.id.item_title1, data);

}

}

@Override

protected int getItemLayoutId(int viewType) {

if (viewType == 0) {

return R.layout.item_layout;

}

return R.layout.item_layout1;

}

@Override

protected int getViewType(int position, String data) {

if (position % 2 == 0) {

return 0;

}

return 1;

}

}

设置Item点击事件时,通过如下方法:

mAdapter.setOnMultiItemClickListener(new OnMultiItemClickListeners() {

@Override

public void onItemClick(ViewHolder viewHolder, String data, int position, int viewType) {

}

});

其它的操作不变。效果就不贴了,可通过源码查看。

2016.12.6更新

使用EmptyView时,初始加载无数据可移除EmptyView,或添加新ReloadView以便进行重新加载、提示等操作。

2017.7.4更新

支持Adapter重置、完善使用方式

2017.12.22更新

支持给RecyclerView添加HeaderView

自动判断是否正在加载更多,避免重复加载

更多详情可参考源码,不合理的地方还求反馈!

☞源码戳这里

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
了解了你的问题,以下是一个 Kotlin 实现横向滑动加载更多的示例代码,使用的是 SmartRefreshLayout 库中的 SmartRefreshHorizontal 组件: 在布局文件中添加 SmartRefreshHorizontal 组件: ```xml <com.scwang.smartrefresh.layout.SmartRefreshHorizontal android:id="@+id/refreshLayout" android:layout_width="match_parent" android:layout_height="wrap_content"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="wrap_content" /> </com.scwang.smartrefresh.layout.SmartRefreshHorizontal> ``` 在 Kotlin 中设置 SmartRefreshHorizontal 组件的相关属性和监听器: ```kotlin val refreshLayout = findViewById<SmartRefreshHorizontal>(R.id.refreshLayout) val recyclerView = findViewById<RecyclerView>(R.id.recyclerView) // 设置 Adapter 和 LayoutManager val adapter = MyAdapter() val layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false) recyclerView.adapter = adapter recyclerView.layoutManager = layoutManager // 监听刷新和加载更多事件 refreshLayout.setOnRefreshListener { // 刷新数据 adapter.refreshData() // 结束刷新 refreshLayout.finishRefresh() } refreshLayout.setOnLoadMoreListener { // 加载更多数据 adapter.loadMoreData() // 结束加载更多 refreshLayout.finishLoadMore() } ``` 其中,`MyAdapter` 是你自己实现RecyclerView Adapter,根据需要实现其中的 `refreshData()` 和 `loadMoreData()` 方法来进行数据刷新和加载更多的操作。 希望这个示例代码可以帮到你。如果还有问题或需要更多帮助,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值