ViewPager inside CardView inside RecyclerView Android

I am trying to create something very similar to this design from the Material Design website .

ViewPager inside cardview

Basically, I have a list of CardViews, each of which has several types that the user can carry between them. (The first map in the image)

I started by implementing a simple recyclerview that contains a CardView, and for each card I implemented a FragmentStatePagerAdapter that contains several fragments that the user can scroll through and it works (sort of). The problem I am facing is very similar to this problem. The fragment in the ViewPager using the FragmentPagerAdapter is empty the second time it is viewed , where the fragments either will not load or they disappear when scrolling up and down. I tried all the possible fixes that people suggested, and I still can't get this to work.

I was wondering if there is a better way to do this.

Here is my code (C # - Xamarin)

using System; using Android.Support.V7.Widget; using System.Collections.Generic; using Android.Views; using Android.Widget; using System.Security.Cryptography; using Android.Support.V4.View; using Android.Support.V4.App; using Android.Runtime; using Android.OS; using Android.Content; using Android.App; using Android.Support.V7.App; using JavaString = Java.Lang.String; using Android.Util; using Android.Animation; using System.ComponentModel.Design.Serialization; namespace Answers.PortalAppXamarin.Droid { public enum MyTestAdapterItemType { Type1, Type2, Type3 }; public class MyTestDataObj { public MyTestAdapterItemType type { get; set; } public string data { get; set; } } public class PagerFragmentAdapter : Android.Support.V4.App.FragmentStatePagerAdapter { public List<MyTestPageFragmentContainer> Tabs { get; set; } public PagerFragmentAdapter(Android.Support.V4.App.FragmentManager fm) : base(fm) { } public override Android.Support.V4.App.Fragment GetItem(int position) { return Tabs [position].Fragment; } public override Java.Lang.ICharSequence GetPageTitleFormatted(int position) { return new JavaString(Tabs [position].Title); } public override int GetItemPosition (Java.Lang.Object objectValue) { for (int i = 0; i < Tabs.Count; i++) { if (Tabs[i].Fragment.Equals (objectValue) ) { return i; } } return PositionNone; } public override int Count { get { return Tabs.Count; } } } public class MyTestPageFragmentContainer { public string Title { get; set; } public Android.Support.V4.App.Fragment Fragment { get; set; } public MyTestPageFragmentContainer(string title, Android.Support.V4.App.Fragment fragment) { this.Title = title; this.Fragment = fragment; } } public class BaseFragment : Android.Support.V4.App.Fragment { public static Android.Support.V4.App.Fragment newInstance(int position) { BaseFragment f = new BaseFragment (); // Supply num input as an argument. Bundle args = new Bundle (); args.PutInt ("num", position); f.Arguments = (args); return f; } } public class PageFragment1 : BaseFragment { View RootView; public override void OnCreate (Bundle savedInstanceState) { base.OnCreate (savedInstanceState); } public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { RootView = inflater.Inflate (Resource.Layout.LoadingIndicatorOverlay, container, false); return RootView; } public override void OnResume () { base.OnResume (); ValueAnimator _animator = ValueAnimator.OfFloat(0, 1); _animator.SetDuration(2000); _animator.Update += (object sender, ValueAnimator.AnimatorUpdateEventArgs e) => { if (RootView != null) { RootView.Alpha = (float)e.Animation.AnimatedValue; } }; } } public class PageFragment2 : BaseFragment { View RootView; public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { RootView = inflater.Inflate(Resource.Layout.MeasureListItem, container, false); return RootView; } public override void OnResume () { base.OnResume (); ValueAnimator _animator = ValueAnimator.OfFloat(0, 1); _animator.SetDuration(2000); _animator.Update += (object sender, ValueAnimator.AnimatorUpdateEventArgs e) => { if (RootView != null) { RootView.Alpha = (float)e.Animation.AnimatedValue; } }; } } public abstract class BaseCardViewHolder : RecyclerView.ViewHolder { public ViewGroup Header; public ViewGroup Footer; public TextView TitleTextView; public Button MoreInfoBtn; public ViewPager ContentViewPager; public PagerFragmentAdapter Adapter; public List<MyTestPageFragmentContainer> Tabs; public Android.Support.V4.App.FragmentManager fm; public BaseCardViewHolder(View rootView, Android.Support.V4.App.FragmentManager fm) : base(rootView) { Header = rootView.FindViewById<ViewGroup> (Resource.Id.card_view_header); Footer = rootView.FindViewById<ViewGroup> (Resource.Id.card_view_footer); ContentViewPager = rootView.FindViewById<ViewPager> (Resource.Id.card_view_content_viewPager); TitleTextView = Header.FindViewById<TextView> (Resource.Id.card_view_title); MoreInfoBtn = Footer.FindViewById<Button> (Resource.Id.card_view_moreInfoBtn); this.fm = fm; Tabs = setupTabs (); if (Tabs != null && fm != null) { Adapter = new PagerFragmentAdapter (fm); Adapter.Tabs = Tabs; ContentViewPager.Adapter = Adapter; } } public void RefreshViewPager() { ContentViewPager.Adapter.NotifyDataSetChanged (); } public abstract List<MyTestPageFragmentContainer> setupTabs (); } public class Type1ViewHolder : BaseCardViewHolder { public Type1ViewHolder(View v, Android.Support.V4.App.FragmentManager fm) : base(v, fm) { } public override List<MyTestPageFragmentContainer> setupTabs() { return new List<MyTestPageFragmentContainer> { new MyTestPageFragmentContainer ("Tab1", new PageFragment1 ()), new MyTestPageFragmentContainer ("Tab2", new PageFragment1 ()) }; } } public class Type2ViewHolder : BaseCardViewHolder { public Type2ViewHolder(View v, Android.Support.V4.App.FragmentManager fm) : base(v, fm) { } public override List<MyTestPageFragmentContainer> setupTabs() { return new List<MyTestPageFragmentContainer> { new MyTestPageFragmentContainer ("Tab1", new PageFragment2 ()), new MyTestPageFragmentContainer ("Tab2", new PageFragment2 ()), new MyTestPageFragmentContainer ("Tab1", new PageFragment1 ()), new MyTestPageFragmentContainer ("Tab2", new PageFragment1 ()), }; } } public class Type3ViewHolder : BaseCardViewHolder { public Type3ViewHolder(View v, Android.Support.V4.App.FragmentManager fm) : base(v, fm) { } public override List<MyTestPageFragmentContainer> setupTabs() { return new List<MyTestPageFragmentContainer> { new MyTestPageFragmentContainer ("Tab1", new PageFragment2 ()), new MyTestPageFragmentContainer ("Tab2", new PageFragment1 ()), new MyTestPageFragmentContainer ("Tab3", new PageFragment2 ()), new MyTestPageFragmentContainer ("Tab4", new PageFragment1 ()) }; } } public class MyTestRecyclerAdapter : RecyclerView.Adapter { public Android.Support.V4.App.FragmentManager FM; readonly List<MyTestDataObj> myTestDataList = new List<MyTestDataObj>{ new MyTestDataObj {type = MyTestAdapterItemType.Type1, data = "Item 1"}, new MyTestDataObj {type = MyTestAdapterItemType.Type1, data = "Item 2"}, new MyTestDataObj {type = MyTestAdapterItemType.Type2, data = "Item 3"}, new MyTestDataObj {type = MyTestAdapterItemType.Type3, data = "Item 4"}, new MyTestDataObj {type = MyTestAdapterItemType.Type1, data = "Item 5"}, new MyTestDataObj {type = MyTestAdapterItemType.Type2, data = "Item 6"}, new MyTestDataObj {type = MyTestAdapterItemType.Type1, data = "Item 7"}, new MyTestDataObj {type = MyTestAdapterItemType.Type1, data = "Item 8"}, new MyTestDataObj {type = MyTestAdapterItemType.Type2, data = "Item 9"}, new MyTestDataObj {type = MyTestAdapterItemType.Type3, data = "Item 10"}, new MyTestDataObj {type = MyTestAdapterItemType.Type1, data = "Item 11"}, new MyTestDataObj {type = MyTestAdapterItemType.Type2, data = "Item 12"}, new MyTestDataObj {type = MyTestAdapterItemType.Type1, data = "Item 13"}, new MyTestDataObj {type = MyTestAdapterItemType.Type1, data = "Item 14"}, new MyTestDataObj {type = MyTestAdapterItemType.Type2, data = "Item 15"}, new MyTestDataObj {type = MyTestAdapterItemType.Type3, data = "Item 16"}, new MyTestDataObj {type = MyTestAdapterItemType.Type1, data = "Item 17"}, new MyTestDataObj {type = MyTestAdapterItemType.Type2, data = "Item 18"}, new MyTestDataObj {type = MyTestAdapterItemType.Type1, data = "Item 19"}, new MyTestDataObj {type = MyTestAdapterItemType.Type1, data = "Item 20"}, new MyTestDataObj {type = MyTestAdapterItemType.Type2, data = "Item 21"}, new MyTestDataObj {type = MyTestAdapterItemType.Type3, data = "Item 22"}, new MyTestDataObj {type = MyTestAdapterItemType.Type1, data = "Item 23"}, new MyTestDataObj {type = MyTestAdapterItemType.Type2, data = "Item 24"}, }; #region implemented abstract members of Adapter public override void OnBindViewHolder (RecyclerView.ViewHolder holder, int position) { MyTestDataObj item = myTestDataList [position]; switch (GetItemViewType (position)) { case (int) MyTestAdapterItemType.Type1: { ((Type1ViewHolder)holder).TitleTextView.Text = item.data; ((Type1ViewHolder)holder).RefreshViewPager (); break; } case (int) MyTestAdapterItemType.Type2: { ((Type2ViewHolder)holder).TitleTextView.Text = item.data; ((Type2ViewHolder)holder).RefreshViewPager (); break; } case (int) MyTestAdapterItemType.Type3: { ((Type3ViewHolder)holder).TitleTextView.Text = item.data; ((Type3ViewHolder)holder).RefreshViewPager (); break; } } } public override RecyclerView.ViewHolder OnCreateViewHolder (Android.Views.ViewGroup parent, int viewType) { LayoutInflater layoutInflater = LayoutInflater.From (parent.Context); switch (viewType) { case (int) MyTestAdapterItemType.Type1: { return new Type1ViewHolder (layoutInflater.Inflate (Resource.Layout.MyTestCardViewLayout, parent, false), FM); } case (int) MyTestAdapterItemType.Type2: { return new Type2ViewHolder (layoutInflater.Inflate (Resource.Layout.MyTestCardViewLayout, parent, false), FM); } case (int) MyTestAdapterItemType.Type3: { return new Type3ViewHolder (layoutInflater.Inflate (Resource.Layout.MyTestCardViewLayout, parent, false), FM); } } return null; } public override int ItemCount { get { return myTestDataList.Count; } } public override int GetItemViewType (int position) { return (int)myTestDataList [position].type; } #endregion } } 

Here is a demo:

Demo

Note. I’m sure it’s possible, since the Google Analytics app for iOS and Android has the same design as I'm looking for. Google Analytics App for Android

I would be grateful for any help, thanks

+2
android android-fragments xamarin android-recyclerview android-cardview
source share
1 answer

From what I understand, you have a simulated question for mine. I managed to solve the problem by switching to the PagerAdapter, as it gives you more control over the pages created and displayed.

This is the solution I came up with:

PagerAdapter Class:

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

And the updated onBindViewHolder () of the parent 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); ... } 

I noticed a slight increase in memory usage, but the user interface is very fluid. I assume that some additional optimizations can be made on how many pages are stored and how they are destroyed and restored.

Your requirements are a little different, but the logic behind this should be the same. Hope this solves your problem.

+3
source share

All Articles