4

I'm creating a recycler view of data objects, each of these object will vary, eg.

Obj 1 - String Heading - String Desc - Image

Obj 2 - String Desc

Obj 3 - Image - String Link

Obj 4 - String Desc - Video

etc

So I need to create my item layout dynamically to suit each data object. I don't want to create an item layout with all the possible views and components and show and hide as I feel it's bad practice.

So my issue is I need to access an object from the list using the position in the onBindViewHolder in order to construct my layout in the ViewHolder class.

I tried using a method which I called in the onBindViewHolder class and passed in the object in which I added custom views to an array, setting the content inside at the same time but this didn't work.

Does anyone know how this can be done?

Regards

DJ-DOO
  • 4,545
  • 15
  • 58
  • 98
  • Duplicate of : http://stackoverflow.com/questions/26245139/how-to-create-recyclerview-with-multiple-view-type – Dean Wild Aug 21 '15 at 10:53
  • @DeanWild It's not a duplicate, I'm trying to create dynamic layouts on the fly, these answers as far as I can figure out suggest creating multiple viewholders – DJ-DOO Aug 21 '15 at 11:02
  • 1
    It's totally duplicate. What you believe you're trying to achieve is bad practice. Create separate view holders, separate layout files and properly implement `getItemViewType` is the best practice the correct answer. – Budius Aug 21 '15 at 11:04
  • @Budius I am going to have 20+ variations of view type, so I should create 20+ layout and create 20+ viewholders? – DJ-DOO Aug 21 '15 at 11:06
  • 2
    @DJ-DOO yes. Try to imagine with 20+ variations and trying to manage all of them by dynamic generating through Java is going to be a maintenance nightmare. And with separate viewholders and separate XML it becomes much simpler. Create a nice package on your project `holders` and put there all ViewHolders. Each view holder can have a `setData(Object)` method and internally the holder "knows" how to properly work with that data. – Budius Aug 21 '15 at 11:16
  • @Budius thanks for the additional info. Can I ask one question, if I have multiple layouts, where do I inflate them? Normally you inflate your layout in the onCreateViewHolder? Thanks again. – DJ-DOO Aug 21 '15 at 11:30
  • I'll write as an answer.... check again in 5min – Budius Aug 21 '15 at 11:54

2 Answers2

6

first you proper implement view type like explained here How to create RecyclerView with multiple view type? actually, your question is duplicated of that, but I'll just write some extra organization stuff to help you deal with larger quantity of holders.

  • on this answer I'll be using ButterKnife and Picasso libraris because they're awesome: http://jakewharton.github.io/butterknife/ and http://square.github.io/picasso/

  • Create a package on your project holders and then all view holder you put inside there, below is an example of holder:

  • Create an AbstractHolder with public void bindData(Data data) so then your adapter extends RecyclerView.Adapter<AbstractHolder>:

  • Create holders like below:

example Holder1.java

public class Holder1 extends AbstractHolder {

    // that's an example of views this would use
    @Bind(R.id.text) TextView text;
    @Bind(R.id.image) ImageView image;

    // constructor as normal:
    public Holder1(View itemView){
        super(itemView);
        ButterKnife.bind(this, itemView); // init the views
    }

    // call this from the adapter
    @Override public void bindData(Data data){
        text.setText(data.text);
        Picasso.with(itemView.getContext()).load(data.url).into(image);        
    }

    // here you create an instance of this holder,
    // this way the holder and the layout it's associated with goes together
    public static Holder1 create(ViewGroup parent){
        View root = LayoutInflater.from(parent.getContext()).inflate(R.layout.holder1, parent, false);
        return new Holder1(root);
    }

}
  • then your adapter code would be:

.

@Override public AbstractHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        switch(viewType){
            case TYPE_HOLDER_1: return Holder1.create(parent);
            case TYPE_HOLDER_2: return Holder2.create(parent);
            case TYPE_HOLDER_3: return Holder3.create(parent);
            ... carry on for all types   
        }
 }

