我想把ListView改为RecyclerView。我想在RecyclerView中使用OnScrollListener的onScroll来确定用户是否滚动到列表的末尾。
我如何知道用户是否滚动到列表的末尾以便我可以从REST服务获取新数据?
我想把ListView改为RecyclerView。我想在RecyclerView中使用OnScrollListener的onScroll来确定用户是否滚动到列表的末尾。
我如何知道用户是否滚动到列表的末尾以便我可以从REST服务获取新数据?
当前回答
也可以在没有滚动侦听器的情况下实现,只使用数据模型的纯逻辑。滚动视图需要按位置获取项目以及最大项目计数。模型可以具有后台逻辑,以块的形式获取所需的项,而不是一个一个地获取,并在后台线程中执行此操作,在数据准备就绪时通知视图。
这种方法允许获取队列优先选择最近请求的(所以当前可见)项而不是旧的(很可能已经滚动了)提交,控制使用的并行线程的数量等等。这个方法的完整源代码(演示应用程序和可重用库)可以在这里找到。
其他回答
if (layoutManager.findLastCompletelyVisibleItemPosition() ==
recyclerAdapter.getItemCount() - 1) {
//load more items.
}
公平而简单。 这是可行的。
我刚试了一下:
final LinearLayoutManager rvLayoutManager = new LinearLayoutManager(this);
rvMovieList = (RecyclerView) findViewById(R.id.rvMovieList);
rvMovieList.setLayoutManager(rvLayoutManager);
adapter = new MovieRecyclerAdapter(this, movies);
rvMovieList.setAdapter(adapter);
adapter.notifyDataSetChanged();
rvMovieList.addOnScrollListener(new OnScrollListener() {
boolean loading = true;
int pastVisiblesItems, visibleItemCount, totalItemCount;
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = rvLayoutManager.getChildCount();
totalItemCount = rvLayoutManager.getItemCount();
pastVisiblesItems = rvLayoutManager.findFirstVisibleItemPosition();
int lastitem = visibleItemCount + pastVisiblesItems;
if ( lastitem == totalItemCount
&& page < total_pages
&& !keyword.equals("") ) {
// hit bottom ..
String strnext;
if (page == total_pages - 1) {
int last = total_results - (page * 20);
strnext = String.format(getResources().getString(R.string.next), last);
} else {
strnext = String.format(getResources().getString(R.string.next), 20);
}
btNext.setText(strnext);
btNext.setVisibility(View.VISIBLE);
} else {
btNext.setVisibility(View.INVISIBLE);
}
if(pastVisiblesItems == 0 && page > 1){
// hit top
btPrev.setVisibility(View.VISIBLE);
} else {
btPrev.setVisibility(View.INVISIBLE);
}
}
});
我搞不清楚在哪里加载真或假,所以我试图删除它,这段代码工作如我所料。
借助Kotlin扩展函数的强大功能,代码可以看起来更加优雅。把它放在你想要的任何地方(我把它放在一个ExtensionFunctions中。kt文件):
/**
* WARNING: This assumes the layout manager is a LinearLayoutManager
*/
fun RecyclerView.addOnScrolledToEnd(onScrolledToEnd: () -> Unit){
this.addOnScrollListener(object: RecyclerView.OnScrollListener(){
private val VISIBLE_THRESHOLD = 5
private var loading = true
private var previousTotal = 0
override fun onScrollStateChanged(recyclerView: RecyclerView,
newState: Int) {
with(layoutManager as LinearLayoutManager){
val visibleItemCount = childCount
val totalItemCount = itemCount
val firstVisibleItem = findFirstVisibleItemPosition()
if (loading && totalItemCount > previousTotal){
loading = false
previousTotal = totalItemCount
}
if(!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)){
onScrolledToEnd()
loading = true
}
}
}
})
}
然后像这样使用它:
youRecyclerView.addOnScrolledToEnd {
//What you want to do once the end is reached
}
这个解决方案基于Kushal Sharma的回答。然而,这是一个更好的,因为:
它使用onScrollStateChanged而不是onScroll。这样做比较好,因为每当RecyclerView中有任何类型的移动时,onScroll都会被调用,而onScrollStateChanged只在RecyclerView的状态改变时被调用。使用onScrollStateChanged将节省CPU时间,从而节省电池。 因为它使用扩展函数,所以可以在任何RecyclerView中使用。客户端代码只有一行。
这里我的解决方案使用AsyncListUtil,在网络上说: 注意,这个类使用一个线程来加载数据,所以它适合从二级存储(如磁盘)加载数据,但不适合从网络加载数据。 但是我使用odata来读取数据并工作良好。 我在示例中遗漏了数据实体和网络方法。 我只包括示例适配器。
public class AsyncPlatoAdapter extends RecyclerView.Adapter {
private final AsyncPlatoListUtil mAsyncListUtil;
private final MainActivity mActivity;
private final RecyclerView mRecyclerView;
private final String mFilter;
private final String mOrderby;
private final String mExpand;
public AsyncPlatoAdapter(String filter, String orderby, String expand, RecyclerView recyclerView, MainActivity activity) {
mFilter = filter;
mOrderby = orderby;
mExpand = expand;
mRecyclerView = recyclerView;
mActivity = activity;
mAsyncListUtil = new AsyncPlatoListUtil();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).
inflate(R.layout.plato_cardview, parent, false);
// Create a ViewHolder to find and hold these view references, and
// register OnClick with the view holder:
return new PlatoViewHolderAsync(itemView, this);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final Plato item = mAsyncListUtil.getItem(position);
PlatoViewHolderAsync vh = (PlatoViewHolderAsync) holder;
if (item != null) {
Integer imagen_id = item.Imagen_Id.get();
vh.getBinding().setVariable(BR.plato, item);
vh.getBinding().executePendingBindings();
vh.getImage().setVisibility(View.VISIBLE);
vh.getProgress().setVisibility(View.GONE);
String cacheName = null;
String urlString = null;
if (imagen_id != null) {
cacheName = String.format("imagenes/imagen/%d", imagen_id);
urlString = String.format("%s/menusapi/%s", MainActivity.ROOTPATH, cacheName);
}
ImageHelper.downloadBitmap(mActivity, vh.getImage(), vh.getProgress(), urlString, cacheName, position);
} else {
vh.getBinding().setVariable(BR.plato, item);
vh.getBinding().executePendingBindings();
//show progress while loading.
vh.getImage().setVisibility(View.GONE);
vh.getProgress().setVisibility(View.VISIBLE);
}
}
@Override
public int getItemCount() {
return mAsyncListUtil.getItemCount();
}
public class AsyncPlatoListUtil extends AsyncListUtil<Plato> {
/**
* Creates an AsyncListUtil.
*/
public AsyncPlatoListUtil() {
super(Plato.class, //my data class
10, //page size
new DataCallback<Plato>() {
@Override
public int refreshData() {
//get count calling ../$count ... odata endpoint
return countPlatos(mFilter, mOrderby, mExpand, mActivity);
}
@Override
public void fillData(Plato[] data, int startPosition, int itemCount) {
//get items from odata endpoint using $skip and $top
Platos p = loadPlatos(mFilter, mOrderby, mExpand, startPosition, itemCount, mActivity);
for (int i = 0; i < Math.min(itemCount, p.value.size()); i++) {
data[i] = p.value.get(i);
}
}
}, new ViewCallback() {
@Override
public void getItemRangeInto(int[] outRange) {
//i use LinearLayoutManager in the RecyclerView
LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
outRange[0] = layoutManager.findFirstVisibleItemPosition();
outRange[1] = layoutManager.findLastVisibleItemPosition();
}
@Override
public void onDataRefresh() {
mRecyclerView.getAdapter().notifyDataSetChanged();
}
@Override
public void onItemLoaded(int position) {
mRecyclerView.getAdapter().notifyItemChanged(position);
}
});
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
onRangeChanged();
}
});
}
}
}
这些答案都没有考虑到列表是否太小。
这是我一直在使用的一段代码,它在两个方向上都适用于RecycleViews。
@Override
public boolean onTouchEvent(MotionEvent motionEvent) {
if (recyclerViewListener == null) {
return super.onTouchEvent(motionEvent);
}
/**
* If the list is too small to scroll.
*/
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
if (!canScrollVertically(1)) {
recyclerViewListener.reachedBottom();
} else if (!canScrollVertically(-1)) {
recyclerViewListener.reachedTop();
}
}
return super.onTouchEvent(motionEvent);
}
public void setListener(RecyclerViewListener recycleViewListener) {
this.recyclerViewListener = recycleViewListener;
addOnScrollListener(new OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (recyclerViewListener == null) {
return;
}
recyclerViewListener.scrolling(dy);
if (!canScrollVertically(1)) {
recyclerViewListener.reachedBottom();
} else if (!canScrollVertically(-1)) {
recyclerViewListener.reachedTop();
}
}
});
}