7

Ich brauche einen ViewPager (ähnlich einer horizontalen Galerie) innerhalb einer RecyclerView, die eine Liste vertikal anzeigen. Jede Reihe der RecyclerView wird einen ViewPager haben, der es erlaubt zwischen einigen Bildern zu wischen. Der ViewPager unterstützt auch Klick-Ereignisse, die an den übergeordneten RecyclerView weitergegeben werden.ViewPager innerhalb RecyclerView als Zeilenobjekt

Derzeit habe ich die folgende Implementierung:

Liste Adapter:

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

    ... 
} 

Galerie 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; 
    } 
} 

Galerie Fragment:

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

Das Problem, das ich habe, ist, dass die Fragmente des ViewPager nicht korrekt erstellt und angezeigt werden. Manchmal erscheinen sie nach einem manuellen Scroll (aber nicht immer), in den meisten Fällen erscheinen sie überhaupt nicht.

Hat jemand eine Idee, was ich falsch implementiert habe?

Vielen Dank.

+0

Ich bin mit dem gleichen Problem konfrontiert: http://StackOverflow.com/Questions/37801078/viewpager-inside-cardview-inside-recyclerview-android –

+0

Ich habe es geschafft, um dieses Problem zu umgehen, indem Sie 'PagerAdapter' direkt verwenden. Ich werde meine Lösung in ein paar Minuten veröffentlichen. –

Antwort

11

Ich habe es geschafft, dieses Problem zu umgehen, indem Sie PagerAdapter direkt verwenden.

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 
     } 
    } 
} 

und der aktualisierte onBindViewHolder() der Mutter 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); 

    ... 
} 

bemerkte ich einen kleinen Anstieg der Speichernutzung, aber die Benutzeroberfläche ist sehr flüssig. Ich denke, es gibt noch weitere Optimierungen, wie viele Seiten behalten und wie sie zerstört und wiederhergestellt werden.

Ich hoffe, das hilft anderen in einer ähnlichen Situation.

+0

@lonut Negru, was ist, wenn ich Fragment anstelle von 'View' verwenden möchte? Ein Ausweg? –

+0

Wenn Sie sich auf 'FragmentPagerAdapter' beziehen, dann ist es machbar, da Sie den Fragmentpager horizontal steuern und in jedem Fragment eine Liste haben. Wenn Sie Fragmente in den Adapter der Recycler-Ansicht einfügen wollen, weiß ich nicht, aber ich denke, es wird einen großen Einfluss auf die Performance haben, da Fragmente ihren eigenen Lebenszyklus haben. Mit freundlichen Grüßen. –