7

Мне нужно иметь ViewPager (похожую на горизонтальную галерею) внутри RecyclerView, который отображает список по вертикали. Каждая строка RecyclerView будет иметь ViewPager, который позволит прокручивать между некоторыми изображениями. ViewPager также будет поддерживать события кликов, которые будут переданы родительскому RecyclerView.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 не создаются и отображаются правильно. Иногда они появляются после ручного свитка (но не всегда), в большинстве случаев они вообще не появляются.

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

спасибо.

+0

Я столкнулся с той же проблемой: http://stackoverflow.com/questions/37801078/viewpager-inside-cardview-inside-recyclerview-android –

+0

Мне удалось обойти эту проблему с помощью 'PagerAdapter' напрямую. Я отправлю свое решение через несколько минут. –

ответ

11

Мне удалось обойти эту проблему, используя 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); 

    ... 
} 

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

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

+0

@ lonut Negru, что делать, если я хочу использовать фрагмент вместо 'View'? Любой выход? –

+0

Если вы имеете в виду «FragmentPagerAdapter», то это выполнимо, так как вы будете контролировать фрагмент пейджера горизонтально, и в каждом фрагменте у вас будет список. Если вы хотите вставить фрагменты в адаптер просмотра recycler, я не знаю, но я думаю, что это будет иметь большое влияние на производительность, поскольку фрагменты имеют свой собственный жизненный цикл. С уважением. –

Смежные вопросы