Set Sticky Header and Items Layouts Recyclerview

Andromeda picture Andromeda · Apr 2, 2018 · Viewed 7.1k times · Source

I want to build a complex layout using recyclerview android. In the layout, I want to have a camera button to the top left fixed and a recyclerview wrapped around it with gallery images. I have checked flexbox layout manager for recyclerview but it doesn't seem to match my use-case.

I want the header to be non-repeating and not to scroll with other items vertically. Here's the layout for the header:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/shareLayout"
android:layout_width="185dp"
android:layout_height="135dp"
android:layout_below="@id/trendingToolbar"
android:background="@color/black">

<ImageView
    android:id="@+id/cameraShareIV"
    android:layout_width="60dp"
    android:layout_height="60dp"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="10dp"
    app:srcCompat="@drawable/camera_white" />

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_below="@id/cameraShareIV"
    android:layout_centerHorizontal="true">

    <TextView
        android:id="@+id/infoTxt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginLeft="20dp"
        android:gravity="center_horizontal"
        android:text="@string/share_pic_video"
        android:textColor="@android:color/white"
        android:textSize="13sp"
        android:textStyle="bold" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/infoTxt"
        android:layout_marginLeft="16dp"
        android:text="@string/share_timeout_txt"
        android:textColor="@color/colorPrimaryDark"
        android:textSize="11sp"
        android:textStyle="bold" />

</RelativeLayout>

and in my activity, here's the XML:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="base.android.com.thumbsapp.UI.Fragments.TrendingFragment">

<include layout="@layout/trending_toolbar"
    android:id="@+id/trendingToolbar"/>

<android.support.v7.widget.RecyclerView
    android:id="@+id/trendingRV"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_below="@id/trendingToolbar"/>

Previously, I had the header inside the activity XML but had no way to wrap a recyclerview around it. So, I have decide to use an adapter like below:

public class TrendingAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

private static final String TAG = TrendingAdapter.class.getSimpleName();

private Context context;
private List<Trending> itemList;

private static final int HEADER = 0;
private static final int ITEMS = 1;

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View v;
    switch (viewType){
        case HEADER:
           v = LayoutInflater.from(parent.getContext()).inflate(R.layout.trending_header, parent, false);
           return new TrendingHeaderViewHolder(v);
        case ITEMS:
            v = LayoutInflater.from(parent.getContext()).inflate(R.layout.trending_items_layout, parent, false);
            return new TrendingItemsViewHolder(v);
    }
    return null;
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    Trending tr = itemList.get(position);
    if (holder instanceof TrendingHeaderViewHolder){
        ((TrendingHeaderViewHolder) holder).cameraShareIV.setOnClickListener( view -> {
            // TODO: 4/2/2018   select image from gallery
        });
    } else if (holder instanceof TrendingItemsViewHolder){
        // TODO: 4/2/2018 populate gallery items here with picasso  
    }
}

@Override
public int getItemCount() {
    return itemList.size();
}

@Override
public int getItemViewType(int position) {
    return super.getItemViewType(position);
}
}

I'm confused how to make the header stick and also what to do for getItemViewType method.

Is this the right way to approach this? Can anyone help out? Thanks.

Answer

alfredmack picture alfredmack · Apr 2, 2018

To make things simple i suggest you to look into this library

In your XML Place RecylerView into StickyHeaderView,choose horizontal or vertical orientation for your RecylerView

 <tellh.com.stickyheaderview_rv.StickyHeaderView
        android:id="@+id/stickyHeaderView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/white"
            android:scrollbars="vertical" />
    </tellh.com.stickyheaderview_rv.StickyHeaderView>

Create data bean class for each item type in RecyclerView. They should extend DataBean. Override the method public boolean shouldSticky() to decide whether the item view should be suspended on the top.

public class User extends DataBean {
    private String login;
    private int id;
    private String avatar_url;
    private boolean shouldSticky;
    @Override
    public int getItemLayoutId(StickyHeaderViewAdapter adapter) {
        return R.layout.item_user;
    }
    public void setShouldSticky(boolean shouldSticky) {
        this.shouldSticky = shouldSticky;
    }
    // Decide whether the item view should be suspended on the top.
    @Override
    public boolean shouldSticky() {
        return shouldSticky;
    }
}
public class ItemHeader extends DataBean {
    private String prefix;
    @Override
    public int getItemLayoutId(StickyHeaderViewAdapter adapter) {
        return R.layout.header;
    }
    @Override
    public boolean shouldSticky() {
        return true;
    }
}

Create ViewBinder to bind different type views with specific data beans. As you see, provideViewHolder(View itemView) corresponds for onCreateViewHolder in RecyclerView, and bindView corresponds for onBindViewHolder in RecyclerView.

public class ItemHeaderViewBinder extends ViewBinder<ItemHeader, ItemHeaderViewBinder.ViewHolder> {
    @Override
    public ViewHolder provideViewHolder(View itemView) {
        return new ViewHolder(itemView);
    }
    @Override
    public void bindView(StickyHeaderViewAdapter adapter, ViewHolder holder, int position, ItemHeader entity) {
        holder.tvPrefix.setText(entity.getPrefix());
    }
    @Override
    public int getItemLayoutId(StickyHeaderViewAdapter adapter) {
        return R.layout.header;
    }
    static class ViewHolder extends ViewBinder.ViewHolder {
        TextView tvPrefix;
        public ViewHolder(View rootView) {
            super(rootView);
            this.tvPrefix = (TextView) rootView.findViewById(R.id.tv_prefix);
        }
    }
}

Instantiate StickyHeaderViewAdapter for RecyclerView and register ViewBinders for each item types.

rv = (RecyclerView) findViewById(R.id.recyclerView);
    rv.setLayoutManager(new LinearLayoutManager(this));
    List<DataBean> userList = new ArrayList<>();
    adapter = new StickyHeaderViewAdapter(userList)
            .RegisterItemType(new UserItemViewBinder())
            .RegisterItemType(new ItemHeaderViewBinder());
    rv.setAdapter(adapter);