ViewPager внутри RecyclerView как элемент строки

мне нужно иметь ViewPager (похожий на горизонтальную галерею) внутри RecyclerView, который отображает список вертикально. Каждая строка RecyclerView будет иметь ViewPager, который позволит проводить между некоторыми изображениями. ViewPager также будет поддерживать события click, которые будут распространяться на родительский RecyclerView.

В настоящее время у меня есть следующие реализации:

список адаптеров:

@Override
public void onBindViewHolder(MyHolder holder, int position) {
    super.onBindViewHolder(holder, position);

    Item listItem = get(position);

    ...

    GalleryAdapter adapter =
                    new GalleryAdapter(getActivity().getSupportFragmentManager(),
                                                         item.mediaGallery);
    holder.imageGallery.setAdapter(adapter);

    ...
}

Галерея адаптер:

public class GalleryAdapter extends FragmentStatePagerAdapter {

    private final List<Item.Gallery> mItems;
    @Bind(R.id.gallery_item)
    ImageView galleryView;

    public SearchResultsGalleryPagerAdapter(FragmentManager fm, @NonNull ArrayList<Item.Gallery> mediaGallery) {
        super(fm);

        mItems = mediaGallery;
    }

    @Override
    public Fragment getItem(int position) {
        GalleryFragment fragment = GalleryFragment.newInstance(mItems.get(position));
        ...
        return fragment;
    }

    @Override
    public int getCount() {
        return null == mItems ? 0 : mItems.size();
    }

    @Override
    public int getItemPosition(Object object) {
        //return super.getItemPosition(object);
        return PagerAdapter.POSITION_NONE;
    }
}

фрагмент Галерея:

public class GalleryFragment extends Fragment {

    private static final String GALLERY_ITEM_BUNDLE_KEY = "gallery_item_bundle_key";

    @Bind(R.id.gallery_item)
    ImageView mGalleryView;

    private Item.Gallery mGalleryItem;

    // Empty constructor, required as per Fragment docs
    public GalleryFragment() {}

    public static SearchResultsGalleryFragment newInstance(Item.Gallery galleryItem) {
        GalleryFragment fragment = new GalleryFragment();

        // Add the item in the bundle which will be set to the fragment
        Bundle bundle = new Bundle();
        bundle.putSerializable(GALLERY_ITEM_BUNDLE_KEY, galleryItem);
        fragment.setArguments(bundle);

        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mGalleryItem = (Item.Gallery) getArguments().getSerializable(GALLERY_ITEM_BUNDLE_KEY);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_gallery_item, container, false);
        ButterKnife.bind(this, view);

        displayGalleryItem();

        return view;
    }

    private void displayGalleryItem() {
        if (null != mGalleryItem) {
            Glide.with(getContext()) // Bind it with the context of the actual view used
                 .load(mGalleryItem.getImageUrl()) // Load the image
                 .centerCrop() // scale type
                 .placeholder(R.drawable.default_product_400_land) // temporary holder displayed while the image loads
                 .crossFade()
                 .into(mGalleryView);
        }
    }
}

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

есть ли у кого-нибудь идея о том, что я реализовал неправильно?

1 ответов


мне удалось обойти эту проблему с помощью PagerAdapter напрямую.

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v4.view.PagerAdapter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DecodeFormat;
import com.peoplepost.android.R;
import com.peoplepost.android.common.listener.ItemClickSupport;
import com.peoplepost.android.network.merv.model.Product;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * Custom pager adapter which will manually create the pages needed for showing an slide pages gallery.
 * </p>
 * Created by Ionut Negru on 13/06/16.
 */
public class GalleryAdapter extends PagerAdapter {

    private static final String TAG = "GalleryAdapter";

    private final List<Item> mItems;
    private final LayoutInflater mLayoutInflater;
    /**
     * The click event listener which will propagate click events to the parent or any other listener set
     */
    private ItemClickSupport.SimpleOnItemClickListener mOnItemClickListener;

    /**
     * Constructor for gallery adapter which will create and screen slide of images.
     *
     * @param context
     *         The context which will be used to inflate the layout for each page.
     * @param mediaGallery
     *         The list of items which need to be displayed as screen slide.
     */
    public GalleryAdapter(@NonNull Context context,
                                            @NonNull ArrayList<Item> mediaGallery) {
        super();

        // Inflater which will be used for creating all the necessary pages
        mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        // The items which will be displayed.
        mItems = mediaGallery;
    }

