3

I am working with RecyclerView. In which I have to show two items in GridLayout Manager and span count of 2 to display native ads with full width. Can anyone help me.Output coming like image 1

Actual Output I need like image 2

Zoe
  • 27,060
  • 21
  • 118
  • 148
Sumit Pansuriya
  • 543
  • 10
  • 23
  • Hello Sumit, have you taken a look at the RecyclerView Item Type supported by the Adapter? https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int) your adapter will provide a container for an Ad every 4 items. For all the other cases, it will return a normal view. And so forth. – Martin Marconcini Jun 10 '19 at 11:46
  • I tried to edit the comment but can't after a while; I forgot to mention that no current layout manager will do this for you, you'll have to either extend GridLayoutManager or ... create your own. This is no "easy" task, so be prepared to suffer a lot (but it's doable). – Martin Marconcini Jun 10 '19 at 13:53

2 Answers2

6

Update July 2022

The Sample Project has been updated to latest Dependencies.

Someone also asked "what if I want to swap the layout manager at runtime based on the user choosing one?" No problem, check the updated branch. There's no Magic.

Original Answer

I think I have found a solution that may work for you. I am using Kotlin here but I'm sure the same can be achieved in Java.

Assumptions

  • You are using GridLayoutManager
  • You want to insert an "ad" every N number of items.
  • You have implemented or have ways to determine what TYPE of data you're showing (aka: you inflate different "ViewHolders" based on the itemType.
  • You don't have problems inserting placeholders in your list of immutable data so your RecyclerView adapter is not full of crazy logic and also your GridLayout doesn't have to be custom.

How?

  • A Grid layout manager, calculates the layouts/measures depending upon the number of "spans" (which I mentally refer to as Columns, even if this doesn't really mean that).
  • When you create a GridLayoutManager, you do so, by providing the number of spans said layout will span (lol?)
  • It is possible to change the number of spans(?) a view will span when laid out in the grid. By default, views will span ONE span (ok enough with the span thingy already).

Enough with the "Span" word...

Say you have a List<Thing> as your data source. You have 0..n things. Said Thing has means to be identified by Type. aka: aThing.type. Imagine you have two types:

Normal and AD.

enum Type { Normal, AD }

(for example).

so you can do:

if (thing.type == Type.AD)

Now I assume you receive a list of Thing from your repository.

You have different options (I'll explore only one, and leave others as an exercise to the reader, primarily because I don't have that much time, but also because each best solution will really depend on the context. I don't have much context here). My requirement is simple: make the ads span all the grid.

A simple solution involves mutating the list of results your Repository gives you (note, I would not touch the repo's data, make a copy, use the copy for your adapter).

So when you get your list, instead of sending it directly to the adapter, modify it:

(this is pseudo code, assuming listOfData is the data you want to show, already populated of course)

var newList = ArrayList<Thing>()

for ((index, thing) in listOfData.withIndex()) {
    // if it's an Ad, insert one, and continue adding items
    if (index % 4 == 0) {
        newList.add(AdPlaceholder())
    } else {
        newList.add(thing)
    }
}

return newList

So we return a newList based off of the original list (this is just a way of doing it, not the only one and certainly not the best one in all contexts, there may be much more than meet the eye).

What is AdPlaceholder() ?

It's just a specialized "Thing" :) (see below).

Now somewhere in your activity/fragment you probably set up your recyclerview like so: (again, pseudo code, but valid Kotlin)

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        layoutManager = GridLayoutManager(this, 2)
        mainRecyclerView.layoutManager = layoutManager
        mainRecyclerView.adapter = YourAdapter()

        // and somewhere you'll send data to this adapter...
        adapter.submitList(repository.getMyListOfThings())
  }

This would look like your initial grid.

Now, at first, I thought, what if we create a custom LayoutManager that is "intelligent" about when to display ads and whatnot.

But then I found that there's a value that is intelligently exposed by the GridLayoutManager (and that it works):

spanSizeLookup: Sets the source to get the number of spans occupied by each item in the adapter.

This is exactly what we need, to tell the Grid: hey, this item occupies N spans, but this other item occupies Y.

So, I added this:

layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
    override fun getSpanSize(position: Int): Int {
        return when (adapter.getItemViewType(position)) {
            adapter.normalViewType -> 1
            adapter.adViewType -> 2
            else -> 1 //default
        }
    }
}

And Voila!

Since this is a lot to process, I've created a sample project that shows this in action. It has a bug, hopefully you can spot it (and fix it!) :)

Note I did not use any AD loading library, that's up to you. Keep in mind ads are usually implemented as WebViews and therefore tend to be slow, asynchronous, etc. Be prepared to show placeholders when the ad hasnt loaded yet, etc. It will really depend how you implement that side of things, the sky is the limit. This has just been a simple sample I built to test my theory.

I personally thought this was going to be harder; I proved myself wrong.

Good luck.

Martin Marconcini
  • 26,875
  • 19
  • 106
  • 144
  • What if I have 4 different viewholders? – MML Jul 04 '22 at 08:54
  • What issue do you have that is different with more viewholders? The logic to decide when to show an Ad can be customized anyway you like. – Martin Marconcini Jul 04 '22 at 09:56
  • @Mmartin-marconcini I have 4 differents layouts with 4 view holders in my adapter, card, cardMagazine, title, and grid layout, when the user chooses from menuItem any one of them, it's changed the layoutManager of recyclerview immediately you can check this [question](https://stackoverflow.com/q/55171778/7639296), the problem is on grid layouts I tried to use your method in this demo app, but it's not working, it's showing the ad like the grid item (not taking the whole space) – MML Jul 04 '22 at 11:43
  • From what I see in the code posted there, they don't have 4 viewholders, but trying to change the layout inflated for the viewHolder... not sure if that would work. Create different types. – Martin Marconcini Jul 04 '22 at 11:55
  • @MML I am not sure I understand what you mean, but the sample project already has 3 different layouts (green, purple, and Ad). The Layoutmanager is hardcoded to Grid because that's what this question was about. You can swap the LayoutManager at runtime without changing the data. I've created [a branch which dynamically swaps between Grid and Linear](https://github.com/Gryzor/GridToShowAds/tree/swap_layout_managers) while still preserving the Ad every 4 items logic required here. (Also updated the old project to the latest dependencies) – Martin Marconcini Jul 04 '22 at 12:42
  • 1
    I solved the problem, it was misunderstanding from me of method `layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() ` I thought this will observe the span itself and change it depending on `adapter.getItemViewType(position)` so that sometimes I returned 1 or 0, but after a long time of search I understand what this actual dose it's like merging 2 or 3 cells in one and show the ad of it, so I changed the value on each layout thanks a lot :) – MML Jul 04 '22 at 12:51
1

In Activity :

StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
recyclerView.setAdapter(mAdapter);

In your recycler adpater OnCreateViewHolder :

switch (viewType) {

       ...........      

       case TYPE_AD: {
            View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.AD_ITEM_LAYOUT, parent, false);
            final ViewGroup.LayoutParams lp = v.getLayoutParams();
                if (lp instanceof StaggeredGridLayoutManager.LayoutParams){
                    StaggeredGridLayoutManager.LayoutParams sglp = (StaggeredGridLayoutManager.LayoutParams)lp;
                    sglp.setFullSpan(true);
                }
                return new AdViewHolder(v);
            }

       ...........

}

and do calculation for serving TYPE_AD in getItemViewType method :

@Override
    public int getItemViewType(int position) {

       .........

       if (position % 4 == 0){
          return TYPE_AD;
       }

       .........
    }
xaif
  • 553
  • 6
  • 27
  • This way every 4th item disappears in favor of an add. – S. Gissel Jan 26 '23 at 10:13
  • You can add your calculation to show ads, i gave an exmaple of showing ads at position multiple of 4 – xaif Feb 19 '23 at 14:34
  • @S.Gissel You need to have a wrapper class that holds different types of data for the recycler view. Sealed class is a best way to do it in kotlin – Ram Kumar Apr 30 '23 at 13:25