有很多关于如何使用EndlessScroll
并使用RecyclerView
按需加载数据的信息。但是,它仅支持在一个方向上滚动和加载数据。在我们的项目中,我们需要能够加载任意数据部分并允许用户在任一方向(向上和向下)滚动并在两个方向上按需加载数据。换句话说,每次用户滚动结束 - 在历史结束时加载数据。每次用户滚动到开头 - 在历史开头加载数据
此类实施的一个示例是Skype / Telegram聊天记录。当您打开聊天时,您将进入未读消息列表的开头,只要您开始滚动聊天记录,他们就会按需加载数据。
RecyclerView
的问题在于它使用偏移位置来处理项目和视图;难以将加载的数据提供给适配器并通知位置和计数的变化。当我们滚动到历史的开头时,我们无法在-1到-n的位置插入数据。
有人找到了解决方案吗?动态更新物品的位置?
答案 0 :(得分:0)
滚动双向时,您可以检查第一个和最后一个可见项目。基于此,您将能够对数据进行分页。
答案 1 :(得分:0)
我知道这是一个迟到的答案,但是当问题首次发布时,我认为github上已经有了实现,遗憾的是我找不到任何东西,并且有点忙于其他东西和回购被推回去。
所以我提出了解决问题的方法。您需要延长RecyclerView.Adapter
并将自己的开始和结束位置保持为data adapter
(Map<Integer, DataItem>
)。
<强> TwoWayEndlessAdapter.java 强>
/**
* The {@link TwoWayEndlessAdapter} class provides an implementation to manage two end data
* insertion into a {@link RecyclerView} easy by handling all of the logic within.
* <p>To implement a TwoWayEndlessAdapter simply extend from it, provide the class type parameter
* of the data type and <code>Override onBindViewHolder(ViewHolder, DataItem, int)</code> to bind
* the view to.</p>
*
* @param <DataItem> A class type that can used by the data adapter.
* @version 1.0.0
* @author Abbas
* @see android.support.v7.widget.RecyclerView.Adapter
* @see TwoWayEndlessAdapterImp
*/
public abstract class TwoWayEndlessAdapter<VH extends RecyclerView.ViewHolder, DataItem> extends RecyclerView.Adapter<VH> {
/*
* Data Adapter Container.
* */
protected List<DataItem> data;
private Callback mEndlessCallback = null;
/*
* Number of items before the last to get the lazy loading callback to load more items.
* */
private int bottomAdvanceCallback = 0;
private boolean isFirstBind = true;
/**
* @param callback A listener to set if want to receive bottom and top reached callbacks.
* @see TwoWayEndlessAdapter.Callback
*/
public void setEndlessCallback(Callback callback)
{
mEndlessCallback = callback;
}
/**
* Appends the provided list at the bottom of the {@link RecyclerView}
*
* @param bottomList The list to append at the bottom of the {@link RecyclerView}
*/
public void addItemsAtBottom(ArrayList<DataItem> bottomList)
{
if (data == null) {
throw new NullPointerException("Data container is `null`. Are you missing a call to setDataContainer()?");
}
if (bottomList == null || bottomList.isEmpty()) {
return;
}
int adapterSize = getItemCount();
data.addAll(adapterSize, bottomList);
notifyItemRangeInserted(adapterSize, adapterSize + bottomList.size());
}
/**
* Prepends the provided list at the top of the {@link RecyclerView}
*
* @param topList The list to prepend at the bottom of the {@link RecyclerView}
*/
public void addItemsAtTop(ArrayList<DataItem> topList)
{
if (data == null) {
throw new NullPointerException("Data container is `null`. Are you missing a call to setDataContainer()?");
}
if (topList == null || topList.isEmpty()) {
return;
}
Collections.reverse(topList);
data.addAll(0, topList);
notifyItemRangeInserted(0, topList.size());
}
/**
* To call {@link TwoWayEndlessAdapter.Callback#onBottomReached()} before the exact number of items to when the bottom is reached.
* @see this.bottomAdvanceCallback
* @see Callback
* */
public void setBottomAdvanceCallback(int bottomAdvance)
{
if (bottomAdvance < 0) {
throw new IndexOutOfBoundsException("Invalid index, bottom index must be greater than 0");
}
bottomAdvanceCallback = bottomAdvance;
}
/**
* Provide an instance of {@link Map} where the data will be stored.
* */
public void setDataContainer(List<DataItem> data)
{
this.data = data;
}
/**
* Called by RecyclerView to display the data at the specified position. This method should
* update the contents of the {@link RecyclerView.ViewHolder#itemView} to reflect the item at
* the given position.
* <p>
* Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
* again if the position of the item changes in the data set unless the item itself is
* invalidated or the new position cannot be determined. For this reason, you should only
* use the <code>position</code> parameter while acquiring the related data item inside
* this method and should not keep a copy of it. If you need the position of an item later
* on (e.g. in a click listener), use {@link RecyclerView.ViewHolder#getAdapterPosition()} which
* will have the updated adapter position.
*
* Any class that extends from {@link TwoWayEndlessAdapter} should not Override this method but
* should Override {@link #onBindViewHolder(VH, DataItem, int)} instead.
*
* @param holder The ViewHolder which should be updated to represent the contents of the
* item at the given position in the data set.
* @param position The position of the item within the adapter's data set.
*/
@Override
public void onBindViewHolder(VH holder, int position)
{
EndlessLogger.logD("onBindViewHolder() for position : " + position);
onBindViewHolder(holder, data.get(position), position);
if (position == 0 && !isFirstBind) {
notifyTopReached();
}
else if ((position + bottomAdvanceCallback) >= (getItemCount() - 1)) {
notifyBottomReached();
}
isFirstBind = false;
}
/**
* Called by {@link TwoWayEndlessAdapter} to display the data at the specified position. This
* method should update the contents of the {@link RecyclerView.ViewHolder#itemView} to reflect
* the item at the given position.
* <p>
* Note that unlike {@link android.widget.ListView}, {@link TwoWayEndlessAdapter} will not call
* this method again if the position of the item changes in the data set unless the item itself
* is invalidated or the new position cannot be determined. For this reason, you should only
* use the <code>position</code> parameter while acquiring/verifying the related data item
* inside this method and should not keep a copy of it. If you need the position of an item
* later on (e.g. in a click listener), use {@link RecyclerView.ViewHolder#getAdapterPosition()}
* which will have the updated adapter position.
*
* Any class that extends from {@link TwoWayEndlessAdapter} must Override this method.
*
* @param holder The ViewHolder which should be updated to represent the contents of the
* item at the given position in the data set.
* @param data The data class object associated with the corresponding position which contains
* the updated content that represents the item at the given position in the data
* set.
* @param position The position of the item within the adapter's data set.
*/
public abstract void onBindViewHolder(VH holder, DataItem data, int position);
/**
* Sends the {@link Callback#onTopReached} callback if provided.
* */
protected void notifyTopReached()
{
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (mEndlessCallback != null) {
mEndlessCallback.onTopReached();
}
}
}, 50);
}
/**
* Sends the {@link Callback#onBottomReached} callback if provided.
* */
protected void notifyBottomReached()
{
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (mEndlessCallback != null) {
mEndlessCallback.onBottomReached();
}
}
}, 50);
}
/**
* The {@link TwoWayEndlessAdapter.Callback} class provides an interface notify when bottom or
* top of the list is reached.
*/
public interface Callback {
/**
* To be called when the first item of the {@link RecyclerView}'s data adapter is bounded to
* the view.
* Except the first time.
* */
void onTopReached();
/**
* To be called when the last item of the {@link RecyclerView}'s data adapter is bounded to
* the view.
* Except the first time.
* */
void onBottomReached();
}
}
上述类的实现可以是。
<强> TwoWayEndlessAdapterImp.java 强>
public class TwoWayEndlessAdapterImp<VH extends RecyclerView.ViewHolder> extends TwoWayEndlessAdapter<VH, ValueItem> {
@Override
public int getItemViewType(int position)
{
return R.layout.item_layout;
}
@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType)
{
View itemViewLayout = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
switch (viewType)
{
case R.layout.item_layout:
return (VH) new ItemLayoutViewHolder(itemViewLayout);
default:
return null;
}
}
@Override
public void onBindViewHolder(VH holder, ValueItem item, int position)
{
switch (getItemViewType(position)) {
case R.layout.item_layout:
ItemLayoutViewHolder viewHolder = (ItemLayoutViewHolder) holder;
viewHolder.textView.setText(item.data);
break;
}
}
@Override
public int getItemCount()
{
return data == null ? 0 : data.size();
}
}
使用TwoWayEndlessAdapter
TwoWayEndlessAdapterImp endlessAdapter = new TwoWayEndlessAdapterImp<>();
endlessAdapter.setDataContainer(new ArrayList<DataItem>());
endlessAdapter.setEndlessCallback(this);
最后致电addItemsAtBottom(list);
以在底部添加新项目并致电addItemsAtTop(list);
,仅在顶部添加项目。
答案 2 :(得分:0)
正确的方法是使用Paging Library。
在此之前,我在制作必须进行双向滚动的排行榜时遇到了同样的问题。我使用<bean id="jdbcTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"/>
</bean>
和RecyclerView.OnScrollListener
提供的一些方法解决了这个问题。
LinearLayoutManager
然后,当向适配器添加元素时,我必须对它们进行排序(就我而言,这很容易,因为我正在排行榜)。所以在我的适配器中:
private LinearLayoutManager mLinearLayoutManager;
private RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (mLinearLayoutManager.findLastVisibleItemPosition() == mFullLeaderboardAdapter.getItemCount() - 1) {
//GET DATA HERE
} else if (mLinearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
//GET DATA HERE
}
super.onScrolled(recyclerView, dx, dy);
}
};
这有效(您必须处理所有必须禁用滚动侦听器的情况),但是如果可以的话,我建议您使用Google的分页库。
答案 3 :(得分:0)
只需使用以下代码:
LinearLayoutManager linearLayoutManager=new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false);
mRecycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
if((newState==RecyclerView.SCROLL_STATE_IDLE)&&(linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0)&&(linearLayoutManager.findLastCompletelyVisibleItemPosition()!=linearLayoutManager.getItemCount()-1)){
Toast.makeText(ChatPageActivity.this, "start", Toast.LENGTH_SHORT).show();
}
}