I'm having a very difficult time understanding why I'm getting a ClassCastException in my RecyclerAdapter class. I have set it up to accept two different types of views, one for regular feed items and the other for native advertisements. What is going wrong?
In my main activity, I call my constructor as follows:
feedItems = new ArrayList<>();
List<Feed> adItems = new ArrayList<>();
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
marketFeedRecyclerAdapter = new MarketFeedRecyclerAdapter(this, feedItems, new ImageLoader(new FeedItemFileCache(this)), adItems);
I get a complaint at this line in my RecyclerAdapter class, with the stack trace to follow:
bindAdItemView((AdViewHolder) viewHolder);
My stack trace:
java.lang.ClassCastException:
com.elgami.utility.LoadingRowRecyclerAdapter$LoadingViewHolder cannot be cast to com.elgami.market.MarketFeedRecyclerAdapter$AdViewHolder
at com.elgami.market.MarketFeedRecyclerAdapter.onBindViewHolder(MarketFeedRecyclerAdapter.java:80)
at android.support.v7.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:5768)
at android.support.v7.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:5801)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5037)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4913)
at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2029)
at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1414)
at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1377)
at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:578)
at android.support.v7.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3260)
at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:3069)
at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:3518)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.support.v4.widget.SwipeRefreshLayout.onLayout(SwipeRefreshLayout.java:598)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1495)
at android.view.View.layout(View.java:16636)
at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1079)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1495)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1495)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at com.android.internal.policy.PhoneWindow$DecorView.onLayout(PhoneWindow.java:2678)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2171)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1931)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1107)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6013)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858)
at android.view.Choreographer.doCallbacks(Choreographer.java:670)
at android.view.Choreographer.doFrame(Choreographer.java:606)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
at an
The following is my RecyclerAdapter class:
public class MarketFeedRecyclerAdapter extends LoadingRowRecyclerAdapter {
private static final int VIEW_TYPE_MARKET_FEED = 0;
private static final int VIEW_TYPE_AD = 1;
private final Context context;
private final List<Feed> feedItems;
private final ImageLoader feedItemImageLoader;
private FeedItemClickListener feedItemClickListener;
private boolean isLongPressed = false;
public MarketFeedRecyclerAdapter(Context context, List<Feed> feedItems, ImageLoader feedItemImageLoader, List<Feed> adItems) {
this.context = context;
this.feedItems = feedItems;
this.feedItemImageLoader = feedItemImageLoader;
this.feedItems.addAll(adItems);
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_TYPE_MARKET_FEED:
return new MarketFeedViewHolder(new FeedItemView(context));
case VIEW_TYPE_AD:
return new AdViewHolder(new MarketFeedAdItemView(context));
}
return super.onCreateViewHolder(parent, viewType);
}
// Differentiate between feedItem views and nativeAds
#Override
public int getViewType(int position) {
int viewType = VIEW_TYPE_MARKET_FEED;
if ((position % 25 == 0)) {
viewType = VIEW_TYPE_AD;
}
return viewType;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
if (getViewType(position) == VIEW_TYPE_MARKET_FEED) {
bindMarketFeedItemView((MarketFeedViewHolder) viewHolder, position - position / 6);
} else {
bindAdItemView((AdViewHolder) viewHolder);
}
}
// For Ad Objects
private void bindAdItemView(AdViewHolder viewHolder) {
showNativeAd(viewHolder);
}
private void bindMarketFeedItemView(MarketFeedViewHolder viewHolder, int position) {
final FeedItemView feedItemView = viewHolder.feedItemView;
final Feed feedDesign = feedItems.get(position);
// TODO we can probably conditionally show or hide these based on the type of feed item, same as in FeedActivitySingle
feedItemView.showOrHideEditButton(false);
feedItemView.showOrHideBuyButton(true);
feedItemView.showOrHideFlipButton(feedDesign.getDesign().getCompressedBackImage() != null);
feedItemView.showOrHidePriceText(true);
// Set the results into TextViews
feedItemView.setProductPriceText(String.valueOf(feedDesign.getDesign().getPrice()));
feedItemView.setDownloadsText(String.valueOf(feedDesign.getDesign().getDownloadCount()));
feedItemView.setLikesText(String.valueOf(feedDesign.getDesign().getLikesCount()));
feedItemView.setUsernameText(feedDesign.getDesign().getAuthor().getUsername());
feedItemView.setTimestampText(feedDesign.getTimestampText());
feedItemView.getSaveImage().setImageResource(feedDesign.isInPersonalGallery() ? R.drawable.ic_action_saved : R.drawable.ic_not_saved);
feedItemView.getLikeImage().setImageResource(feedDesign.isLiked() ? R.drawable.ic_action_like_feed_full : R.drawable.ic_action_like_feed);
feedItemView.getTrashImage().setVisibility(ParseHelper.isCurrentUser(feedDesign.getDesign().getAuthor().getObjectId()) ? View.VISIBLE : View.GONE);
feedItemView.getFeedSocialShareImage().setVisibility(View.VISIBLE);
switch(feedDesign.getDisplayedSide()) {
case FRONT:
feedItemImageLoader.DisplayImage(feedDesign.getDesign().getCompressedImage().getUrl(), feedItemView.getImage(), feedItemView.getProgressBar());
break;
case BACK:
feedItemImageLoader.DisplayImage(feedDesign.getDesign().getCompressedBackImage().getUrl(), feedItemView.getImage(), feedItemView.getProgressBar());
break;
}
if(feedDesign.getDesign().getAuthor().getProfilePicture() != null) {
feedItemImageLoader.DisplayImage(feedDesign.getDesign().getAuthor().getProfilePicture().getUrl(), feedItemView.getProfilePicture(), null); // TODO should this use profilePictureFileCache?
} else {
viewHolder.feedItemView.getProfilePicture().setImageResource(R.drawable.ic_anonymous);
}
SetCommentViews(feedItemView, feedDesign.getComments());
SetClickListeners(feedItemView, feedDesign, position);
}
#Override
protected int getContentDataSize() {
return feedItems.size();
}
class MarketFeedViewHolder extends RecyclerView.ViewHolder {
FeedItemView feedItemView;
public MarketFeedViewHolder(FeedItemView view) {
super(view);
this.feedItemView = view;
}
}
class AdViewHolder extends RecyclerView.ViewHolder {
MarketFeedAdItemView adItemView;
public AdViewHolder(MarketFeedAdItemView view) {
super(view);
this.adItemView = view;
}
}
private NativeAd nativeAd;
private AdChoicesView adChoicesView;
private void showNativeAd(AdViewHolder viewHolder){
AdSettings.addTestDevice("a6ffb7bec7af13f768f033dbfea042df");
nativeAd = new NativeAd(context, "846223392142435_1025413774223395");
nativeAd.setAdListener(new AdListener() {
#Override
public void onError(Ad ad, AdError adError) {
}
#Override
public void onAdLoaded(Ad ad) {
final MarketFeedAdItemView adItemView = viewHolder.adItemView;
// Setting the Text
adItemView.nativeAdSocialContext.setText(nativeAd.getAdSocialContext());
adItemView.nativeAdCallToAction.setText(nativeAd.getAdCallToAction());
adItemView.nativeAdTitle.setText(nativeAd.getAdTitle());
adItemView.nativeAdBody.setText(nativeAd.getAdBody());
// Downloading and setting the ad icon
NativeAd.Image adIcon = nativeAd.getAdIcon();
NativeAd.downloadAndDisplayImage(adIcon, adItemView.nativeAdIcon);
// Download and setting the cover image
/*NativeAd.Image adCoverImage = nativeAd.getAdCoverImage();*/
adItemView.nativeAdMedia.setNativeAd(nativeAd);
// Add adChoices icon
if (adChoicesView == null) {
adChoicesView = new AdChoicesView(context, nativeAd, true);
adItemView.addView(adChoicesView, 0);
}
nativeAd.registerViewForInteraction(adItemView);
}
#Override
public void onAdClicked(Ad ad) {
}
});
nativeAd.loadAd();
}
}
My RecyclerAdapter extends the following class, which helps with loading more feedItems:
public abstract class LoadingRowRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public static final int ROW_VIEW_TYPE_LOADING = 72398; // obscure number
private boolean mContainsLoadingRow;
protected abstract int getContentDataSize();
protected abstract int getViewType(int position);
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case ROW_VIEW_TYPE_LOADING:
return new LoadingViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.loading_row, parent, false));
}
throw new IllegalArgumentException("viewType is not ROW_VIEW_TYPE_LOADING. You must handle all other values of viewType (defined by getViewType) before calling super.");
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
// empty
}
#Override
public final int getItemCount() {
return mContainsLoadingRow ? (getContentDataSize() + 1) : getContentDataSize();
}
#Override
public int getItemViewType(int position) {
return (position == getContentDataSize()) ? ROW_VIEW_TYPE_LOADING : getViewType(position);
}
/**
* Sets a boolean which is used by getItemCount and in turn getItemViewType to determine which view type the row should be (loading view vs. other view).
* Should only be called when there is more results to load in an upcoming api request (determined by calling fragment).
*/
public void toggleLoadingRowOn() {
mContainsLoadingRow = true;
}
/**
* Checks to see if a loading row exists by checking an instance boolean and removes the row / clears the boolean.
* This helps to 'replace' a loading row with a different row.
*/
public void toggleLoadingRowOff() {
if (mContainsLoadingRow) {
mContainsLoadingRow = false;
// removes the loading row explicitly instead of allowing it to be 'pushed' down when new user suggestion rows are added.
// this is only required to maintain consistency with the rest of the app.
int position = getContentDataSize();
if (position >= 0) {
notifyItemRemoved(position);
}
}
}
protected class LoadingViewHolder extends RecyclerView.ViewHolder {
public LoadingViewHolder(View v) {
super(v);
}
}
}
Finally, the class which determines the threshold for loading more items:
public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
private static final int VISIBLE_THRESHOLD = 5; // The minimum amount of items to have below your current scroll position before loading more
private LinearLayoutManager mLinearLayoutManager;
public abstract void onLoadMore();
public EndlessRecyclerOnScrollListener(LinearLayoutManager linearLayoutManager) {
this.mLinearLayoutManager = linearLayoutManager;
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int totalItemCount = mLinearLayoutManager.getItemCount();
int firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();
int visibleItemCount = recyclerView.getChildCount();
int lastItemVisible = firstVisibleItem + visibleItemCount;
// once the last visible item is within VISIBLE_THRESHOLD from the bottom, we want to load more
if ((totalItemCount - lastItemVisible) <= VISIBLE_THRESHOLD) {
onLoadMore();
}
}
}
The problem in your code is there:
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
if (getViewType(position) == VIEW_TYPE_MARKET_FEED) {
bindMarketFeedItemView((MarketFeedViewHolder) viewHolder, position - position / 6);
} else {
bindAdItemView((AdViewHolder) viewHolder);
}
}
You are checking for current row viewType (and that's good), but you can actually have 3 possible viewType: market, ad and loading.
So when this
#Override
public int getItemViewType(int position) {
return (position == getContentDataSize()) ? ROW_VIEW_TYPE_LOADING : getViewType(position);
}
return ROW_VIEW_TYPE_LOADING you are casting a LoadingViewHolder to AdViewHolder.
That cause the ClassCastException.
For solving that just change the onBindViewHolder method to either include all three possible view types or use an else if instead of else.
Hope this helps
Related
Is it possible to expand and collapse multiple CardViews within a RecyclerView at the same time? I created a header (for my RecyclerView) with two buttons but I'm not sure what needs to go in the click event.
RecyclerView adapter class
public class MyRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
public boolean isSupposedToBeCollapsed;
private Context mContext;
RecyclerViewHeader header;
List<RecyclerViewItem> listItems;
ValueAnimator mAnimator;
public MyRecyclerAdapter(Context context, RecyclerViewHeader header, List<RecyclerViewItem> listItems)
{
this.mContext = context;
this.header = header;
this.listItems = listItems;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType == TYPE_HEADER)
{
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_header, parent, false);
return new MyRecyclerAdapter.VHHeader(v);
}
else if(viewType == TYPE_ITEM)
{
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_item, parent, false);
return new MyRecyclerAdapter.VHItem(v);
}
throw new RuntimeException("there is no type that matches the type " + viewType + " + make sure your using types correctly");
}
private RecyclerViewItem getItem(int position)
{
return listItems.get(position);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final Typeface iconFont = FontManager.getTypeface(mContext, FontManager.FONTAWESOME);
if (holder instanceof VHHeader)
{
final VHHeader vhHeader = (VHHeader)holder;
vhHeader.btnExpandAll.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(isSupposedToBeCollapsed){
// change visibility to 'VISIBLE'
txtB.setVisibility(View.VISIBLE);
// change direction of chevron to 'up'
txtExpandCollapse.setText(R.string.fa_icon_chevron_up);
// apply animation to the height of 'txtB'
mAnimator = slideAnimator(0, textBHeight);
// start the animation
mAnimator.start();
}
else{
}
}
});
vhHeader.btnCollapseAll.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(isSupposedToBeCollapsed){
// change visibility to 'VISIBLE'
txtB.setVisibility(View.VISIBLE);
// change direction of chevron to 'up'
txtExpandCollapse.setText(R.string.fa_icon_chevron_up);
// apply animation to the height of 'txtB'
mAnimator = slideAnimator(0, textBHeight);
// start the animation
mAnimator.start();
}
else{
}
}
});
}
else if (holder instanceof VHItem)
{
RecyclerViewItem currentItem = getItem(position-1);
final VHItem vhItem = (VHItem)holder;
vhItem.txtA.setText(currentItem.getTitle());
vhItem.txtB.setText(currentItem.getDescription());
vhItem.txtB.setVisibility(View.GONE);
vhItem.txtExpandCollapse.setText(R.string.fa_icon_chevron_down);
vhItem.txtExpandCollapse.setTypeface(iconFont);
//Add onPreDrawListener
vhItem.txtB.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
vhItem.txtB.getViewTreeObserver().removeOnPreDrawListener(this);
vhItem.txtB.setVisibility(View.GONE);
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
vhItem.txtB.measure(widthSpec, heightSpec);
vhItem.textBHeight = vhItem.txtB.getMeasuredHeight();
return true;
}
});
vhItem.cardView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(vhItem.txtB.getVisibility() == View.GONE){
vhItem.expand();
} else {
vhItem.collapse();
}
}
});
vhItem.mLinearLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(vhItem.txtB.getVisibility() == View.GONE){
vhItem.expand();
} else {
vhItem.collapse();
}
}
});
vhItem.txtExpandCollapse.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(vhItem.txtB.getVisibility() == View.GONE){
vhItem.expand();
} else {
vhItem.collapse();
}
}
});
}
}
// need to override this method
#Override
public int getItemViewType(int position) {
if(isPositionHeader(position))
return TYPE_HEADER;
return TYPE_ITEM;
}
private boolean isPositionHeader(int position)
{
return position == 0;
}
// increasing getItemcount to 1. This will be the row of header.
#Override
public int getItemCount() {
return listItems.size()+1;
}
class VHHeader extends RecyclerView.ViewHolder{
Button btnCollapseAll, btnExpandAll;
public VHHeader(View headerView) {
super(headerView);
this.btnCollapseAll = headerView.findViewById(R.id.btn_collapseall);
this.btnExpandAll = headerView.findViewById(R.id.btn_expandall);
}
}
public class VHItem extends RecyclerView.ViewHolder{
CardView cardView;
LinearLayout mLinearLayout;
RecyclerView mRecyclerView;
RelativeLayout mRelativeLayout;
TextView txtExpandCollapse, txtA, txtB;
public int textBHeight;
public VHItem(View itemView) {
super(itemView);
this.cardView = itemView.findViewById(R.id.cv);
this.mLinearLayout = itemView.findViewById(R.id.cardview_tconnections_titlerow);
this.mRelativeLayout = itemView.findViewById(R.id.my_relativelayout);
this.mRecyclerView = itemView.findViewById(R.id.my_recyclerview);
this.txtExpandCollapse = itemView.findViewById(R.id.tv_expandcollapse);
this.txtA = itemView.findViewById(R.id.tv_A);
this.txtB = itemView.findViewById(R.id.tv_B);
}
private void expand() {
// change visibility to 'VISIBLE'
txtB.setVisibility(View.VISIBLE);
// change direction of chevron to 'up'
txtExpandCollapse.setText(R.string.fa_icon_chevron_up);
// apply animation to the height of 'txtB'
mAnimator = slideAnimator(0, textBHeight);
// start the animation
mAnimator.start();
}
private void collapse() {
// change direction of chevron to 'down'
txtExpandCollapse.setText(R.string.fa_icon_chevron_down);
int finalHeight = txtB.getHeight();
ValueAnimator mAnimator = slideAnimator(finalHeight, 0);
mAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animator) {
txtB.setVisibility(View.GONE);
}
#Override
public void onAnimationStart(Animator animator) {
}
#Override
public void onAnimationCancel(Animator animator) {
}
#Override
public void onAnimationRepeat(Animator animator) {
}
});
mAnimator.start();
}
public ValueAnimator slideAnimator(int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
// update height
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = txtB.getLayoutParams();
layoutParams.height = value;
txtB.setLayoutParams(layoutParams);
}
});
return animator;
}
}
}
Fragment class
public class MyFragment extends android.support.v4.app.Fragment {
private MyRecyclerAdapter adapter;
public MyFragment() {}
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_rv, container, false);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
View v = getView();
assert v != null;
recyclerView = v.findViewById(R.id.my_recyclerview);
linearLayoutManager = new LinearLayoutManager(getActivity());
MyRecyclerAdapter adapter = new MyRecyclerAdapter(getContext(), getHeader(), getListItems());
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(adapter);
super.onActivityCreated(savedInstanceState);
}
RecyclerView recyclerView;
LinearLayoutManager linearLayoutManager;
public RecyclerViewHeader getHeader()
{
return new RecyclerViewHeader();
}
public List<RecyclerViewItem> getListItems()
{
List<RecyclerViewItem> rvItems = new ArrayList<>();
RecyclerViewItem itemA = new RecyclerViewItem();
itemA.setConnectionMode("Item A");
itemA.setConnectionName("Feature A1");
rvItems.add(itemA);
RecyclerViewItem itemB = new RecyclerViewItem();
itemB.setConnectionMode("Item B");
itemB.setConnectionName("Feature B1\nFeature B2");
rvItems.add(itemB);
RecyclerViewItem itemC = new RecyclerViewItem();
itemC.setConnectionMode("Item C");
itemC.setConnectionName("Feature C1\nFeature C2\nFeature C3");
rvItems.add(itemC);
return rvItems;
}
}
What you can do is, set a variable(boolean) that stores the state of the card (expanded/collapsed) in the RecyclerAdapter code.
Then, in your onBindViewHolder code block, write a piece of code to check if the card should be expanded or collapsed using the above-mentioned variable and the required display code.
Example:
if(isSuppossedToBeCollapsed){
textView.setVisibility = false;
}
else{
textView.setVisibility = true;
}
You can then call notifyDatasetChanged in your RecyclerAdapter code to update the recycler views when you click the button which will redraw the views and reflect the state of the cards(expanded/collapsed) accordingly.
NotifyDatasetChanged Documentation
After implementing a custom animation for these CardViews, the first two are not behaving in the way they are supposed to. Item A and Item B won't expand upon the 2nd click, but yet Item C works perfectly fine.
public class MyRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
private Context mContext;
RecyclerViewHeader header;
List<MyRecyclerViewItem> listItems;
ValueAnimator mAnimator;
public MyRecyclerAdapter(Context context, RecyclerViewHeader header, List<MyRecyclerViewItem> listItems)
{
this.mContext = context;
this.header = header;
this.listItems = listItems;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType == TYPE_HEADER)
{
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_header, parent, false);
return new MyRecyclerAdapter.VHHeader(v);
}
else if(viewType == TYPE_ITEM)
{
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_item, parent, false);
return new MyRecyclerAdapter.VHItem(v);
}
throw new RuntimeException("there is no type that matches the type " + viewType + " + make sure your using types correctly");
}
private MyRecyclerViewItem getItem(int position)
{
return listItems.get(position);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final Typeface iconFont = FontManager.getTypeface(mContext, FontManager.FONTAWESOME);
if (holder instanceof VHHeader)
{
final VHHeader vhHeader = (VHHeader)holder;
}
else if (holder instanceof VHItem)
{
MyRecyclerViewItem currentItem = getItem(position-1);
final VHItem vhItem = (VHItem)holder;
vhItem.txtA.setText(currentItem.getContinent());
vhItem.txtB.setText(currentItem.getCountry());
vhItem.txtB.setVisibility(View.GONE);
vhItem.txtExpandCollapse.setText(R.string.fa_icon_chevron_down);
vhItem.txtExpandCollapse.setTypeface(iconFont);
//Add onPreDrawListener
vhItem.txtB.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
vhItem.txtB.getViewTreeObserver().removeOnPreDrawListener(this);
vhItem.txtB.setVisibility(View.GONE);
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
vhItem.txtB.measure(widthSpec, heightSpec);
mAnimator = vhItem.slideAnimator(0, vhItem.txtB.getMeasuredHeight());
return true;
}
});
vhItem.cardView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(vhItem.txtB.getVisibility() == View.GONE){
vhItem.expand();
} else {
vhItem.collapse();
}
}
});
}
}
#Override
public int getItemViewType(int position) {
if(isPositionHeader(position))
return TYPE_HEADER;
return TYPE_ITEM;
}
private boolean isPositionHeader(int position)
{
return position == 0;
}
// increasing getItemcount to 1. This will be the row of header.
#Override
public int getItemCount() {
return listItems.size()+1;
}
class VHHeader extends RecyclerView.ViewHolder{
Button btnCollapseAll, btnExpandAll;
public VHHeader(View headerView) {
super(headerView);
this.btnCollapseAll = headerView.findViewById(R.id.btn_collapseall);
this.btnExpandAll = headerView.findViewById(R.id.btn_expandall);
}
}
public class VHItem extends RecyclerView.ViewHolder{
CardView cardView;
RelativeLayout mRelativeLayout;
TextView txtExpandCollapse, txtA, txtB;
public VHItem(View itemView) {
super(itemView);
this.cardView = itemView.findViewById(R.id.cv);
this.mRelativeLayout = itemView.findViewById(R.id.tv_rv_relativelayout);
this.txtExpandCollapse = itemView.findViewById(R.id.tv_rv_expandcollapse);
this.txtA = itemView.findViewById(R.id.tv_rv_A);
this.txtB = itemView.findViewById(R.id.tv_rv_B);
}
private void expand() {
// set Visible
txtB.setVisibility(View.VISIBLE);
// change direction of chevron to 'up'
txtExpandCollapse.setText(R.string.fa_icon_chevron_up);
mAnimator.start();
}
private void collapse() {
// change direction of chevron to 'down'
txtExpandCollapse.setText(R.string.fa_icon_chevron_down);
int finalHeight = txtB.getHeight();
ValueAnimator mAnimator = slideAnimator(finalHeight, 0);
mAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animator) {
//Height=0, but it set visibility to GONE
txtB.setVisibility(View.GONE);
}
#Override
public void onAnimationStart(Animator animator) {
}
#Override
public void onAnimationCancel(Animator animator) {
}
#Override
public void onAnimationRepeat(Animator animator) {
}
});
mAnimator.start();
}
public ValueAnimator slideAnimator(int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//Update Height
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = txtB.getLayoutParams();
layoutParams.height = value;
txtB.setLayoutParams(layoutParams);
}
});
return animator;
}
}
}
You need to re-initialize mAnimator object.
Try below,
Defind one more member variable in VHItem class to keep the textB height
public class VHItem extends RecyclerView.ViewHolder{
CardView cardView;
RelativeLayout mRelativeLayout;
TextView txtExpandCollapse, txtA, txtB;
int textBHeight; // new variable
}
Then initialize it from onPreDraw method
public boolean onPreDraw() {
vhItem.txtB.getViewTreeObserver().removeOnPreDrawListener(this);
vhItem.txtB.setVisibility(View.GONE);
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
vhItem.txtB.measure(widthSpec, heightSpec);
vhItem.textBHeight = vhItem.txtB.getMeasuredHeight();
return true;
}
Then initialize the animator before starting
private void expand() {
txtB.setVisibility(View.VISIBLE);
txtExpandCollapse.setText(R.string.fa_icon_chevron_up);
mAnimator = slideAnimator(0,textBHeight);
mAnimator.start();
}
Change the object CardView for View.
Instead of generating the event on the cardview try to do the following on the inner class VHItem:
View view;
RelativeLayout mRelativeLayout;
TextView txtExpandCollapse, txtA, txtB;
public VHItem(View itemView) {
super(itemView);
this.view = itemView;
this.mRelativeLayout = itemView.findViewById(R.id.tv_rv_relativelayout);
this.txtExpandCollapse = itemView.findViewById(R.id.tv_rv_expandcollapse);
this.txtA = itemView.findViewById(R.id.tv_rv_A);
this.txtB = itemView.findViewById(R.id.tv_rv_B);
}
now, on the method onBindViewHolder:
vhItem.view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(vhItem.txtB.getVisibility() == View.GONE){
vhItem.expand();
} else {
vhItem.collapse();
}
}
});
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
I have a list. I set cardview card for each item of the list.I have implemented remove on swipe up , but when I remove last item(last card) an IndexOutOfBoundsException is thrown.
My code:
Activity:
public class FirstPage extends Activity
{
RallyRestApi restApi;
private RecyclerView recyclerView;
private CustomAdapter adapter;
private List<MyData> data_list;
private Context mcontext;
String username;
String password;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_first_page);
mcontext=this;
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
data_list = new ArrayList<>();
load_data();
recyclerView.setLayoutManager(new LinearLayoutManager(mcontext,LinearLayoutManager.HORIZONTAL,false));
adapter = new CustomAdapter(FirstPage.this,data_list);
recyclerView.setAdapter(adapter);
username=getIntent().getStringExtra("username");
password=getIntent().getStringExtra("password");
ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.UP )
{
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target)
{
Toast.makeText(getApplicationContext(), "on Move", Toast.LENGTH_SHORT).show();
return false;
}
#Override
public void onSwiped(final RecyclerView.ViewHolder viewHolder, int swipeDir)
{
Toast.makeText(getApplicationContext(), "Task Status changed to COMPLETE", Toast.LENGTH_LONG).show();
String username = getIntent().getStringExtra("username");
String password = getIntent().getStringExtra("password");
try
{
restApi=new RallyRestApi(new URI("https://rally1.rallydev.com"),username,password);
JsonObject updatedValues = new JsonObject();
updatedValues.addProperty("State", "Completed");
UpdateRequest taskUpdate = new UpdateRequest(data_list.get(viewHolder.getAdapterPosition()).getRef(), updatedValues);
restApi.update(taskUpdate);
data_list.remove(viewHolder.getAdapterPosition());
adapter.notifyDataSetChanged();
}
catch (URISyntaxException | IOException e)
{
e.printStackTrace();
}
}
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
}
my Adapter class:
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {
private Context context;
private List<MyData> my_data;
FirstPage activity;
RallyRestApi restApi;
public CustomAdapter(FirstPage activity, List<MyData> my_data)
{
this.my_data = my_data;
this.activity=activity;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.card,parent,false);
itemView.setMinimumWidth(parent.getMeasuredWidth());
itemView.setMinimumHeight(parent.getMeasuredHeightAndState());
return new ViewHolder(itemView);
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position)
{
holder.userstory.setText("User Story: "+my_data.get(position).getUserstory());
holder.tasks.setText("Task: "+my_data.get(position).getTask());
holder.seekBar.setMax(my_data.get(position).getEstimate());
holder.seekBar.setProgress(my_data.get(position).getActual());
holder.actual_estimate.setText(my_data.get(position).getActual()+"/"+my_data.get(position).getEstimate());
holder.seekBar.setOnSeekBarChangeListener(new CircularSeekBar.OnCircularSeekBarChangeListener() {
#Override
public void onProgressChanged(CircularSeekBar circularSeekBar, int progress, boolean fromUser)
{
//IOB exception
holder.seekBar.setProgress(holder.seekBar.getProgress());
holder.actual_estimate.setText(holder.seekBar.getProgress()+"/"+my_data.get(position).getEstimate());
}
#Override
public void onStopTrackingTouch(CircularSeekBar seekBar)
{
try
{
restApi=new RallyRestApi(new URI("https://rally1.rallydev.com"),activity.username,activity.password);
JsonObject updatedValues = new JsonObject();
updatedValues.addProperty("Actuals", holder.seekBar.getProgress());
UpdateRequest taskUpdate = new UpdateRequest(my_data.get(position).getRef(), updatedValues);
restApi.update(taskUpdate);
holder.actual_estimate.setText(holder.seekBar.getProgress()+"/"+my_data.get(position).getEstimate());
holder.seekBar.setProgress(holder.seekBar.getProgress());
}
catch (URISyntaxException | IOException e)
{
e.printStackTrace();
}
}
#Override
public void onStartTrackingTouch(CircularSeekBar seekBar)
{
}
});
holder.seekBar.setProgress(holder.seekBar.getProgress());
holder.actual_estimate.setText(holder.seekBar.getProgress()+"/"+my_data.get(position).getEstimate());
}
#Override
public int getItemCount()
{
return my_data.size();
}
public class ViewHolder extends RecyclerView.ViewHolder
{
public TextView userstory,tasks,actual_estimate;
public CircularSeekBar seekBar;
//
public ViewHolder(View itemView)
{
super(itemView);
userstory=(TextView) itemView.findViewById(R.id.tvUserStory);
tasks=(TextView) itemView.findViewById(R.id.tvTask);
seekBar=(CircularSeekBar) itemView.findViewById(R.id.circularSeekBar1);
actual_estimate=(TextView) itemView.findViewById(R.id.tvactuals_estimate);
}
}
}
ErrorLog:
java.lang.IndexOutOfBoundsException: Invalid index 2, size is 2
at
java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
at java.util.ArrayList.get(ArrayList.java:308)
at
com.bmc.apetkar.akshay_rallyrest.CustomAdapter$1.onProgressChanged(CustomAdapter.java:68)
at
com.circularseekbar.CircularSeekBar.setProgress(CircularSeekBar.java:530)
at
com.bmc.apetkar.akshay_rallyrest.CustomAdapter$1.onProgressChanged(CustomAdapter.java:67)
at
com.circularseekbar.CircularSeekBar.setProgress(CircularSeekBar.java:530)
at
com.bmc.apetkar.akshay_rallyrest.CustomAdapter.onBindViewHolder(CustomAdapter.java:61)
at
com.bmc.apetkar.akshay_rallyrest.CustomAdapter.onBindViewHolder(CustomAdapter.java:32)
at
android.support.v7.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:5277)
at
android.support.v7.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:5310)
at
android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4568)
at
android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4461)
at
android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1962)
at
android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1371)
at
android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1334)
at
android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:563)
at
android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:2847)
at
android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:3145)
at android.view.View.layout(View.java:16630)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1495)
at android.view.View.layout(View.java:16630)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16630)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1495)
at android.view.View.layout(View.java:16630)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at
com.android.internal.policy.PhoneWindow$DecorView.onLayout(PhoneWindow.java:2678)
at android.view.View.layout(View.java:16630)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2171)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1931)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1107)
at
android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6013)
at
android.view.Choreographer$CallbackRecord.run(Choreographer.java:858)
at android.view.Choreographer.doCallbacks(Choreographer.java:670)
at android.view.Choreographer.doFrame(Choreographer.java:606)
at
android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
I know it might be a duplicate of IndexOutOfBoundsException but I referred it and did not understand where exactly I need to change to position-1 as list starts from 0;
The exception originates from my_data.get(position) in your onProgressChanged() listener.
This listener is called asynchronously, when progress changes, but it refers to the original position provided, when you perform the onBindViewHolder().
So when at time X you do the onBindViewHolder(), position with value 2 is valid (if there are at least 3 entries in the list). The listener will keep this value 2 and hold on to it.
Now, if you delete items and only have 2 items left, position = 2 is no longer valid, but the listener still keeps that value and when it is called, it tries to access my_data at position = 2, which has now become invalid.
To fix this, you will have to make the listener hold on to the actual data, not the position. You can do this like so:
public void onBindViewHolder(final ViewHolder holder, final int position)
{
final SomeClass data = my_data.get(position);
holder.seekBar.setOnSeekBarChangeListener(new CircularSeekBar.OnCircularSeekBarChangeListener() {
#Override
public void onProgressChanged(CircularSeekBar circularSeekBar, int progress, boolean fromUser)
{
holder.seekBar.setProgress(holder.seekBar.getProgress());
holder.actual_estimate.setText(holder.seekBar.getProgress()+"/" + data.getEstimate());
}
Before removing an item , you can check if the list is already empty.
if(!data_list.isEmpty()){
data_list.remove(viewHolder.getAdapterPosition());
adapter.notifyDataSetChanged();
}
I have a fragment that is suppose to show a list of Game objects. When the fragment starts, I read all the Game objects from the database and add them to a List. It all works fine until the size of the list reaches 12. If it does, the App crashes even before the fragment is shown. If the list is bigger than 13 then it only crashes if I scroll all the way down and try and show the Games that are below.
Any ideas on what could cause this? I don´t really believe the RecyclerView is the problem.
Here are my classes. Please let me know if you need more information.
Any help would be very welcome.
My Game class
public class Game extends RealmObject{
#PrimaryKey
private long id;
private RealmList<Coordinates> mGameCoordinates;
private RealmList<Coordinates> mDrinkingStopsCoordinates;
private RealmList<Team> mTeams;
private String mDate;
private boolean mIsGameFinished;
private long mStartTime;
private long mFinishTime;
private long mTimeTaken;
public Game(RealmList<Team> teams, String date, long id) {
mTeams = teams;
mDate = date;
this.id = id;
}
public int getGameSize(){
int size = mTeams.size();
return size;
}
public String getDate() {
return mDate;
}
public void setDate(String date) {
mDate = date;
}
public RealmList<Team> getTeams() {
return mTeams;
}
public void setTeams(RealmList<Team> teams) {
mTeams = teams;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
//Default empty constructor - must be present
public Game(){
}
public RealmList<Coordinates> getDrinkingStopsCoordinates() {
return mDrinkingStopsCoordinates;
}
public void setDrinkingStopsCoordinates(RealmList<Coordinates> drinkingStopsCoordinates) {
mDrinkingStopsCoordinates = drinkingStopsCoordinates;
}
public RealmList<Coordinates> getGameCoordinates() {
return mGameCoordinates;
}
public void setGameCoordinates(RealmList<Coordinates> gameCoordinates) {
mGameCoordinates = gameCoordinates;
}
public long getStartTime() {
return mStartTime;
}
public void setStartTime(long startTime) {
mStartTime = startTime;
}
public long getFinishTime() {
return mFinishTime;
}
public void setFinishTime(long finishTime) {
mFinishTime = finishTime;
}
public long getTimeTaken() {
mTimeTaken = mFinishTime - mStartTime;
return mTimeTaken;
}
public void setTimeTaken(long timeTaken) {
mTimeTaken = timeTaken;
}
public boolean isGameFinished() {
return mIsGameFinished;
}
public void setGameFinished(boolean gameFinished) {
mIsGameFinished = gameFinished;
}
}
My Fragment
public class OldGameFragment extends Fragment {
private static final String TAG = "OldGameFragment";
private RecyclerView mRecyclerView;
private TextView mTextViewPlayers;
private TextView mTextViewDate;
private RecyclerAdapter mRecyclerAdapter;
private Toolbar myToolbar;
private String mStringReadyToShow;
private List<Game> mGames;
private RealmResults<Game> playerRealmResults;
private List<Game> mGameRealmList = new ArrayList<>();
private Realm mRealm;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_old_game, container,false);
//DB
mRealm = Realm.getDefaultInstance();
//Singleton
ActivityStateSingleton activityStateSingleton = ActivityStateSingleton.getInstance();
activityStateSingleton.setActivityFlag(1);
mRecyclerView = (RecyclerView)v.findViewById(R.id.recyclerview_player_list);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
//Toolbar
myToolbar = (Toolbar)v.findViewById(R.id.my_toolbar_old_game);
myToolbar.setTitle(R.string.drawer_old_game);
myToolbar.setTitleTextColor(0xffffffff);
((AppCompatActivity)getActivity()).setSupportActionBar(myToolbar);
((AppCompatActivity)getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
readFromDB();
return v;
}
//Fragment
public static OldGameFragment newInstance(){
return new OldGameFragment();
}
public void readFromDB(){
RealmResults<Game> playerRealmResults = mRealm.where(Game.class).findAll();
int count = 0;
for(Game game : playerRealmResults){
mGameRealmList.add(game);
count++;
Log.d(TAG, "Count " + count);
}
mRecyclerAdapter = new RecyclerAdapter(mGameRealmList);
mRecyclerView.setAdapter(mRecyclerAdapter);
mRecyclerView.addItemDecoration(new SimpleDividerItemDecoration(getContext()));
mRecyclerAdapter.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(long id) {
Intent intent = GameOverviewActivity.toGameOverview(getActivity(), id);
startActivity(intent);
}
});
mRecyclerAdapter.notifyDataSetChanged();
}
/*RecyclerView*/
public class RecyclerHolder extends RecyclerView.ViewHolder {
private Game mGame;
private SwipeRevealLayout mSwipeRevealLayout;
private View mDeleteView;
public RecyclerHolder(View itemView) {
super(itemView);
mTextViewPlayers = (TextView)itemView.findViewById(R.id.list_item_player_teams);
mTextViewDate = (TextView)itemView.findViewById(R.id.list_item_date);
mSwipeRevealLayout = (SwipeRevealLayout)itemView.findViewById(R.id.swipe_layout);
mDeleteView = (View) itemView.findViewById(R.id.delete_layout);
}
public void bindPlayer(Game game){
mGame = game;
String name = game.getTeams().get(0).getTeamName();
int num = game.getTeams().get(0).getPlayers().size();
String text = name + "(" + num + ")";
for(Team t : game.getTeams()){
if(!text.contains(t.getTeamName())){
name = t.getTeamName();
num = t.getPlayers().size();
text += " vs. " + name + "(" + num + ")";
}
mDeleteView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(getContext(),
R.style.AlertDialogCustom));
builder.setTitle(R.string.dialog_alert_title).setMessage(R.string.delete_game)
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
mRecyclerAdapter.dismissGame(getAdapterPosition(), mGames.get(getAdapterPosition()).getId());
}
}).setNegativeButton("Nein", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(getContext(), "No", Toast.LENGTH_SHORT).show();
}
});
AlertDialog dialog = builder.create();
dialog.show();
//So it won´t show the white borders around the round egdges
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
}
});
}
mTextViewPlayers.setText(text);
String date = getResources().getString(R.string.date) + game.getDate();
String space = " ";
mTextViewDate.setText(space + date);
}
}
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerHolder>{
/* This object helps you save/restore the open/close state of each view*/
private final ViewBinderHelper mViewBinderHelper = new ViewBinderHelper();
private OnItemClickListener mOnItemClickListener;
public RecyclerAdapter(List<Game> realmResults){
mGames = realmResults;
/*To only open one row at the time*/
mViewBinderHelper.setOpenOnlyOne(true);
}
#Override
public RecyclerHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
View v = layoutInflater.inflate(
R.layout.list_item_game_rows, parent, false);
return new RecyclerHolder(v);
}
#Override
public void onBindViewHolder(RecyclerHolder holder, final int position) {
final Game game = mGames.get(position);
final String data = mGames.get(position).toString();
// You need to provide a String id which uniquely defines the data object.
mViewBinderHelper.bind(holder.mSwipeRevealLayout, data);
holder.bindPlayer(game);
View.OnClickListener listener = new View.OnClickListener() {
#Override
public void onClick(View v) {
mOnItemClickListener.onItemClick(game.getId());
}
};
mTextViewDate.setOnClickListener(listener);
mTextViewPlayers.setOnClickListener(listener);
}
#Override
public int getItemCount() {
return mGames.size();
}
/*Dismiss Game*/
public void dismissGame(int position, final long id){
mGames.remove(position);
this.notifyItemRemoved(position);
mRealm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
RealmResults<Game> gameRealmResults = mRealm.where(Game.class).equalTo("id", id).findAll();
gameRealmResults.deleteAllFromRealm();
}
});
}
public OnItemClickListener getOnItemClickListener(){
return mOnItemClickListener;
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener){
this.mOnItemClickListener = onItemClickListener;
}
}
}
EDIT:
Here is the Log:
FATAL EXCEPTION: main Process: bosseln.swenden.de.bosseln, PID: 23424
Theme: themes:{default=overlay:com.init.designloper.init_v001, iconPack:com.init.designloper.init_v001, fontPkg:com.init.designloper.init_v001, com.android.systemui=overlay:com.init.designloper.init_v001, com.android.systemui.navbar=overlay:com.init.designloper.init_v001}
java.lang.ArrayIndexOutOfBoundsException: rowIndex > available rows: 0 > 0
at io.realm.internal.LinkView.nativeGetTargetRowIndex(Native Method)
at io.realm.internal.LinkView.getTargetRowIndex(LinkView.java:81)
at io.realm.RealmList.get(RealmList.java:448)
at bosseln.swenden.de.bosseln.fragments.OldGameFragment$RecyclerHolder.bindPlayer(OldGameFragment.java:149)
at bosseln.swenden.de.bosseln.fragments.OldGameFragment$RecyclerAdapter.onBindViewHolder(OldGameFragment.java:223)
at bosseln.swenden.de.bosseln.fragments.OldGameFragment$RecyclerAdapter.onBindViewHolder(OldGameFragment.java:195)
at android.support.v7.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:6279)
at android.support.v7.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:6312)
at android.support.v7.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:5258)
at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5521)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5363)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5359)
at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2141)
at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1525)
at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1488)
at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:585)
at android.support.v7.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3506)
at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:3254)
at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:3767)
at android.view.View.layout(View.java:16639)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1079)
at android.view.View.layout(View.java:16639)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16639)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16639)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1735)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1579)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1488)
at android.view.View.layout(View.java:16639)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16639)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1735)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1579)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1488)
at android.view.View.layout(View.java:16639)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at com.android.internal.policy.PhoneWindow$DecorView.onLayout(PhoneWindow.java:2934)
at android.view.View.layout(View.java:16639)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.
Please check the rows in the database, I think there may be some null value you are getting at row number 13.
I'm using a custom recyclerView that can have a footer and a header, that shoudnt influence the animations tho, here is a video of what's happening: http://www.videosprout.com/video?id=00fae6ac-39ff-47b6-b981-803d2773b67b
Why is every view moving one position back and then back to where it was instead of not doing that?
Here's my adapter:
public class AddEventsAdapter extends HFRecyclerViewAdapter<String, AddEventsAdapter.ViewHolder> {
public AddEventsAdapter(Context context) {
super(context);
}
#Override
public void footerOnVisibleItem() {
}
#Override
public void addData(int position, String item) {
super.addData(position, item);
}
#Override
public ViewHolder onCreateDataItemViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.event_item, parent, false);
return new ViewHolder(v);
}
#Override
public void onBindDataItemViewHolder(ViewHolder holder, int position) {
holder.itemTv.setText(getData().get(position));
}
class ViewHolder extends RecyclerView.ViewHolder{
TextView itemTv;
public ViewHolder(View itemView) {
super(itemView);
itemTv = (TextView)itemView.findViewById(R.id.eventName);
}
}
}
The implementation:
final AddEventsAdapter MyAdapter = new AddEventsAdapter(this);
AddEventsRecycler.setAdapter(MyAdapter);
AddEventsRecycler.setLayoutManager(new LinearLayoutManager(this));
//add footer
final View footerView = LayoutInflater.from(this).inflate(R.layout.events_footer, AddEventsRecycler, false);
MyAdapter.setFooterView(footerView);
footerView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
MyAdapter.addData(0, "Event number" + ++g);
}
});
ArrayList<String> data = new ArrayList<>();
data.add("Vacation");
MyAdapter.setData(data);
The custom RecyclerAdapter:
public abstract class HFRecyclerViewAdapter<T, VH extends RecyclerView.ViewHolder> extends BaseRecyclerViewAdapter<T> {
public HFRecyclerViewAdapter(Context context) {
super(context);
}
private static final int TYPE_HEADER = Integer.MAX_VALUE;
private static final int TYPE_FOOTER = Integer.MAX_VALUE - 1;
private static final int ITEM_MAX_TYPE = Integer.MAX_VALUE - 2;
private RecyclerView.ViewHolder headerViewHolder;
private RecyclerView.ViewHolder footerViewHolder;
class HFViewHolder extends RecyclerView.ViewHolder {
HFViewHolder(View v) {
super(v);
}
}
public void setHeaderView(View header){
if (headerViewHolder == null || header != headerViewHolder.itemView) {
headerViewHolder = new HFViewHolder(header);
notifyDataSetChanged();
}
}
public void setFooterView(View foot){
if (footerViewHolder == null || foot != footerViewHolder.itemView) {
footerViewHolder = new HFViewHolder(foot);
notifyDataSetChanged();
}
}
public void removeHeader(){
if (headerViewHolder != null){
headerViewHolder = null;
notifyDataSetChanged();
}
}
public void removeFooter(){
if (footerViewHolder != null){
footerViewHolder = null;
notifyDataSetChanged();
}
}
public boolean isHeader(int position){
return hasHeader() && position == 0;
}
public boolean isFooter(int position){
return hasFooter() && position == getDataItemCount() + (hasHeader() ? 1 : 0);
}
private int itemPositionInData(int rvPosition){
return rvPosition - (hasHeader() ? 1 : 0);
}
private int itemPositionInRV(int dataPosition){
return dataPosition + (hasHeader() ? 1 : 0);
}
#Override
public void notifyMyItemInserted(int itemPosition) {
notifyItemInserted(itemPositionInRV(itemPosition));
}
#Override
public void notifyMyItemRemoved(int itemPosition) {
notifyItemRemoved(itemPositionInRV(itemPosition));
}
#Override
public void notifyMyItemChanged(int itemPosition) {
notifyItemChanged(itemPositionInRV(itemPosition));
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER) {
return headerViewHolder;
} else if (viewType == TYPE_FOOTER) {
return footerViewHolder;
}
return onCreateDataItemViewHolder(parent, viewType);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (!isHeader(position) && !isFooter(position))
onBindDataItemViewHolder((VH)holder, itemPositionInData(position));
if (isFooter(position)){
footerOnVisibleItem();
}
}
public abstract void footerOnVisibleItem();
#Override
public int getItemCount() {
int itemCount = getDataItemCount();
if (hasHeader()) {
itemCount += 1;
}
if (hasFooter()) {
itemCount += 1;
}
return itemCount;
}
#Override
public int getItemViewType(int position) {
if (isHeader(position)) {
return TYPE_HEADER;
}
if (isFooter(position)) {
return TYPE_FOOTER;
}
int dataItemType = getDataItemType(itemPositionInData(position));
if (dataItemType > ITEM_MAX_TYPE) {
throw new IllegalStateException("getDataItemType() must be less than " + ITEM_MAX_TYPE + ".");
}
return dataItemType;
}
public int getDataItemCount() {
return super.getItemCount();
}
/**
* make sure your dataItemType < Integer.MAX_VALUE-1
*
* #param position item view position in rv
* #return item viewType
*/
public int getDataItemType(int position){
return 0;
}
public boolean hasHeader(){
return headerViewHolder != null;
}
public boolean hasFooter(){
return footerViewHolder != null;
}
public abstract VH onCreateDataItemViewHolder(ViewGroup parent, int viewType);
public abstract void onBindDataItemViewHolder(VH holder, int position);
}
EDIT: Same is happening when removing a view, with removeData(getAdapterPosition()
why
this is happening because the element is being added to at the beginning of the array (index 0). when this happens, the RecyclerView will react as shown in the video, because the it pretends the backing data store is a list, and all elements are moved over one index, and the element is inserted in the beginning...which happens to be at the top.
you can see that it won't do this ugly behavior if elements are added at the end of the adapter: MyAdapter.addData(MyAdapter.getItemCount() - 1, "Event ");. but of course, this is not what you want either, because it is the wrong index...and now it would seem like existing elements are actually jumping up one index on the GUI and things...but this is an interesting experiment to verify the theory.
a solution
still add elements at the end of the array using MyAdapter.addData(MyAdapter.getItemCount() - 1, "Event ");, but make the LinearLayoutManager display elements in reverse! this can be done by:
setting the attribute directly like so: linearLayoutManager.setReverseLayout(true)
or you can use this handy constructor: LinearLayoutManager(context,orientation,isReversed)
you may have to do something about the headers and footers are positioned in the adapter to make sure they stay as headers and footers in the RecyclerView.Adapter though...once the RecyclerView.LayoutManager is displaying things in reverse.