    @Override
    public int getCount() {
        // Just to be safe, check also if we have an valid list of items - never return invalid size.
        return null == mItems ? 0 : mItems.size();
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        // The object returned by instantiateItem() is a key/identifier. This method checks whether
        // the View passed to it (representing the page) is associated with that key or not.
        // It is required by a PagerAdapter to function properly.
        return view == object;
    }

    @Override
    public Object instantiateItem(ViewGroup container, final int position) {
        // This method should create the page for the given position passed to it as an argument.
        // In our case, we inflate() our layout resource to create the hierarchy of view objects and then
        // set resource for the ImageView in it.
        // Finally, the inflated view is added to the container (which should be the ViewPager) and return it as well.

        // inflate our layout resource
        View itemView = mLayoutInflater.inflate(R.layout.fragment_gallery_item, container, false);

        // Display the resource on the view
        displayGalleryItem((ImageView) itemView.findViewById(R.id.gallery_item), mItems.get(position));

        // Add our inflated view to the container
        container.addView(itemView);

        // Detect the click events and pass them to any listeners
        itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (null != mOnItemClickListener) {
                    mOnItemClickListener.onItemClicked(position);
                }
            }
        });

        // Return our view
        return itemView;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        // Removes the page from the container for the given position. We simply removed object using removeView()
        // but could’ve also used removeViewAt() by passing it the position.
        try {
            // Remove the view from the container
            container.removeView((View) object);

            // Try to clear resources used for displaying this view
            Glide.clear(((View) object).findViewById(R.id.gallery_item));
            // Remove any resources used by this view
            unbindDrawables((View) object);
            // Invalidate the object
            object = null;
        } catch (Exception e) {
            Log.w(TAG, "destroyItem: failed to destroy item and clear it's used resources", e);
        }
    }

    /**
     * Recursively unbind any resources from the provided view. This method will clear the resources of all the
     * children of the view before invalidating the provided view itself.
     *
     * @param view
     *         The view for which to unbind resource.
     */
    protected void unbindDrawables(View view) {
        if (view.getBackground() != null) {
            view.getBackground().setCallback(null);
        }
        if (view instanceof ViewGroup) {
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                unbindDrawables(((ViewGroup) view).getChildAt(i));
            }
            ((ViewGroup) view).removeAllViews();
        }
    }

    /**
     * Set an listener which will notify of any click events that are detected on the pages of the view pager.
     *
     * @param onItemClickListener
     *         The listener. If {@code null} it will disable any events from being sent.
     */
    public void setOnItemClickListener(ItemClickSupport.SimpleOnItemClickListener onItemClickListener) {
        mOnItemClickListener = onItemClickListener;
    }

    /**
     * Display the gallery image into the image view provided.
     *
     * @param galleryView
     *         The view which will display the image.
     * @param galleryItem
     *         The item from which to get the image.
     */
    private void displayGalleryItem(ImageView galleryView, Item galleryItem) {
        if (null != galleryItem) {
            Glide.with(galleryView.getContext()) // Bind it with the context of the actual view used
                 .load(galleryItem.getImageUrl()) // Load the image
                 .asBitmap() // All our images are static, we want to display them as bitmaps
                 .format(DecodeFormat.PREFER_RGB_565) // the decode format - this will not use alpha at all
                 .centerCrop() // scale type
                 .placeholder(R.drawable.default_product_400_land) // temporary holder displayed while the image loads
                 .animate(R.anim.fade_in) // need to manually set the animation as bitmap cannot use cross fade
                 .thumbnail(0.2f) // make use of the thumbnail which can display a down-sized version of the image
                 .into(galleryView); // Voilla - the target view
        }
    }
}

и обновленный onBindViewHolder () родительского RecyclerView:

@Override
public void onBindViewHolder(MyHolder holder, int position) {
    super.onBindViewHolder(holder, position);

    Item listItem = get(position);

    ...

    GalleryAdapter adapter =
                    new GalleryAdapter(getActivity(), product.mediaGallery);
    // Set the custom click listener on the adapter directly
    adapter.setOnItemClickListener(new ItemClickSupport.SimpleOnItemClickListener() {
        @Override
        public void onItemClicked(int position) {
            // inner view pager page was clicked
        }
    });
    // Set the adapter on the view pager
    holder.imageGallery.setAdapter(adapter);

    ...
}

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

Я надеюсь, что это поможет другим в подобной ситуации.