Подтвердить что ты не робот

RecyclerView onBindViewHolder вызывается только один раз внутри макета вкладки

У меня есть четыре вкладки и четыре фрагмента (каждая для каждой вкладки).

Каждый фрагмент имеет вертикальное представление ресайклера. Поскольку все представления фрагментов выглядят одинаково, я повторно использую один и тот же файл макета, те же элементы представления ресайклеров и тот же адаптер.

Проблема заключается в том, что под первой вкладкой и третьей вкладкой и четвертой вкладкой загружается только один элемент, а вторая вкладка успешно загружает все данные.

Надеюсь, что изображение, добавленное ниже, дает лучшее понимание проблемы.

введите описание изображения здесь

Вот мой код адаптера

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();
    }
}

Мне нравится указывать пару вещей -

  • Я проверил другие ответы на Stack Overflow. Они говорят о настройке вида recycler layout_height на wrap_content. Это не проблема, поскольку layout_height уже wrap_content, а вторая вкладка загружает все данные, как ожидалось.

  • И некоторые другие ответы, упомянутые в тех же версиях для всех библиотек поддержки, и я уже использую версию 25.1.0 для всех библиотек поддержки.

  • Размер массива данных равен 20 и возвращает 20 из метода адаптера getItemCount().

  • В массиве данных есть ожидаемое количество элементов в нем, и они не являются нулевыми или пустыми.

  • Чистая сборка, invalidate/caches также не работает.

  • Наконец, я использую FragmentStatePagerAdapter для загрузки фрагментов, когда вкладки находятся в фокусе.

EDIT:

Вот как я разбираю полученные данные JSON

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 метод находится внутри вспомогательного класса и вызывается с помощью initializeHotels.initializeRV();

initializeRV() инициализирует представление Recycler, делает сетевой вызов на сервере, и полученные данные передаются методу parseLocalDeals. initializeHotels является переменной экземпляра класса Helper.

ИЗМЕНИТЬ 2:

Для тех, кто хочет подробно изучить код, я переместил часть кода в другой проект и поделился им с Github. Вот ссылка https://github.com/gSrikar/TabLayout и чтобы понять иерархию, проверьте файл README.

Может ли кто-нибудь сказать мне, что мне не хватает?

4b9b3361

Ответ 1

Резюме

Исправлена ​​проблема с компоновкой в ​​ точке 1, заменяя LinearLayout на RelativeLayout, инвертируя логику видимости, чтобы избежать эффекта призрака и улавливать исключения и предотвращать их, когда соответствующий вид не найден.

Добавлена ​​ точка 2, чтобы продемонстрировать, что визуальный дефект присутствует только на устройствах Marshmallow и Nougat.

Наконец FragmentStatePagerAdapter загружает страницы, прежде чем получать фокус, поэтому исправление предлагается в точке 3 (загружать все страницы и обновлять их при выборе).

Дополнительная информация в комментариях ниже и @d4h answer.

Четвертая страница не использует один и тот же макет, только те же RecyclerView и id, возможно, работа продолжается. Проблема макета может быть решена с использованием того же макета, что и предыдущие страницы, но я считаю, что это изменение выходит за рамки.


1. Частично фиксируется для устройств Marshmallow и Nougat. Незавершенная работа.

Update2 Изменение LinearLayout с помощью RelativeLayout и инвертирование логики видимости решает проблему с макетами:

введите описание изображения здесь

Обновление: Комментирование initializeTrending во всех инициализациях фрагментов также работает на Api23 +

enter image description here

Я проверю его позже, кажется, что транзакции правильно загружены, но затем тренд загружается и сделки теряются. WIP здесь.

Если trending array empty и trending view go, сделки не отображаются, но с использованием невидимых

enter image description here

enter image description here


2. Вы загружаете неправильную страницу на устройствах Marshmallow и Nougat

FragmentStatePagerAdapter первым вызовом getItem() неправильно на устройствах Nougat

Это не имело ничего общего с FragmentStatePagerAdapter код. Скорее, в моем фрагменте я захватил сохраненный объект из массива используя строку ( "id" ), которую я передал фрагменту в init. Если я захватил этот сохраненный объект, передав в положение объекта в массив, проблем не было. Только в устройствах с Android 7.

