RecyclerView onBindViewHolder sólo se llama una vez dentro del diseño de la pestaña
Tengo cuatro pestañas y cuatro fragmentos (cada uno por cada pestaña).
Cada fragmento tiene una vista de reciclador vertical. Dado que todos los fragmentos de vista se ven similares Estoy reutilizando el mismo archivo de diseño, los mismos elementos de vista de reciclador y el mismo adaptador.
- Android: Listview se duplica cuando inicie una nueva actividad y presiona atrás para volver a ella
- No se pueden intercambiar fragmentos correctamente en las pestañas de la barra de acciones
- Ficha de Android como pie de página
- Cómo crear una nueva línea o pestaña en <string> XML (eclipse / android)?
- Actualizar / Recargar / Re instanciar un fragmento en un sistema de pestañas android
El problema es que solo se carga un elemento bajo la primera pestaña y la tercera pestaña y la cuarta, mientras que la segunda pestaña carga con éxito todos los datos.
Espero que la imagen añadida a continuación da una mejor comprensión sobre el tema.
Aquí está mi código del adaptador
public class OthersAdapter extends RecyclerView.Adapter<OthersAdapter.OthersViewHolder> { private final Context context; private final ArrayList<LocalDealsDataFields> othersDataArray; private LayoutInflater layoutInflater; public OthersAdapter(Context context, ArrayList<LocalDealsDataFields> othersDataArray) { this.context = context; this.othersDataArray = othersDataArray; if (this.context != null) { layoutInflater = LayoutInflater.from(this.context); } } class OthersViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { TextView othersSmallTitleTextView; ImageView othersImageView; OthersViewHolder(View itemView) { super(itemView); othersSmallTitleTextView = (TextView) itemView.findViewById(R.id.others_small_title); othersImageView = (ImageView) itemView.findViewById(R.id.others_image); itemView.setOnClickListener(this); } @Override public void onClick(View view) { Intent couponDetailsItem = new Intent(context, LocalDealsActivity.class); Bundle extras = new Bundle(); extras.putString(Constants.SECTION_NAME, context.getString(R.string.local_deals_section_title)); // Add the offer id to the extras. This will be used to retrieve the coupon details // in the next activity extras.putInt(Constants.COUPONS_OFFER_ID, othersDataArray.get( getAdapterPosition()).getLocalDealId()); couponDetailsItem.putExtras(extras); context.startActivity(couponDetailsItem); } } @Override public OthersViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = layoutInflater.inflate(R.layout.others_items, parent, false); return new OthersViewHolder(view); } @Override public void onBindViewHolder(OthersViewHolder holder, int position) { String lfImage = othersDataArray.get(position).getLocalDealImage(); String lfCategoryName = othersDataArray.get(position).getLocalDealSecondTitle(); if (lfCategoryName != null) { // Set the second title holder.othersSmallTitleTextView.setText(lfCategoryName); } if (lfImage != null) { if (!lfImage.isEmpty()) { // Get the Uri Uri lfUriImage = Uri.parse(lfImage); // Load the Image Picasso.with(context).load(lfUriImage).into(holder.othersImageView); } } } @Override public int getItemCount() { return othersDataArray.size(); } }
Me gusta señalar un par de cosas –
-
He comprobado otras respuestas sobre el desbordamiento de pila. Hablan de configurar la vista de reciclaje
layout_height
parawrap_content
. Este no es el problema ya que ellayout_height
ya eswrap_content
y también la segunda pestaña carga todos los datos como se esperaba. -
Y algunas otras respuestas mencionadas usaron las mismas versiones para todas las bibliotecas de soporte y ya estoy usando la versión 25.1.0 para todas las bibliotecas de soporte.
-
El tamaño de la matriz de datos es 20 y devuelve 20 del método
getItemCount()
del adaptador. -
La matriz de datos tiene el número esperado de elementos en ella y no son nulos o vacíos.
-
Clean build, invalidate / caches tampoco funciona.
-
Por último, estoy usando
FragmentStatePagerAdapter
para cargar los fragmentos cuando las pestañas están en foco.
EDITAR:
Esta es la forma en que estoy analizando los datos JSON recibidos
private void parseLocalDeals(String stringResponse) throws JSONException { JSONArray localJSONArray = new JSONArray(stringResponse); // If the array length is less than 10 then display to the end of the JSON data or else // display 10 items. int localArrayLength = localJSONArray.length() <= 20 ? localJSONArray.length() : 20; for (int i = 0; i < localArrayLength; i++) { // Initialize Temporary variables int localProductId = 0; String localSecondTitle = null; String localImageUrlString = null; JSONObject localJSONObject = localJSONArray.getJSONObject(i); if (localJSONObject.has(JSONKeys.KEY_LOCAL_DEAL_ID)) { localProductId = localJSONObject.getInt(JSONKeys.KEY_LOCAL_DEAL_ID); } if (localJSONObject.has(JSONKeys.KEY_LOCAL_DEAL_CATEGORY)) { localSecondTitle = localJSONObject.getString(JSONKeys.KEY_LOCAL_DEAL_CATEGORY); } if (localJSONObject.has(JSONKeys.KEY_LOCAL_DEAL_IMAGE)) { localImageUrlString = localJSONObject.getString(JSONKeys.KEY_LOCAL_DEAL_IMAGE); } if (localImageUrlString != null) { if (!localImageUrlString.isEmpty()) { // Remove the dots at the start of the Product Image String while (localImageUrlString.charAt(0) == '.') { localImageUrlString = localImageUrlString.replaceFirst(".", ""); } // Replace the spaces in the url with %20 (useful if there is any) localImageUrlString = localImageUrlString.replaceAll(" ", "%20"); } } LocalDealsDataFields localDealsData = new LocalDealsDataFields(); localDealsData.setLocalDealId(localProductId); localDealsData.setLocalDealSecondTitle(localSecondTitle); localDealsData.setLocalDealImage(localImageUrlString); localDealsDataArray.add(localDealsData); } // Initialize the Local Deals List only once and notify the adapter that data set has changed // from second time. If you initializeRV the localDealsRVAdapter at an early instance and only // use the notifyDataSetChanged method here then the adapter doesn't update the data. This is // because the adapter won't update items if the number of previously populated items is zero. if (localDealsCount == 0) { if (localArrayLength != 0) { // Populate the Local Deals list // Specify an adapter localDealsRVAdapter = new OthersAdapter(context, localDealsDataArray); localDealsRecyclerView.setAdapter(localDealsRVAdapter); } else { // localArrayLength is 0; which means there are no rv elements to show. // So, remove the layout contentMain.setVisibility(View.GONE); // Show no results layout showNoResultsIfNoData(localArrayLength); } } else { // Notify the adapter that data set has changed localDealsRVAdapter.notifyDataSetChanged(); } // Increase the count since parsing the first set of results are returned localDealsCount = localDealsCount + 20; // Remove the progress bar and show the content prcVisibility.success(); }
parseLocalDeals
método parseLocalDeals
está dentro de una clase auxiliar y se llama usando initializeHotels.initializeRV();
initializeRV()
inicializa la vista Recycler, realiza una llamada de red al servidor y los datos recibidos se pasan al método parseLocalDeals
. initializeHotels
siendo una variable de instancia de la clase Helper.
EDIT 2:
Para aquellos que quieran explorar el código en detalle, he movido la parte del código a otro proyecto y lo compartimos en Github. Aquí está el enlace https://github.com/gSrikar/TabLayout y para entender la jerarquía echa un vistazo al archivo README.
¿Puede alguien decirme lo que estoy perdiendo?
- No se encontró ningún identificador de recurso para el atributo 'layout_behavior' en el paquete
- Cómo actualizar el contenido de la ficha de fragmento en el botón de clic
- Android: conflicto de ID cuando se crean 3 pestañas o más
- Cómo ocultar las pestañas de Android utilizando Xamarin Formulario renderizador personalizado?
- Agregue los iconos al adaptador de las lengüetas que extiende FragmentPagerAdapter
- Las pestañas muestran el fragmento en blanco al volver a fragmentar la segunda vez
- ¿Cómo agregar divider en el estilo de TabLayout de android? TabLayout es nuevo en la biblioteca de soporte de diseño
- Cambiar el índice de la pestaña de fragmentos con pestañas mediante programación
No mucho de una respuesta, pero demasiado tiempo para un comentario.
He duplicado (casi) su código del adaptador y trabaja totalmente para mí. Creo que he hecho lo mismo que tú. Estoy utilizando el mismo archivo de diseño, el mismo elemento y el mismo adaptador para todas las pestañas. Creo que no hay problemas con el código del adaptador.
Digo "casi" porque tuve que cambiar un par de cosas ya que no tengo acceso a sus datos. Cambié su modelo de LocalDealsDataField
para incluir un BitmapDrawable y cambié onBindViewHolder()
para manejarlo.
BitmapDrawable lfImage = othersDataArray.get(position).getLocalDealImage(); holder.othersImageView.setBackground(lfImage);
Dado que no parece haber ningún problema con su adaptador, me centraría en obtener los datos o configurar el adaptador como su problema. Lo siento, no puedo ser de ayuda más allá de eso.
FYI, he aquí cómo configuro el adaptador en onCreateView()
rootView = inflater.inflate(R.layout.recycler_view, container, false); mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview); mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); mAdapter = new OthersAdapter(this.getContext(), list); mRecyclerView.setAdapter(mAdapter);
He mirado su código, el problema es igual que explicado por @ardock
Solución me gustaría proponer,
Tienes que cambiar tu código en 3 lugar ::
-
Dentro de todo el
Fragment
que está utilizando enViewPager
No llame ainitializeRESPECTIVEView()
del métodoonCreateView
. -
Dentro de
LocalFragment
hacer una lista de fragmentos que va a utilizar conViewPager
y pasarlo aBottomSectionsPagerAdapter
. Y devuelveFragment
de esa lista desdegetItem(int position)
deBottomSectionsPagerAdapter
. -
Agregue el siguiente código a
LocalFragment
dentro deuseSlidingTabViewPager()
.tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
` @Override public void onTabSelected(TabLayout.Tab tab) { } @Override public void onTabUnselected(TabLayout.Tab tab) { } @Override public void onTabReselected(TabLayout.Tab tab) { } });`
// Call Respective fragment método
initializeRESPECTIVEView()
deonTabSelected
, puede obtener la instancia de fragmento de la lista que pasó aBottomSectionsPagerAdapter
Resumen
Resolvió el problema de diseño en el punto 1 reemplazando un LinearLayout
por un RelativeLayout
, invirtiendo la lógica de visibilidad para evitar el efecto fantasma y capturando excepciones e impidiéndolas cuando no se encuentra la vista relacionada.
Se añadió el punto 2 para demostrar que el defecto visual sólo está presente en los dispositivos de Marshmallow y Nougat.
Finalmente, FragmentStatePagerAdapter
carga las páginas antes de obtener el foco, por lo que se propone una corrección en el punto 3 (cargar todas las páginas y actualizarlas cuando estén seleccionadas).
Más información en los comentarios a continuación y @ d4h respuesta .
La cuarta página no está usando el mismo diseño, sólo el mismo RecyclerView
e id
, tal vez un trabajo en progreso. El problema de diseño puede resolverse utilizando el mismo diseño que las páginas anteriores, pero considero este cambio fuera del alcance.
1. Parcialmente fijado para dispositivos Marshmallow y Nougat. Trabajo en progreso.
Update2 Cambiar LinearLayout por RelativeLayout e invertir la lógica de visibilidad resuelve el problema de diseño:
Actualización: Comentar initializeTrending
en todas las inicializaciones de fragmentos también funciona enApi23 +
Lo comprobaré más adelante, parece que las ofertas se cargan correctamente, pero las tendencias se cargan y las ofertas se pierden. WIP aquí .
Si la matriz de tendencias está vacía y la vista de tendencias ha desaparecido, las ofertas no se muestran , pero se muestran invisibles
2. Estás cargando una página incorrecta en los dispositivos de Marshmallow y Nougat
FragmentStatePagerAdapter primera llamada a getItem () incorrecta en dispositivos Nougat
Esto terminó por no tener nada que ver con el código FragmentStatePagerAdapter. Más bien, en mi fragmento, agarré un objeto almacenado de una matriz usando la cadena ("id") que pasé al fragmento en init. Si agarré ese objeto almacenado pasando en la posición del objeto en la matriz, no hubo ningún problema. Solo ocurre en dispositivos con Android 7.
FragmentStatePagerAdapter – getItem
Un adaptador FragmentStatePager cargará la página actual y una página a cada lado. Es por eso que registra 0 y 1 al mismo tiempo. Cuando cambia a la página 2, cargará la página 3 y mantendrá la página 1 en la memoria. Luego, cuando llegue a la página 4, no cargará nada, ya que 4 se cargó cuando se desplazó a 3 y no hay nada más allá de eso. Así que el int que se está dando en getItem () no es la página que se está viendo, es el que se está cargando en la memoria. Espero que despeje las cosas por ti
Estos comentarios se confirman en esta
Todas las páginas se cargan correctamente en el emulador Lollipop, la última página tiene un problema extra, vea OthersFragment
:
3. Inicialice todas las páginas en la creación y actualícelas en la selección.
Aumentar OffScreenPageLimit para que todas las páginas se inicialicen
Añadir en la página seleccionada / no seleccionada / reselected escucha
Estos cambios solucionan el problema comentado a continuación:
/** * Implement the tab layout and view pager */ private void useSlidingTabViewPager() { // Create the adapter that will return a fragment for each of the three // primary sections of the activity. BottomSectionsPagerAdapter mBottomSectionsPagerAdapter = new BottomSectionsPagerAdapter(getChildFragmentManager()); // Set up the ViewPager with the sections adapter. ViewPager mBottomViewPager = (ViewPager) rootView.findViewById(R.id.local_bottom_pager); mBottomViewPager.setOffscreenPageLimit(mBottomSectionsPagerAdapter.getCount()); mBottomViewPager.setAdapter(mBottomSectionsPagerAdapter); TabLayout tabLayout = (TabLayout) rootView.findViewById(R.id.tab_layout); tabLayout.setupWithViewPager(mBottomViewPager); tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { /** * Called when a tab enters the selected state. * * @param tab The tab that was selected */ @Override public void onTabSelected(TabLayout.Tab tab) { // TODO: update the selected page here Log.i(LOG_TAG, "page " + tab.getPosition() + " selected."); } /** * Called when a tab exits the selected state. * * @param tab The tab that was unselected */ @Override public void onTabUnselected(TabLayout.Tab tab) { // Do nothing Log.i(LOG_TAG, "Page " + tab.getPosition() + " unselected and "); } /** * Called when a tab that is already selected is chosen again by the user. Some applications * may use this action to return to the top level of a category. * * @param tab The tab that was reselected. */ @Override public void onTabReselected(TabLayout.Tab tab) { // Do nothing Log.i(LOG_TAG, "Page " + tab.getPosition() + " reselected."); } }); }
Comentarios anteriores:
Compruebe su método LocalFragmento getItem () utilizando puntos de interrupción.
Si selecciona una página, la página siguiente también se inicializa y está compartiendo el recicladorVer, etc.
Me gustaría mover la inicialización fuera de getItem () como se sugiere aquí :
ViewPager es la opción por defecto para cargar la siguiente página (Fragmento) que no puede cambiar por setOffscreenPageLimit (0). Pero puedes hacer algo para hackear. Puede implementar la función onPageSelected en Activity que contiene el ViewPager. En el fragmento siguiente (que no quieres cargar), escribes una función, digamos showViewContent (), donde se pone todo el código init que consuma recursos y no hace nada antes del método onResume (). A continuación, llame a la función showViewContent () dentro de onPageSelected. Espero que esto ayude
Lea estas preguntas relacionadas (la primera tiene soluciones posibles para cortar el límite a cero):
ViewPager.setOffscreenPageLimit (0) no funciona como se esperaba
¿ViewPager requiere un mínimo de 1 páginas sin pantalla?
Sí. Si estoy leyendo el código fuente correctamente, debería recibir una advertencia sobre esto en LogCat, algo así como:
Límite de página fuera de pantalla requerido 0 demasiado pequeño; Predeterminado a 1
viewPager.setOffscreenPageLimit(couponsPagerAdapter.getCount());
public void setOffscreenPageLimit(int limit) { if (limit < DEFAULT_OFFSCREEN_PAGES) { Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " + DEFAULT_OFFSCREEN_PAGES); limit = DEFAULT_OFFSCREEN_PAGES; } if (limit != mOffscreenPageLimit) { mOffscreenPageLimit = limit; populate(); } }
- Controles de dependencia estrictos de Android en SDK 17
- El widget de Android funciona bien en el emulador pero en el teléfono se convierte en el widget de Google App