Viewpager in Android recyclerview cardview
I'm trying to design something very similar to the material design website
So basically, I have a list of cardviews. Each cardview has multiple views, and the user can slide between them. (the first card on the image)
First, I implemented a simple recyclerview containing cardview. For each cardview, I implemented a fragmentstatepageradapter, which contains multiple fragments. Users can slide through these fragments and work (a little). The problem I face is very similar to the problem of fragment in viewpager using fragmentpageradapter is blank the second time it is viewed, In this problem, the fragment will not load or disappear when you scroll up and down. I have tried all possible solutions proposed by people, but still can't solve it
I wonder if there is a better way
This 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
}
}
This is a demonstration:
Note: I'm sure this is possible because the Google Analytics app for IOS and Android has exactly the same design as the one I'm looking for
I would appreciate it. Thank you
resolvent:
As far as I know, you have a similar problem to mine. I have tried to solve this problem by switching to pageradapter, because it gives you better 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'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
}
}
}
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've noticed an increase in memory usage, but the user interface is very smooth. I guess there can be some further optimization on the number of reserved pages and how to destroy and restore them
Your requirements are different, but the logic behind them should be the same. I hope this can solve your problem