ebarrenechea / header-decor

A couple of sticky header decorations for android's recycler view.
Apache License 2.0
878 stars 159 forks source link

Not working with recyclerView having more than 2 view types #59

Closed pipipzz closed 4 years ago

pipipzz commented 7 years ago

I implemented my adapter using the library, but still I am not able to make headers stick. Here is my adapter implementation:

public class StickyFeedAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements StickyHeaderAdapter<StickyFeedAdapter.HeaderViewHolder> {

    protected final String TAG = getClass().getSimpleName();
    private Context context;
    private List<FeedItems> feedItems;
    private Fragment fragment;
    String API;
    private String token;
    TinyDB tinyDB;
    RestAdapter restAdapter;
    private static int radius = Utils.dpToPx(28);
    private LayoutInflater mInflater;
    Galleri5Application application;
    MixpanelAPI mixpanel;

    private static final int ITEM_TYPE_HEADER = 0, ITEM_TYPE_CARD = 1, ITEM_TYPE_RIBBON = 2, ITEM_TYPE_LOADING = 3;

    public StickyFeedAdapter(Context context, List<FeedItems> list, Fragment fragment) {
        this.context = context;
        this.feedItems = list;
        this.fragment = fragment;

        mInflater = LayoutInflater.from(context);
        application = (Galleri5Application) ((AppCompatActivity) context).getApplication();
        API = application.getAPI();
        mixpanel = MixpanelAPI.getInstance(context, application.getMixpanelId());
        OkHttpClient okHttpClient = new OkHttpClient();
        tinyDB = new TinyDB(context);
        token = tinyDB.getString(AppConstants.GALLERI5_ACCESS_TOKEN);
        RequestInterceptor requestInterceptor = new RequestInterceptor() {
            @Override
            public void intercept(RequestFacade request) {
                request.addHeader("Accept", "application/json");
                request.addHeader("Authorization", "Token " + token);
            }
        };
        restAdapter = new RestAdapter.Builder()
                .setClient(new OkClient(okHttpClient))
                .setLogLevel(RestAdapter.LogLevel.FULL)
                .setEndpoint(API)
                .setRequestInterceptor(requestInterceptor)
                .build();
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LogUtil.i(TAG, "onCreateViewHolder called");
        if (viewType == ITEM_TYPE_HEADER) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.feed_row, parent, false);
            return new HeaderViewHolder(view);
        } else if (viewType == ITEM_TYPE_CARD) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.feed_new_card, parent, false);
            return new CardViewHolder(view);
        } else if (viewType == ITEM_TYPE_RIBBON) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.feed_row_ribbon, parent, false);
            return new RibbonViewHolder(view, context);
        } else if (viewType == ITEM_TYPE_LOADING) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_loading, parent, false);
            return new LoadingViewHolder(view);
        } else {
            return null;
        }
    }

    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
        LogUtil.i(TAG, "onBindViewHolder called");
        int viewType = getItemViewType(position);
        FeedItems currentItem = getItem(position);
        StaggeredGridLayoutManager.LayoutParams layoutParams = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams();

        if (viewType == ITEM_TYPE_HEADER) {
            final HeaderItem headerItem = currentItem.getHeaderItem();
            layoutParams.setFullSpan(true);

            if (headerItem.isFollowing()) {
                ((HeaderViewHolder) holder).mightLike.setVisibility(View.GONE);
                ((HeaderViewHolder) holder).followButton.setBackgroundResource(R.drawable.button_background_filled);
                ((HeaderViewHolder) holder).followText.setTextColor(Color.parseColor("#424447"));
                ((HeaderViewHolder) holder).followText.setText("FOLLOWING");
            } else {
                ((HeaderViewHolder) holder).mightLike.setVisibility(View.VISIBLE);
                ((HeaderViewHolder) holder).followButton.setBackgroundResource(R.drawable.button_background);
                ((HeaderViewHolder) holder).followText.setTextColor(Color.parseColor("#F0F0E9"));
                ((HeaderViewHolder) holder).followText.setText("FOLLOW");
            }

            ((HeaderViewHolder) holder).followButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (!headerItem.isFollowing()) {
                        //follow gallery
                        headerItem.setFollowing(true);
                        ((HeaderViewHolder) holder).mightLike.setVisibility(View.GONE);
                        ((HeaderViewHolder) holder).followButton.setBackgroundResource(R.drawable.button_background_filled);
                        ((HeaderViewHolder) holder).followText.setTextColor(Color.parseColor("#424447"));
                        ((HeaderViewHolder) holder).followText.setText("FOLLOWING");

                        follow(headerItem.getGalleryPk());

                    } else {
                        //unfollow gallery
                        headerItem.setFollowing(false);
                        ((HeaderViewHolder) holder).mightLike.setVisibility(View.VISIBLE);
                        ((HeaderViewHolder) holder).followButton.setBackgroundResource(R.drawable.button_background);
                        ((HeaderViewHolder) holder).followText.setTextColor(Color.parseColor("#F0F0E9"));
                        ((HeaderViewHolder) holder).followText.setText("FOLLOW");

                        unfollow(headerItem.getGalleryPk());
                    }
                }
            });

            ((HeaderViewHolder) holder).shareButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {

                }
            });

            ((HeaderViewHolder) holder).galleryName.setText(headerItem.getGalleryName());

            ((HeaderViewHolder) holder).numPhotos.setText(String.format("%s PHOTOS", String.valueOf(headerItem.getNumPhotos())));

            ((HeaderViewHolder) holder).numFollowers.setText(String.format("%s FOLLOWERS", String.valueOf(headerItem.getNumFollowers())));

        } else if (viewType == ITEM_TYPE_CARD) {
            final FeedPhoto cardItem = currentItem.getCardItem();
            layoutParams.setFullSpan(false);

            double aspectRatio = (double) cardItem.getWidth() / cardItem.getHeight();
            ((CardViewHolder) holder).image.setAspectRatio((float) aspectRatio);

            Uri uri = Uri.parse(cardItem.getPhotoUrl());
            ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
                    .setProgressiveRenderingEnabled(true)
                    .build();
            DraweeController controller = Fresco.newDraweeControllerBuilder()
                    .setImageRequest(request)
                    .setOldController(((CardViewHolder) holder).image.getController())
                    .build();
            ((CardViewHolder) holder).image.setController(controller);

            Picasso.with(context)
                    .load(cardItem.getProfilePic())
                    .resize(radius, radius)
                    .centerCrop()
                    .transform(new CircleTransform())
                    .into(((CardViewHolder) holder).profilePic);

            ((CardViewHolder) holder).username.setText(cardItem.getUserName());

            ((CardViewHolder) holder).timestamp.setText(cardItem.getTime());

            if (TextUtils.isEmpty(cardItem.getLocation())) {
                ((CardViewHolder) holder).locationBox.setVisibility(View.GONE);
            } else {
                ((CardViewHolder) holder).locationBox.setVisibility(View.VISIBLE);
                ((CardViewHolder) holder).locationText.setText(cardItem.getLocation());
            }

            if (TextUtils.isEmpty(cardItem.getCaption())) {
                ((CardViewHolder) holder).captionText.setVisibility(View.GONE);
            } else {
                ((CardViewHolder) holder).captionText.setVisibility(View.VISIBLE);
                ((CardViewHolder) holder).captionText.setText(cardItem.getCaption());
            }

            ((CardViewHolder) holder).collectButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(context, AddToCollectionActivity.class);
                    intent.putExtra("photoPk", cardItem.getPk());
                    intent.putExtra("imageUrl", cardItem.getPhotoUrl());
                    intent.putExtra("location", cardItem.getLocation());
                    intent.putExtra("caption", cardItem.getCaption());
                    context.startActivity(intent);
                    ((Activity) context).overridePendingTransition(R.anim.slide_up, R.anim.no_change);
                }
            });

        } else if (viewType == ITEM_TYPE_RIBBON) {
            RibbonItem ribbonItem = currentItem.getRibbonItem();
            layoutParams.setFullSpan(true);

            FeedRibbonAdapter feedRibbonAdapter = new FeedRibbonAdapter(context, fragment, ribbonItem);
            ((RibbonViewHolder) holder).feedRibbonAdapter = feedRibbonAdapter;

            ((RibbonViewHolder) holder).ribbonText.setText(ribbonItem.getTitle());
            ((RibbonViewHolder) holder).recyclerView.setAdapter(feedRibbonAdapter);

        } else if (viewType == ITEM_TYPE_LOADING) {
            layoutParams.setFullSpan(true);
            ((LoadingViewHolder) holder).indicator.setVisibility(View.VISIBLE);
        }
    }

    public FeedItems getItem(int position) {
        return feedItems.get(position);
    }

    @Override
    public int getItemViewType(int position) {
        LogUtil.i(TAG, "getItemViewType called");
        FeedItems currentItem = getItem(position);
        if (currentItem.getType() == 0) {
            return ITEM_TYPE_HEADER;
        } else if (currentItem.getType() == 1) {
            return ITEM_TYPE_CARD;
        } else if (currentItem.getType() == 2) {
            return ITEM_TYPE_RIBBON;
        } else {
            return ITEM_TYPE_LOADING;
        }
    }

    @Override
    public int getItemCount() {
        return feedItems == null ? 0 : feedItems.size();
    }

    @Override
    public long getHeaderId(int position) {
        LogUtil.i(TAG, "getHeaderId called");
        if (position == 0) { // don't show header for first item
            return StickyHeaderDecoration.NO_HEADER_ID;
        } else {
            return getHeaderForPosition(position);
        }
    }

    private long getHeaderForPosition(int position) {
        for (int i = position; i > 0; i--) {
            if (getItem(position) != null) {
                if (getItem(position).getType() == 0) {
                    return (long) i;
                }
            }
        }

        return StickyHeaderDecoration.NO_HEADER_ID;
    }

    @Override
    public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) {
        LogUtil.i(TAG, "onCreateHeaderViewHolder called");
        View view = mInflater.inflate(R.layout.feed_row, parent, false);
        return new HeaderViewHolder(view);
    }

    @Override
    public void onBindHeaderViewHolder(final HeaderViewHolder viewholder, int position) {
        LogUtil.i(TAG, "onBindHeaderViewHolder called");
        FeedItems currentItem = getItem(position);
        StaggeredGridLayoutManager.LayoutParams layoutParams = (StaggeredGridLayoutManager.LayoutParams) viewholder.itemView.getLayoutParams();
        final HeaderItem headerItem = currentItem.getHeaderItem();
        layoutParams.setFullSpan(true);

        if (headerItem.isFollowing()) {
            viewholder.mightLike.setVisibility(View.GONE);
            viewholder.followButton.setBackgroundResource(R.drawable.button_background_filled);
            viewholder.followText.setTextColor(Color.parseColor("#424447"));
            viewholder.followText.setText("FOLLOWING");
        } else {
            viewholder.mightLike.setVisibility(View.VISIBLE);
            viewholder.followButton.setBackgroundResource(R.drawable.button_background);
            viewholder.followText.setTextColor(Color.parseColor("#F0F0E9"));
            viewholder.followText.setText("FOLLOW");
        }

        viewholder.followButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (!headerItem.isFollowing()) {
                    //follow gallery
                    headerItem.setFollowing(true);
                    viewholder.mightLike.setVisibility(View.GONE);
                    viewholder.followButton.setBackgroundResource(R.drawable.button_background_filled);
                    viewholder.followText.setTextColor(Color.parseColor("#424447"));
                    viewholder.followText.setText("FOLLOWING");

                    follow(headerItem.getGalleryPk());

                } else {
                    //unfollow gallery
                    headerItem.setFollowing(false);
                    viewholder.mightLike.setVisibility(View.VISIBLE);
                    viewholder.followButton.setBackgroundResource(R.drawable.button_background);
                    viewholder.followText.setTextColor(Color.parseColor("#F0F0E9"));
                    viewholder.followText.setText("FOLLOW");

                    unfollow(headerItem.getGalleryPk());
                }
            }
        });

        viewholder.shareButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });

        viewholder.galleryName.setText(headerItem.getGalleryName());

        viewholder.numPhotos.setText(String.format("%s PHOTOS", String.valueOf(headerItem.getNumPhotos())));

        viewholder.numFollowers.setText(String.format("%s FOLLOWERS", String.valueOf(headerItem.getNumFollowers())));
    }

    private void follow(int galleryPk) {
        LogUtil.i(TAG, "follow called");

        PostAPI api = restAdapter.create(PostAPI.class);
        api.follow(galleryPk, new Callback<ActionResponse>() {
            @Override
            public void success(ActionResponse actionResponse, Response response) {
                LogUtil.i(TAG, "follow successful");
            }

            @Override
            public void failure(RetrofitError error) {
                LogUtil.i(TAG, "follow failed");
            }
        });
    }

    private void unfollow(int galleryPk) {
        LogUtil.i(TAG, "unfollow called");

        PostAPI api = restAdapter.create(PostAPI.class);
        api.unfollow(galleryPk, new Callback<ActionResponse>() {
            @Override
            public void success(ActionResponse actionResponse, Response response) {
                LogUtil.i(TAG, "unfollow successful");
            }

            @Override
            public void failure(RetrofitError error) {
                LogUtil.i(TAG, "unfollow failed");
            }
        });
    }

    public static class HeaderViewHolder extends RecyclerView.ViewHolder {
        @Bind(R.id.mightLike)
        TextView mightLike;

        @Bind(R.id.galleryName)
        TextView galleryName;

        @Bind(R.id.shareButton)
        RelativeLayout shareButton;

        @Bind(R.id.shareText)
        TextView shareText;

        @Bind(R.id.followButton)
        RelativeLayout followButton;

        @Bind(R.id.followText)
        TextView followText;

        @Bind(R.id.numPhotos)
        TextView numPhotos;

        @Bind(R.id.numFollowers)
        TextView numFollowers;

        public HeaderViewHolder(View view) {
            super(view);
            ButterKnife.bind(this, view);
        }
    }

    public static class CardViewHolder extends RecyclerView.ViewHolder {
        @Bind(R.id.profilePic)
        ImageView profilePic;

        @Bind(R.id.username)
        TextView username;

        @Bind(R.id.timestamp)
        TextView timestamp;

        @Bind(R.id.image)
        SimpleDraweeView image;

        @Bind(R.id.tagButton)
        ImageView tagButton;

        @Bind(R.id.reminderButton)
        ImageView reminderButton;

        @Bind(R.id.collectButton)
        RelativeLayout collectButton;

        @Bind(R.id.shareButton)
        RelativeLayout shareButton;

        @Bind(R.id.locationBox)
        LinearLayout locationBox;

        @Bind(R.id.locationText)
        TextView locationText;

        @Bind(R.id.captionText)
        TextView captionText;

        public CardViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }

    public static class RibbonViewHolder extends RecyclerView.ViewHolder {
        @Bind(R.id.recyclerView)
        FlingRecyclerView recyclerView;

        @Bind(R.id.ribbonText)
        TextView ribbonText;

        FeedRibbonAdapter feedRibbonAdapter;

        public RibbonViewHolder(View view, Context context) {
            super(view);
            ButterKnife.bind(this, view);
            LinearLayoutManager layoutManager = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
            recyclerView.setLayoutManager(layoutManager);
            SpacesItemDecoration spacesItemDecoration = new SpacesItemDecoration(Utils.dpToPx(8));
            recyclerView.addItemDecoration(spacesItemDecoration);
        }
    }

    public static class LoadingViewHolder extends RecyclerView.ViewHolder {
        @Bind(R.id.avloadingIndicatorView)
        AVLoadingIndicatorView indicator;

        public LoadingViewHolder(View view) {
            super(view);
            ButterKnife.bind(this, view);
        }
    }

    private static class SpacesItemDecoration extends RecyclerView.ItemDecoration {
        private int space;

        public SpacesItemDecoration(int space) {
            this.space = space;
        }

        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            int position = parent.getChildAdapterPosition(view);
            int count = state.getItemCount();
            if (position == 0) {
                outRect.left = space;
                outRect.right = space / 2;
            } else if (position == count - 1) {
                outRect.right = space;
                outRect.left = space / 2;
            } else {
                outRect.left = space / 2;
                outRect.right = space / 2;
            }
        }
    }
}

