with viewpager studio fragments example activity android android-fragments tabs

android - viewpager - RecyclerView onBindViewHolder llamado solo una vez dentro del diseño de pestaña



tabs android material design (3)

Tengo cuatro pestañas y cuatro fragmentos (cada uno para cada pestaña).

Cada fragmento tiene una vista de reciclador vertical. Dado que todos los fragmentos tienen una apariencia similar, estoy reutilizando el mismo archivo de diseño, los mismos elementos de la vista de reciclador y el mismo adaptador.

El problema es que solo un elemento se carga debajo de la primera pestaña y la tercera y cuarta, mientras que la segunda pestaña carga con éxito todos los datos.

Espero que la imagen agregada a continuación brinde una mejor comprensión sobre el tema.

Aquí está mi código de 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 gustaría señalar un par de cosas:

  • He verificado otras respuestas en Stack Overflow. Hablan de configurar el reciclador vista layout_height para wrap_content . Este no es el problema ya que layout_height ya es wrap_content y también la segunda pestaña carga todos los datos como se esperaba.

  • Y algunas otras respuestas mencionaron que usé 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 y no son nulos ni están vacíos.

  • Clean build, invalidate / caches tampoco funciona.

  • Finalmente, estoy usando FragmentStatePagerAdapter para cargar los fragmentos cuando las pestañas están enfocadas.

EDITAR:

Así es como 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 parseLocalDeals usando initializeHotels.initializeRV();

initializeRV() inicializa la vista de Recycler, realiza una llamada de red al servidor y los datos recibidos se pasan al método parseLocalDeals . initializeHotels es una variable de instancia de la clase Helper.

EDICION 2:

Para aquellos que quieran explorar el código en detalle, he movido la parte del código a otro proyecto y lo he compartido en Github. Aquí está el enlace https://github.com/gSrikar/TabLayout y para comprender la jerarquía, consulte el archivo README.

¿Alguien puede decirme lo que me estoy perdiendo?


No hay demasiada respuesta, pero es demasiado tiempo para un comentario.

He duplicado (casi) su código de adaptador y funciona completamente para mí. Creo que he hecho lo mismo que tú. Estoy usando el mismo archivo de diseño, el mismo artículo y el mismo adaptador para todas las pestañas. Creo que no hay problemas con su código de adaptador.

Digo ''casi'' porque tuve que cambiar un par de cosas ya que no tengo acceso a sus datos. Cambié tu modelo LocalDealsDataField para incluir un mapa de bits y cambié onBindViewHolder() para manejarlo.

BitmapDrawable lfImage = othersDataArray.get(position).getLocalDealImage(); holder.othersImageView.setBackground(lfImage);

Como parece que no hay 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, así es como configuré 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 revisado tu código, el problema es el mismo explicado por @ardock

Solución que me gustaría proponer,

Tienes que cambiar tu código en 3 lugar ::

  1. Dentro de todos los Fragment que está utilizando en ViewPager No ViewPager initializeRESPECTIVEView() desde el método onCreateView .

  2. Dentro de LocalFragment haz una lista de los Fragmentos que vas a usar con ViewPager y BottomSectionsPagerAdapter a BottomSectionsPagerAdapter . y devuelve el Fragment de esa lista de getItem(int position) de BottomSectionsPagerAdapter .

  3. Agregue el siguiente código a LocalFragment dentro de useSlidingTabViewPager() .

    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) { } });`

    // Llamada fragmento respectivo initializeRESPECTIVEView() método de onTabSelected , puede obtener instancia de fragmento de la lista que pasó a BottomSectionsPagerAdapter


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 capturar excepciones y prevenirlas cuando no se encuentra la vista relacionada.

Se agregó el punto 2 para demostrar que el defecto visual solo está presente en los dispositivos Marshmallow y Nougat.

Finalmente, FragmentStatePagerAdapter carga las páginas antes de obtener el foco, por lo que se propone una solución en el punto 3 (carga todas las páginas y actualízalas cuando se seleccionen).

Más información en los comentarios a continuación y respuesta @ d4h.

La cuarta página no usa el mismo diseño, solo el mismo RecyclerView e id , quizás un trabajo en progreso. El problema de diseño se puede resolver utilizando el mismo diseño que las páginas anteriores, pero considero que este cambio está fuera del alcance.

1. Parcialmente fijado para los dispositivos Marshmallow y Nougat. Trabajo en progreso.

Actualización2 El cambio de LinearLayout por RelativeLayout y la lógica de visibilidad de inversión resuelve el problema de diseño:

Actualización: Comentar initializeTrending en todas las inicializaciones de fragmentos también funciona en Ape 23 +

Lo veré más tarde, parece que las ofertas están cargadas correctamente, pero luego se carga la tendencia y se pierden acuerdos. WIP aquí .

Si la matriz de tendencias vacía y la vista de tendencias se han ido, las ofertas no se muestran , pero se muestra el uso de invisibles.

2. Estás cargando una página incorrecta en los dispositivos Marshmallow y Nougat

FragmentStatePagerAdapter primera llamada a getItem () incorrecta en dispositivos Nougat

Esto terminó sin tener nada que ver con el código FragmentStatePagerAdapter. Por el contrario, 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 en cada lado. Es por eso que registra 0 y 1 al mismo tiempo. Cuando cambie 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á. Por lo tanto, el int que se le está dando en getItem () NO es la página que se está viendo actualmente, es el que se está cargando en la memoria. Espero que aclare las cosas para ti

Estos comentarios se confirman en esta rama y confirman

Todas las páginas se cargan correctamente en el emulador Lollipop, la última página tiene un problema adicional, consulte 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

Agregar en la página seleccionado / no seleccionado / oyente reseleccionado

Estos cambios resuelven 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:

Verifique su método LocalFragment getItem () utilizando puntos de interrupción.

Si selecciona una página, la página siguiente también se inicializa, y está compartiendo el recicladorView, etc.

Me gustaría mover la inicialización fuera de getItem () como se sugiere aquí :

ViewPager se carga de forma predeterminada para cargar la página siguiente (Fragmento) que no puede cambiar mediante setOffscreenPageLimit (0). Pero puedes hacer algo para hackear. Puede implementar la función OnPageSelected en Activity que contiene ViewPager. En el siguiente Fragmento (que no desea cargar), escribe una función, digamos showViewContent () donde pone todo el código init que consume recursos y no hace nada antes del método onResume (). A continuación, llame a la función showViewContent () en onPageSelected. Espero que esto ayude

Lea estas preguntas relacionadas (la primera tiene soluciones posibles para hackear el límite a cero):

ViewPager.setOffscreenPageLimit (0) no funciona como se esperaba

¿ViewPager requiere un mínimo de 1 páginas fuera de 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 solicitado 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(); } }