@Override public void onBindViewHolder(AbstractHolder holder, int position) {
    Object data = getItem(position);
    holder.bindData(data);
}


// the get type would be similar to that:
@Override
public int getItemViewType(int position) {
    Object data = getItem(position);
    if( ... some condition...) return TYPE_HOLDER_1;
    else if( ... other condition...) return TYPE_HOLDER_2;
    else if( ... other condition...) return TYPE_HOLDER_3;
    ... etc ...
}

conclusion:

With this method your Adapter class is just a "distribution center" for the possible types and each type "knows" how to create itself and how to handle its data.

This makes your code easy to maintain and nicely organized.

Community
  • 1
  • 1
Budius
  • 39,391
  • 16
  • 102
  • 144
  • That is excellent, however, after integrating your example with the use of Butterknife I am getting null pointers on my widgets..I will edit my OP to display the implementation – DJ-DOO Aug 21 '15 at 14:04
  • Don't. If you're having difficulties using the library, read the docs or simply don't use it. But it's really not part of your original question anymore. – Budius Aug 21 '15 at 14:05
  • Can we use Android Data Binding instead of ButterKnife? – user1209216 Mar 29 '19 at 17:05
1

In your adapter class you should do these changes:

In your case you should override the getItemViewType method

@Override
    public int getItemViewType(int position) {
        if (position < arrayList.size()) {
            return ITEM_X_TYPE;
        } else
            return ITEM_Y_TYPE;
    }

And in your onCreateViewHolder:

@Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {


        if (viewType == ITEM_X_TYPE) {
            return new XVH(mView);
        } else if (viewType == ITEM_Y_TYPE) {
            return new YVH(mView);
        }

        throw new RuntimeException("type not found");
    }

And in onBindViewHolder do this:

@Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof XVH) {

            XVH m = (XVH) holder;
            ... (continue your code)

EDIT:

Now what I suggest is that in your onCreateViewHolder you should do smth like this:

@Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

View mView = LayoutInflater.from(parent.getContext()).inflate(R.layout.your_linear_layout_container, parent, false);
        if (viewType == ITEM_X_TYPE) {
            return new XVH(mView,ITEM_X_TYPE,Object one);
        } else if (viewType == ITEM_Y_TYPE) {
            return new XVH(mView,ITEM_Y_TYPE, Object two, Object three);
        }

        throw new RuntimeException(type not found");
    }

So here you have created a view and you pass that to your view holder and create different instances of your view holder calling different constructors.

Then inside your VH constructor you do smth like this:

public class XVH extends RecyclerView.ViewHolder implements {
    int viewType;
    public XVH(View itemView,int viewType,Object one) {
        super(itemView);
        this.viewType = viewType;
        // manage your layouts here, build there here or you can inflate from other xml 
    }

public XVH(View itemView,int viewType,Object two, Object three) {
            super(itemView);
            this.viewType = viewType;
            // manage your layouts here, build there here or you can inflate from other xml 
        }

And in your onBindViewHolder you check what item type is and then you load your data.

I hope this helps you

Ultimo_m
  • 4,724
  • 4
  • 38
  • 60
  • But I am going to have 20+ variations are you suggesting that I have 20 +viewholders?! – DJ-DOO Aug 21 '15 at 11:00
  • Ok I understand, then you can have one VH and then depending from your data you can create different `VH constructors` and build the view from java and inflate what you can do easily from `XML` – Ultimo_m Aug 21 '15 at 11:12
  • @Utlimo_m sorry I don't understand completely. Shouldn't I inflate my layout in the onCreateViewHolder? Should I create 20+ layouts? Could you give me an example please...sorry, I'm just not getting my head around it – DJ-DOO Aug 21 '15 at 11:16
  • @DJ-DOO check my edited answer if it helps you, you can use both the solutions based on how your items are similiar with each other – Ultimo_m Aug 21 '15 at 11:42