在RecyclerView中使用ViewPager作为行项

9

我需要在RecyclerView中嵌入一个ViewPager(类似于横向的画廊),用于纵向显示列表。RecyclerView的每一行都将有一个ViewPager,允许在其中滑动查看一些图片。ViewPager还将支持单击事件,并传播到父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的片段未能正确创建和显示。有时它们会在手动滚动后出现(但并非总是如此),在大多数情况下它们根本不会出现。

是否有任何人对我实施错误的地方有任何想法?


我已经成功地通过直接使用 PagerAdapter 来解决了这个问题。我将在几分钟内发布我的解决方案。 - Ionut Negru
1个回答

15

我已经成功解决了这个问题,通过直接使用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
        }
    }
}

并且是已更新的父级RecyclerView的onBindViewHolder():

@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);

    ...
}

我注意到内存使用稍有增加,但用户界面非常流畅。我想可以在保留和恢复多少页面以及它们的方式上进行进一步的优化。

希望这可以帮助其他遇到类似情况的人。


@lonut Negru,如果我想使用片段而不是“View”,有什么办法吗?有什么解决方法吗? - Jimit Patel
如果您正在引用FragmentPagerAdapter,那么这是可行的,因为您将水平控制片段页面,并且在每个片段中都会有一个列表。如果您想将片段插入到回收视图的适配器中,我不知道,但我认为这将对性能产生很大影响,因为片段具有自己的生命周期。 此致敬礼。 - Ionut Negru
1
我尝试了你的解决方案,但它没有起作用。唯一对我有用的方法是使用Android Support Library v7中出现的新功能——使用Horizontal Recyclerview和PagerSnapHelper来获得类似ViewPager的结果。 - Shyam Sunder
有趣的是,在我的应用程序中它起作用了。 现在这是实现这种功能的最佳选择。 - Ionut Negru

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接