FragmentStatePagerAdapter - getItem

Адаптер FragmentStatePager загрузит текущую страницу и одну страницу любая сторона. Вот почему он регистрирует 0 и 1 одновременно. Когда ты перейдите на страницу 2, он загрузит страницу 3 и сохранит страницу 1 в памяти. затем когда вы доберетесь до страницы 4, он ничего не загрузит, так как 4 загрузилось, когда вы прокручиваете до 3, и ничего кроме этого нет. Итак, int int вам предоставляется в getItem(), это НЕ страница, которая в настоящее время просматривается, тот, который загружается в память. Надеюсь, что очистится вещи для вас

Эти комментарии подтверждены в этой ветке и зафиксировать

Все страницы загружаются правильно на эмулятор Lollipop, последняя страница имеет дополнительную проблему, см. OthersFragment:

enter image description here

enter image description here


3. Инициализируйте все страницы при создании и обновите их при выборе.

Увеличить OffScreenPageLimit, чтобы все страницы были инициализированы

Добавить на выбранный/невыбранный/повторно выбранный прослушиватель на странице

Эти изменения решают проблему, описанную ниже:

/**
 * 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.");
    }
});
}

Предыдущие комментарии:

Проверьте метод LocalFragment getItem(), используя точки останова.

Если вы выбираете одну страницу, следующая страница также инициализируется, и вы делите recyclerView и т.д.

Я бы перенесла инициализацию вне getItem(), как предложено здесь:

ViewPager по умолчанию загружает следующую страницу (фрагмент), которую вы не можете измените setOffscreenPageLimit (0). Но вы можете сделать что-то, чтобы взломать. Вы можете реализовать функцию onPageSelected в Activity, содержащую ViewPager. В следующем фрагменте (который вы не хотите загружать), вы написать функцию let say showViewContent(), где вы помещаете все ресурс, содержащий код инициализации и ничего не предпринимать перед методом onResume(). Затем вызовите функцию showViewContent() внутри onPageSelected. Надеюсь, это поможет

Прочтите эти связанные вопросы (у первого есть возможные обходные пути, чтобы взломать предел до нуля):

ViewPager.setOffscreenPageLimit(0) не работает должным образом

Требуется ли ViewPager минимум 1 внеэкранных страниц?

Да. Если я правильно прочитав исходный код, вы должны получить предупреждение об этом в LogCat, что-то вроде:

Запрашивается ограниченный размер страницы 0 слишком мал; по умолчанию 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();
    }
}

Ответ 2

Не так много ответа, но слишком долго для комментария.

Я дублировал (почти) ваш код адаптера, и он полностью работает для меня. Я считаю, что я сделал то же самое, что и вы. Я использую тот же файл макета, тот же самый элемент и тот же адаптер для всех вкладок. Я думаю, что нет никаких проблем с вашим кодом адаптера.

Я говорю "почти", потому что мне пришлось изменить пару вещей, так как у меня нет доступа к вашим данным. Я изменил вашу модель LocalDealsDataField, включив BitmapDrawable, и я изменил onBindViewHolder(), чтобы обработать ее.

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

Поскольку у вашего адаптера нет проблем, я бы сосредоточился на получении данных или настройке адаптера в качестве вашей проблемы. Извините, я не могу помочь вам.

FYI, вот как я настраиваю адаптер в 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);

Ответ 3

Я посмотрел ваш код, проблема такая же, как объясняется @ardock

Решение, которое я хотел бы предложить,

Вы должны изменить свой код на 3-е место::

  • Внутри всего Fragment Вы используете в ViewPager Не вызывайте initializeRESPECTIVEView() из метода onCreateView.

  • Внутри LocalFragment создайте список фрагментов, которые вы собираетесь использовать с ViewPager, и передайте его на BottomSectionsPagerAdapter. и верните Fragment из этого списка из getItem(int position) из BottomSectionsPagerAdapter.

  • Добавьте следующий код в LocalFragment внутри 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) {
    
        }
    });`
    

    //Вызвать соответствующий фрагмент initializeRESPECTIVEView() из onTabSelected, вы можете получить экземпляр фрагмента из списка, который вы передали в BottomSectionsPagerAdapter