【问题标题】:RecyclerView onBindViewHolder called only once inside Tab layoutRecyclerView onBindViewHolder 在选项卡布局中仅调用一次
【发布时间】:2017-07-05 23:15:54
【问题描述】:

我有四个标签和四个片段(每个标签对应一个)。

每个片段都有一个垂直的回收器视图。由于所有片段视图看起来都相似,我正在重复使用相同的布局文件、相同的回收器视图项和相同的适配器。

问题是在第一个标签和第三个标签和第四个标签下只加载了一个项目,而第二个标签成功加载了整个数据。

我希望下面添加的图片可以更好地理解这个问题。

这是我的适配器代码

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 上查看了其他答案。他们谈论将回收站视图layout_height 设置为wrap_content。这不是问题,因为 layout_height 已经是 wrap_content 并且第二个选项卡也按预期加载了所有数据。

  • 还有一些其他答案提到对所有支持库使用相同的版本,而我已经为所有支持库使用 25.1.0 版本。

  • 数据数组的大小为 20,从适配器的 getItemCount() 方法返回 20。

  • 数据数组中包含预期数量的项目,并且它们不为 null 或为空。

  • 清理构建、无效/缓存也不起作用。

  • 最后,我使用FragmentStatePagerAdapter 在标签处于焦点时加载片段。

编辑:

这就是我解析收到的 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,要了解层次结构,请查看自述文件。

谁能告诉我我错过了什么?

【问题讨论】:

标签: android android-fragments tabs


【解决方案1】:

答案不多,但评论太长了。

我已经复制了(几乎)你的适配器代码,它完全适合我。我相信我做过和你一样的事。我对所有选项卡使用相同的布局文件、相同的项目和相同的适配器。我认为您的适配器代码没有问题。

我说“几乎”是因为我无权访问您的数据,因此不得不更改一些内容。我更改了您的 LocalDealsDataField 模型以包含 BitmapDrawable 并且我更改了 onBindViewHolder() 来处理它。

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

由于您的适配器似乎没有问题,因此我将专注于获取数据或将适配器设置为您的问题。抱歉,除此之外我无能为力。

仅供参考,这是我在 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);

【讨论】:

【解决方案2】:

总结

解决了第 1 点的布局问题,将 LinearLayout 替换为 RelativeLayout,反转可见性逻辑以避免重影效应并在未找到相关视图时捕获异常并阻止它们。

添加了第 2 点,以证明视觉缺陷仅存在于 Marshmallow 和 Nougat 设备上。

最后FragmentStatePagerAdapter 在获得焦点之前加载页面,因此在第 3 点提出了修复建议(加载所有页面并在选中时更新它们)。

下面的 cmets 和 @d4h answer 中的更多信息。

第四页没有使用相同的布局,只有相同的RecyclerViewid,可能正在进行中。布局问题可以使用与前几页相同的布局来解决,但我认为这种更改超出了范围。


1。部分修复了棉花糖和牛轧糖设备。正在进行中。

Update2 Changing LinearLayout by RelativeLayout and inverting visibility logic 解决布局问题:

更新:在所有片段初始化中评论 initializeTrending 也适用于 Api23+

稍后我会检查它,似乎交易已正确加载,但随后趋势已加载且交易丢失。在制品here

If trending array empty and trending view gone, deals are not shown,但using invisible are shown


2。您在 Marshmallow 和 Nougat 设备上加载了错误的页面

FragmentStatePagerAdapter first call to getItem() wrong on Nougat devices

这最终与 FragmentStatePagerAdapter 无关 代码。相反,在我的片段中,我从数组中抓取了一个存储的对象 使用我在 init 中传递给片段的字符串(“id”)。如果我 通过传入对象的位置来抓取存储的对象 阵列,没有问题。仅在搭载 Android 7 的设备中发生。

FragmentStatePagerAdapter - getItem

一个 FragmentStatePager 适配器将加载当前页面,然后一页 任何一边。这就是它同时记录 0 和 1 的原因。当你 切换到第 2 页,它将加载第 3 页并将第 1 页保留在内存中。然后 当您到达第 4 页时,它不会加载任何内容,因为 4 是在 您滚动到 3,除此之外没有其他内容。所以int那个 您在 getItem() 中获得的不是当前的页面 正在查看的,是正在加载到内存中的那个。希望清除 为你解决问题

这些cmets已确认in this branch and commit

所有页面在 Lollipop 模拟器上都能正确加载,最后一页有一个额外的问题,请参阅OthersFragment


3。在创建时初始化所有页面并在选择时更新它们。

Increase OffScreenPageLimit so all pages are initialised

Add on page selected/unselected/reselected listener

这些更改解决了下面评论的问题:

/**
 * 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等

我会按照here 的建议将初始化移到 getItem() 之外:

ViewPager 默认加载你不能加载的下一页(片段) 通过 setOffscreenPageLimit(0) 更改。但是你可以做一些事情来破解。 您可以在包含的 Activity 中实现 onPageSelected 函数 查看页面。在下一个片段(您不想加载)中,您 写一个函数让我们说 showViewContent() 你把所有 资源消耗初始化代码并且在 onResume() 方法之前什么都不做。 然后在 onPageSelected 中调用 showViewContent() 函数。希望这个 会有所帮助

阅读这些相关问题(第一个问题有可能将限制破解为零):

ViewPager.setOffscreenPageLimit(0) doesn't work as expected

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

【讨论】:

    【解决方案3】:

    我看过你的代码,问题和@ardock解释的一样

    我想提出的解决方案,

    您必须在 3 处更改代码 ::

    1. 在所有Fragment 里面你正在使用ViewPager 不要从onCreateView 方法调用initializeRESPECTIVEView()

    2. LocalFragment 中列出您将与ViewPager 一起使用的片段并将其传递给BottomSectionsPagerAdapter。并从getItem(int position) of BottomSectionsPagerAdapter 的列表中返回Fragment

    3. 将以下代码添加到LocalFragmentuseSlidingTabViewPager()

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

    //从onTabSelected调用各自的fragment initializeRESPECTIVEView()方法,你可以从你传递给BottomSectionsPagerAdapter的列表中获取fragment实例

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-03-05
      • 2016-07-02
      • 1970-01-01
      • 2016-06-12
      • 1970-01-01
      • 2016-04-19
      • 1970-01-01
      相关资源
      最近更新 更多