android - more - ¿Cómo implementar una lista interminable con RecyclerView?
pagination android (30)
He creado LoadMoreRecyclerView utilizando Abdulaziz Noor Answer
LoadMoreRecyclerView
public class LoadMoreRecyclerView extends RecyclerView {
private boolean loading = true;
int pastVisiblesItems, visibleItemCount, totalItemCount;
//WrapperLinearLayout is for handling crash in RecyclerView
private WrapperLinearLayout mLayoutManager;
private Context mContext;
private OnLoadMoreListener onLoadMoreListener;
public LoadMoreRecyclerView(Context context) {
super(context);
mContext = context;
init();
}
public LoadMoreRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
public LoadMoreRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
init();
}
private void init(){
mLayoutManager = new WrapperLinearLayout(mContext,LinearLayoutManager.VERTICAL,false);
this.setLayoutManager(mLayoutManager);
this.setItemAnimator(new DefaultItemAnimator());
this.setHasFixedSize(true);
}
@Override
public void onScrolled(int dx, int dy) {
super.onScrolled(dx, dy);
if(dy > 0) //check for scroll down
{
visibleItemCount = mLayoutManager.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();
if (loading)
{
if ( (visibleItemCount + pastVisiblesItems) >= totalItemCount)
{
loading = false;
Log.v("...", "Call Load More !");
if(onLoadMoreListener != null){
onLoadMoreListener.onLoadMore();
}
//Do pagination.. i.e. fetch new data
}
}
}
}
@Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
}
public void onLoadMoreCompleted(){
loading = true;
}
public void setMoreLoading(boolean moreLoading){
loading = moreLoading;
}
public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
this.onLoadMoreListener = onLoadMoreListener;
}
}
WrapperLinearLayout
public class WrapperLinearLayout extends LinearLayoutManager
{
public WrapperLinearLayout(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
Log.e("probe", "meet a IOOBE in RecyclerView");
}
}
}
// Añadirlo en xml como
<your.package.LoadMoreRecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</your.package.LoadMoreRecyclerView>
OnCreate o onViewCreated
mLoadMoreRecyclerView = (LoadMoreRecyclerView) view.findViewById(R.id.recycler_view);
mLoadMoreRecyclerView.setOnLoadMoreListener(new OnLoadMoreListener() {
@Override
public void onLoadMore() {
callYourService(StartIndex);
}
});
llame a su servicio
private void callYourService(){
//callyour Service and get response in any List
List<AnyModelClass> newDataFromServer = getDataFromServerService();
//Enable Load More
mLoadMoreRecyclerView.onLoadMoreCompleted();
if(newDataFromServer != null && newDataFromServer.size() > 0){
StartIndex += newDataFromServer.size();
if (newDataFromServer.size() < Integer.valueOf(MAX_ROWS)) {
//StopLoading..
mLoadMoreRecyclerView.setMoreLoading(false);
}
}
else{
mLoadMoreRecyclerView.setMoreLoading(false);
mAdapter.notifyDataSetChanged();
}
}
Me gustaría cambiar ListView
a RecyclerView
. Quiero usar el onScroll
del OnScrollListener
en RecyclerView
para determinar si un usuario se desplazó al final de la lista.
¿Cómo puedo saber si un usuario se desplaza al final de la lista para poder obtener nuevos datos de un servicio REST?
Aquí hay otro enfoque. Funcionará con cualquier gestor de diseño.
- Hacer adaptador clase abstracta
- A continuación, cree un método abstracto en la clase de adaptador (por ejemplo, carga ())
- En onBindViewHolder verifique la posición si es la última y llame a load ()
- Anule la función load () al crear el objeto adaptador en su actividad o fragmento.
- En la función de carga sobredimensionada, implemente su llamada loadmore
Para una comprensión detallada, escribí una publicación de blog y un proyecto de ejemplo, consíguelo aquí http://sab99r.com/blog/recyclerview-endless-load-more/
MyAdapter.java
public abstract class MyAdapter extends RecyclerView.Adapter<ViewHolder>{
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
//check for last item
if ((position >= getItemCount() - 1))
load();
}
public abstract void load();
}
MyActivity.java
public class MainActivity extends AppCompatActivity {
List<Items> items;
MyAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
adapter=new MyAdapter(items){
@Override
public void load() {
//implement your load more here
Item lastItem=items.get(items.size()-1);
loadMore();
}
};
}
}
Aquí mi solución que utiliza AsyncListUtil , en la web dice: Tenga en cuenta que esta clase utiliza un solo hilo para cargar los datos, por lo que es adecuado para cargar datos desde un almacenamiento secundario como el disco, pero no desde la red. pero estoy usando odata para leer los datos y trabajar bien. Extraño en mi ejemplo las entidades de datos y los métodos de red. Incluyo solo el adaptador de ejemplo.
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();
}
});
}
}
}
Aunque hay tantas respuestas a la pregunta, me gustaría compartir nuestra experiencia de crear la vista de lista sin fin. Recientemente hemos implementado Carousel LayoutManager personalizado que puede funcionar en el ciclo desplazando la lista infinitamente y hasta cierto punto. Aquí hay una descripción detallada en GitHub .
Le sugiero que eche un vistazo a este artículo con recomendaciones breves pero valiosas sobre la creación de administradores de Layout personalizados: http://cases.azoft.com/create-custom-layoutmanager-android/
Aunque la respuesta aceptada funciona perfectamente, la siguiente solución utiliza addOnScrollListener ya que setOnScrollListener está en desuso, y reduce el número de variables y condiciones.
final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
feedsRecyclerView.setLayoutManager(layoutManager);
feedsRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {
if ((layoutManager.getChildCount() + layoutManager.findFirstVisibleItemPosition()) >= layoutManager.getItemCount()) {
Log.d("TAG", "End of list");
//loadMore();
}
}
}
});
Con el poder de las funciones de extensión de Kotlin, el código puede parecer mucho más elegante. Coloca esto donde quieras (lo tengo dentro de un archivo 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
}
}
}
})
}
Y luego úsalo así:
youRecyclerView.addOnScrolledToEnd {
//What you want to do once the end is reached
}
Esta solución se basa en la respuesta de Kushal Sharma. Sin embargo, esto es un poco mejor porque:
- Utiliza
onScrollStateChanged
lugar deonScroll
. Esto es mejor porque se llama aonScroll
cada vez que hay algún tipo de movimiento en RecyclerView, mientras que aonScrollStateChanged
solo se llama cuando se cambia el estado de RecyclerView. El uso deonScrollStateChanged
le ahorrará tiempo de CPU y, como consecuencia, la batería. - Como esto utiliza funciones de extensión, esto se puede usar en cualquier RecyclerView que tenga. El código del cliente es solo 1 línea.
Gracias a @Kushal y así lo implementé.
private boolean loading = true;
int pastVisiblesItems, visibleItemCount, totalItemCount;
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
{
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy)
{
if(dy > 0) //check for scroll down
{
visibleItemCount = mLayoutManager.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();
if (loading)
{
if ( (visibleItemCount + pastVisiblesItems) >= totalItemCount)
{
loading = false;
Log.v("...", "Last Item Wow !");
//Do pagination.. i.e. fetch new data
}
}
}
}
});
No olvides añadir
LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
Hay una biblioteca fácil de usar para esta página nombrada. Admite tanto ListView como RecyclerView (LinearLayout, GridLayout y StaggeredGridLayout).
Aquí está el github.com/MarkoMilos/Paginate al proyecto en Github.
Haz estas variables.
private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold = 5;
int firstVisibleItem, visibleItemCount, totalItemCount;
Establecer en desplazamiento para la vista de reciclador.
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = mRecyclerView.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount)
<= (firstVisibleItem + visibleThreshold)) {
// End has been reached
Log.i("Yaeye!", "end called");
// Do something
loading = true;
}
}
});
Nota: asegúrese de que está utilizando
LinearLayoutManager
como administrador de diseño paraRecyclerView
.
LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
y para una grilla
GridLayoutManager mLayoutManager;
mLayoutManager = new GridLayoutManager(getActivity(), spanCount);
mRecyclerView.setLayoutManager(mLayoutManager);
¡Diviértete con tus pergaminos sin fin! ^. ^
Actualización: mRecyclerView.
setOnScrollListener ()está en desuso, simplemente reemplace conmRecyclerView.addOnScrollListener()
y la advertencia desaparecerá. Puedes leer más de esta pregunta SO .
Ya que Android ahora es oficialmente compatible con Kotlin, aquí hay una actualización para el mismo:
Hacer OnScrollListener
class OnScrollListener(val layoutManager: LinearLayoutManager, val adapter: RecyclerView.Adapter<RecyclerAdapter.ViewHolder>, val dataList: MutableList<Int>) : RecyclerView.OnScrollListener() {
var previousTotal = 0
var loading = true
val visibleThreshold = 10
var firstVisibleItem = 0
var visibleItemCount = 0
var totalItemCount = 0
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
visibleItemCount = recyclerView.childCount
totalItemCount = layoutManager.itemCount
firstVisibleItem = layoutManager.findFirstVisibleItemPosition()
if (loading) {
if (totalItemCount > previousTotal) {
loading = false
previousTotal = totalItemCount
}
}
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
val initialSize = dataList.size
updateDataList(dataList)
val updatedSize = dataList.size
recyclerView.post { adapter.notifyItemRangeInserted(initialSize, updatedSize) }
loading = true
}
}
}
y añádelo a tu RecyclerView como este
recyclerView.addOnScrollListener(OnScrollListener(layoutManager, adapter, dataList))
Para un ejemplo de código completo, no dude en consultar este repositorio de Github .
Intentaría extender el LayoutManager
usado (por ejemplo, LinearLayoutManager
) y anular el método scrollVerticallyBy()
. En primer lugar, llamaría super
primero y luego verificaría el valor entero devuelto. Si el valor es igual a 0
, se alcanza un borde inferior o superior. Luego usaría el método findLastVisibleItemPosition()
para averiguar qué borde se alcanza y cargar más datos si es necesario. Solo una idea.
Además, incluso puede devolver su valor de ese método permitiendo el desplazamiento excesivo y mostrando el indicador de "carga".
La mayoría de las respuestas se LinearLayoutManager
el supuesto de que RecyclerView
utiliza un LinearLayoutManager
o GridLayoutManager
, o incluso StaggeredGridLayoutManager
, o suponiendo que el desplazamiento es vertical o horyzontal, pero nadie ha publicado una respuesta completamente genérica .
Usar el adaptador de ViewHolder
claramente no es una buena solución. Un adaptador puede tener más de 1 RecyclerView
usándolo. Se "adapta" a sus contenidos. Debería ser la Vista del reciclador (que es la única clase responsable de lo que se muestra actualmente al usuario, y no el adaptador que solo es responsable de proporcionar contenido al RecyclerView
) que debe notificar a su sistema que se necesitan más elementos ( cargar).
Aquí está mi solución, usando nada más que las clases abstraídas de RecyclerView (RecycerView.LayoutManager y RecycerView.Adapter):
/**
* Listener to callback when the last item of the adpater is visible to the user.
* It should then be the time to load more items.
**/
public abstract class LastItemListener extends RecyclerView.OnScrollListener {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// init
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
RecyclerView.Adapter adapter = recyclerView.getAdapter();
if (layoutManager.getChildCount() > 0) {
// Calculations..
int indexOfLastItemViewVisible = layoutManager.getChildCount() -1;
View lastItemViewVisible = layoutManager.getChildAt(indexOfLastItemViewVisible);
int adapterPosition = layoutManager.getPosition(lastItemViewVisible);
boolean isLastItemVisible = (adapterPosition == adapter.getItemCount() -1);
// check
if (isLastItemVisible)
onLastItemVisible(); // callback
}
}
/**
* Here you should load more items because user is seeing the last item of the list.
* Advice: you should add a bollean value to the class
* so that the method {@link #onLastItemVisible()} will be triggered only once
* and not every time the user touch the screen ;)
**/
public abstract void onLastItemVisible();
}
// --- Exemple of use ---
myRecyclerView.setOnScrollListener(new LastItemListener() {
public void onLastItemVisible() {
// start to load more items here.
}
}
Mi forma de detectar el evento de carga no es detectar el desplazamiento, sino escuchar si se adjuntó la última vista. Si se adjuntó la última vista, considero que es un momento para cargar más contenido.
class MyListener implements RecyclerView.OnChildAttachStateChangeListener {
RecyclerView mRecyclerView;
MyListener(RecyclerView view) {
mRecyclerView = view;
}
@Override
public void onChildViewAttachedToWindow(View view) {
RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
RecyclerView.LayoutManager mgr = mRecyclerView.getLayoutManager();
int adapterPosition = mgr.getPosition(view);
if (adapterPosition == adapter.getItemCount() - 1) {
// last view was attached
loadMoreContent();
}
@Override
public void onChildViewDetachedFromWindow(View view) {}
}
Mi respuesta es una versión modificada de Noor. Pasé de un ListView
donde tenía EndlessScrollListener
(que puedes encontrar fácilmente en muchas respuestas en SO) a un RecyclerView
así que quería un EndlessRecyclScrollListener
para actualizar fácilmente mi oyente anterior.
Así que aquí está el código, espero que ayude:
public abstract class EndlessScrollRecyclListener extends RecyclerView.OnScrollListener
{
// The total number of items in the dataset after the last load
private int previousTotalItemCount = 0;
private boolean loading = true;
private int visibleThreshold = 5;
int firstVisibleItem, visibleItemCount, totalItemCount;
private int startingPageIndex = 0;
private int currentPage = 0;
@Override
public void onScrolled(RecyclerView mRecyclerView, int dx, int dy)
{
super.onScrolled(mRecyclerView, dx, dy);
LinearLayoutManager mLayoutManager = (LinearLayoutManager) mRecyclerView
.getLayoutManager();
visibleItemCount = mRecyclerView.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
onScroll(firstVisibleItem, visibleItemCount, totalItemCount);
}
public void onScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
// If the total item count is zero and the previous isn''t, assume the
// list is invalidated and should be reset back to initial state
if (totalItemCount < previousTotalItemCount)
{
this.currentPage = this.startingPageIndex;
this.previousTotalItemCount = totalItemCount;
if (totalItemCount == 0)
{
this.loading = true;
}
}
// If it’s still loading, we check to see if the dataset count has
// changed, if so we conclude it has finished loading and update the current page
// number and total item count.
if (loading && (totalItemCount > previousTotalItemCount))
{
loading = false;
previousTotalItemCount = totalItemCount;
currentPage++;
}
// If it isn’t currently loading, we check to see if we have breached
// the visibleThreshold and need to reload more data.
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem +
visibleThreshold))
{
onLoadMore(currentPage + 1, totalItemCount);
loading = true;
}
}
// Defines the process for actually loading more data based on page
public abstract void onLoadMore(int page, int totalItemsCount);
}
OK, lo hice utilizando el método onBindViewHolder de RecyclerView.Adapter.
Adaptador:
public interface OnViewHolderListener {
void onRequestedLastItem();
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
...
if (position == getItemCount() - 1) onViewHolderListener.onRequestedLastItem();
}
Fragmento (o Actividad) :
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
contentView = inflater.inflate(R.layout.comments_list, container, false);
recyclerView = (RecyclerView) mContentView.findViewById(R.id.my_recycler_view);
adapter = new Adapter();
recyclerView.setAdapter(adapter);
...
adapter.setOnViewHolderListener(new Adapter.OnViewHolderListener() {
@Override
public void onRequestedLastItem() {
//TODO fetch new data from webservice
}
});
return contentView;
}
Para aquellos que solo desean recibir una notificación cuando el último elemento se muestra totalmente, puede usar View.canScrollVertically()
.
Aquí está mi implementación:
public abstract class OnVerticalScrollListener
extends RecyclerView.OnScrollListener {
@Override
public final void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (!recyclerView.canScrollVertically(-1)) {
onScrolledToTop();
} else if (!recyclerView.canScrollVertically(1)) {
onScrolledToBottom();
} else if (dy < 0) {
onScrolledUp();
} else if (dy > 0) {
onScrolledDown();
}
}
public void onScrolledUp() {}
public void onScrolledDown() {}
public void onScrolledToTop() {}
public void onScrolledToBottom() {}
}
Nota: puede utilizar recyclerView.getLayoutManager().canScrollVertically()
si desea admitir API <14.
Para las personas que usan StaggeredGridLayoutManager aquí está mi implementación, funciona para mí.
private class ScrollListener extends RecyclerView.OnScrollListener {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
firstVivisibleItems = mLayoutManager.findFirstVisibleItemPositions(firstVivisibleItems);
if(!recyclerView.canScrollVertically(1) && firstVivisibleItems[0]!=0) {
loadMoreImages();
}
}
private boolean loadMoreImages(){
Log.d("myTag", "LAST-------HERE------");
return true;
}
}
Para mí, es muy simple:
private boolean mLoading = false;
mList.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int totalItem = mLinearLayoutManager.getItemCount();
int lastVisibleItem = mLinearLayoutManager.findLastVisibleItemPosition();
if (!mLoading && lastVisibleItem == totalItem - 1) {
mLoading = true;
// Scrolled to bottom. Do something here.
mLoading = false;
}
}
});
Tenga cuidado con los trabajos asíncronos: mLoading debe cambiarse al final de los trabajos asíncronos. ¡Espero que sea de ayuda!
Tengo un ejemplo bastante detallado de cómo paginar con un RecyclerView. En un nivel alto, tengo un conjunto PAGE_SIZE, digamos 30. Por lo tanto, solicito 30 artículos y, si vuelvo a obtener 30, solicito la página siguiente. Si obtengo menos de 30 elementos, señalo una variable para indicar que se ha alcanzado la última página y luego dejo de solicitar más páginas. Compruébalo y déjame saber lo que piensas.
https://medium.com/@etiennelawlor/pagination-with-recyclerview-1cb7e66a502b
onBindViewHolder
una implementación de tipo de desplazamiento infinito usando esta lógica en el método onBindViewHolder
de mi clase RecyclerView.Adapter
.
if (position == mItems.size() - 1 && mCurrentPage <= mTotalPageCount) {
if (mCurrentPage == mTotalPageCount) {
mLoadImagesListener.noMorePages();
} else {
int newPage = mCurrentPage + 1;
mLoadImagesListener.loadPage(newPage);
}
}
Con este código cuando RecyclerView llega al último elemento, incrementa la página actual y las devoluciones de llamada en una interfaz que es responsable de cargar más datos desde la API y de agregar los nuevos resultados al adaptador.
Puedo publicar un ejemplo más completo si esto no está claro?
Así es como lo hago, simple y corto:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
{
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy)
{
if(!recyclerView.canScrollVertically(1) && dy != 0)
{
// Load more results here
}
}
});
@erdna Por favor, consulte mi código a continuación. Puede ser que sea útil para usted.
int firstVisibleItem, visibleItemCount, totalItemCount;
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = layoutManager.getChildCount();
totalItemCount = layoutManager.getItemCount();
firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
Log.e("firstVisibleItem", firstVisibleItem + "");
Log.e("visibleItemCount", visibleItemCount + "");
Log.e("totalItemCount", totalItemCount + "");
if (page != total_page_index) {
if (loading) {
if ((visibleItemCount + firstVisibleItem) >= totalItemCount) {
Log.e("page", String.valueOf(page));
page=page+1;
new GetSummary().execute(String.valueOf(page), "");
loading = false;
}
}
}
}
});
@kushal @abdulaziz
¿Por qué no usar esta lógica en su lugar?
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int totalItemCount, lastVisibleItemPosition;
if (dy > 0) {
totalItemCount = _layoutManager.getItemCount();
lastVisibleItemPosition = _layoutManager.findLastVisibleItemPosition();
if (!_isLastItem) {
if ((totalItemCount - 1) == lastVisibleItemPosition) {
LogUtil.e("end_of_list");
_isLastItem = true;
}
}
}
}
Esta solución funciona perfectamente para mí.
//Listener
public abstract class InfiniteScrollListener extendsRecyclerView.OnScrollListener {
public static String TAG = InfiniteScrollListener.class.getSimpleName();
int firstVisibleItem, visibleItemCount, totalItemCount;
private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold = 1;
private int current_page = 1;
private LinearLayoutManager mLinearLayoutManager;
public InfiniteScrollListener(LinearLayoutManager linearLayoutManager) {
this.mLinearLayoutManager = linearLayoutManager;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = recyclerView.getChildCount();
totalItemCount = mLinearLayoutManager.getItemCount();
firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount - firstVisibleItem <= visibleThreshold)) {
current_page++;
onLoadMore(current_page);
loading = true;
}
}
public void resetState() {
loading = true;
previousTotal = 0;
current_page = 1;
}
public abstract void onLoadMore(int current_page);
}
//Implementation into fragment
private InfiniteScrollListener scrollListener;
scrollListener = new InfiniteScrollListener(manager) {
@Override
public void onLoadMore(int current_page) {
//Load data
}
};
rv.setLayoutManager(manager);
rv.addOnScrollListener(scrollListener);
Ninguna de estas respuestas tiene en cuenta si la lista es demasiado pequeña o no.
Aquí hay un fragmento de código que he estado usando que funciona en RecycleViews en ambas direcciones.
@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();
}
}
});
}
Pruebe a continuación:
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutManager;
/**
* Abstract Endless ScrollListener
*
*/
public abstract class EndlessScrollListener extends
RecyclerView.OnScrollListener {
// The minimum amount of items to have below your current scroll position
// before loading more.
private int visibleThreshold = 10;
// The current offset index of data you have loaded
private int currentPage = 1;
// True if we are still waiting for the last set of data to load.
private boolean loading = true;
// The total number of items in the data set after the last load
private int previousTotal = 0;
private int firstVisibleItem;
private int visibleItemCount;
private int totalItemCount;
private LayoutManager layoutManager;
public EndlessScrollListener(LayoutManager layoutManager) {
validateLayoutManager(layoutManager);
this.layoutManager = layoutManager;
}
public EndlessScrollListener(int visibleThreshold,
LayoutManager layoutManager, int startPage) {
validateLayoutManager(layoutManager);
this.visibleThreshold = visibleThreshold;
this.layoutManager = layoutManager;
this.currentPage = startPage;
}
private void validateLayoutManager(LayoutManager layoutManager)
throws IllegalArgumentException {
if (null == layoutManager
|| !(layoutManager instanceof GridLayoutManager)
|| !(layoutManager instanceof LinearLayoutManager)) {
throw new IllegalArgumentException(
"LayoutManager must be of type GridLayoutManager or LinearLayoutManager.");
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = recyclerView.getChildCount();
totalItemCount = layoutManager.getItemCount();
if (layoutManager instanceof GridLayoutManager) {
firstVisibleItem = ((GridLayoutManager) layoutManager)
.findFirstVisibleItemPosition();
} else if (layoutManager instanceof LinearLayoutManager) {
firstVisibleItem = ((LinearLayoutManager) layoutManager)
.findFirstVisibleItemPosition();
}
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading
&& (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
// End has been reached do something
currentPage++;
onLoadMore(currentPage);
loading = true;
}
}
// Defines the process for actually loading more data based on page
public abstract void onLoadMore(int page);
}
Compruebe que todo esto se explica en detalle: Paginación usando RecyclerView De la A a la Z
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView,
int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = mLayoutManager.getChildCount();
int totalItemCount = mLayoutManager.getItemCount();
int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();
if (!mIsLoading && !mIsLastPage) {
if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
&& firstVisibleItemPosition >= 0) {
loadMoreItems();
}
}
}
})
loadMoreItems ():
private void loadMoreItems() {
mAdapter.removeLoading();
//load data here from the server
// in case of success
mAdapter.addData(data);
// if there might be more data
mAdapter.addLoading();
}
en MyAdapter:
private boolean mIsLoadingFooterAdded = false;
public void addLoading() {
if (!mIsLoadingFooterAdded) {
mIsLoadingFooterAdded = true;
mLineItemList.add(new LineItem());
notifyItemInserted(mLineItemList.size() - 1);
}
}
public void removeLoading() {
if (mIsLoadingFooterAdded) {
mIsLoadingFooterAdded = false;
int position = mLineItemList.size() - 1;
LineItem item = mLineItemList.get(position);
if (item != null) {
mLineItemList.remove(position);
notifyItemRemoved(position);
}
}
}
public void addData(List<YourDataClass> data) {
for (int i = 0; i < data.size(); i++) {
YourDataClass yourDataObject = data.get(i);
mLineItemList.add(new LineItem(yourDataObject));
notifyItemInserted(mLineItemList.size() - 1);
}
}
También es posible implementarlo sin el detector de desplazamiento, utilizando solo la lógica pura del modelo de datos. La vista de desplazamiento requiere obtener elementos por posición, así como el recuento máximo de elementos. El modelo puede tener la lógica de fondo para recuperar los elementos necesarios en trozos, en lugar de uno por uno, y hacer esto en el hilo de fondo, notificando a la vista cuando los datos están listos.
Este enfoque permite tener la cola de recuperación que prefiere los elementos solicitados más recientemente (por lo tanto, actualmente visibles) sobre los envíos más antiguos (lo más probable es que ya se hayan desplazado), controlan la cantidad de subprocesos paralelos que se utilizan y cosas similares. El código fuente completo para este enfoque (aplicación de demostración y biblioteca reutilizable) está disponible here .
Te dejo mi aproximación. Funciona bien para mi
Espero que te ayude.
/**
* Created by Daniel Pardo Ligorred on 03/03/2016.
*/
public abstract class BaseScrollListener extends RecyclerView.OnScrollListener {
protected RecyclerView.LayoutManager layoutManager;
public BaseScrollListener(RecyclerView.LayoutManager layoutManager) {
this.layoutManager = layoutManager;
this.init();
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
this.onScroll(recyclerView, this.getFirstVisibleItem(), this.layoutManager.getChildCount(), this.layoutManager.getItemCount(), dx, dy);
}
private int getFirstVisibleItem(){
if(this.layoutManager instanceof LinearLayoutManager){
return ((LinearLayoutManager) this.layoutManager).findFirstVisibleItemPosition();
} else if (this.layoutManager instanceof StaggeredGridLayoutManager){
int[] spanPositions = null; //Should be null -> StaggeredGridLayoutManager.findFirstVisibleItemPositions makes the work.
try{
return ((StaggeredGridLayoutManager) this.layoutManager).findFirstVisibleItemPositions(spanPositions)[0];
}catch (Exception ex){
// Do stuff...
}
}
return 0;
}
public abstract void init();
protected abstract void onScroll(RecyclerView recyclerView, int firstVisibleItem, int visibleItemCount, int totalItemCount, int dx, int dy);
}
recyclerList.setOnScrollListener(new RecyclerView.OnScrollListener()
{
@Override
public void onScrolled(RecyclerView recyclerView, int dx,int dy)
{
super.onScrolled(recyclerView, dx, dy);
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView,int newState)
{
int totalItemCount = layoutManager.getItemCount();
int lastVisibleItem = layoutManager.findLastVisibleItemPosition();
if (totalItemCount> 1)
{
if (lastVisibleItem >= totalItemCount - 1)
{
// End has been reached
// do something
}
}
}
});
if (layoutManager.findLastCompletelyVisibleItemPosition() ==
recyclerAdapter.getItemCount() - 1) {
//load more items.
}
Justo y sencillo Esto funcionará.