I see that the getHeaderId() is never called. I have one type of view which will be header and 3 other view types that would not be header and won't stick. What's wrong with my implementation?

starkej2 commented 7 years ago

I have an adapter that successfully implements StickyHeaderAdapter with 6 different view types - some with and some without headers. Are you adding a StickyHeaderDecoration item decoration to your RecyclerView?

baradvaibhav1 commented 4 years ago

@starkej2 can you provide for an example it would be very useful

starkej2 commented 4 years ago

Unfortunately don't have any non-proprietary code I can show right now. I can try to add an example to the sample app, but probably won't have time to do that in the near future.

This library isn't really concerned with how many view types exist, so any problems that occur with that use case are probably caused by an error in the implementation of the StickyHeaderAdapter.

baradvaibhav1 commented 4 years ago

@starkej2 I have a use case where in i have a list with multiple view types, eg, a contact listing grouped by alphabets, where in each group header would be the Alphabet and the other view types would be the contact them selfs. So what would be the implementation for getHeaderId(int position) return?

public long getHeaderId(int position) {
    if(data.get(position).getViewType == VIEW_HEADER){
        return position;
    else 
        return StickyHeaderDecoration.NO_HEADER_ID;
}

Also a side question, does paging if any affect the implementation?

starkej2 commented 4 years ago

@baradvaibhav1 You can kind of think of it as sections. You don't need to define a header view type in your adapter. Instead each item that should be underneath the same header should return the same header ID.

For example:

item1 - getHeaderId() returns 10001 item2 - getHeaderId() returns 10001 item3 - getHeaderId() returns 10002 item4 - getHeaderId() returns 10002 item5 - getHeaderId() returns 10003

will be rendered like this:

header (10001)
item1
item2
header (10002)
item3
item4
header (10003)
item5

The sample app isn't super thorough, but should help get you started. You probably don't need to use NO_HEADER_ID since that is designed to be used for items that are not part of a header group.