Dynamic position of views in RecyclerView

情到浓时终转凉″ 提交于 2019-12-12 23:54:00

问题


My question is based on a previous question about my Code - it's a newsfeed with different views which are scrollable vertically or horizontally or display various content (ads basically)

  • VERTICAL VIEW
  • HORIZONTAL VIEW
  • AD VIEW

Now I managed with the help of Ben P, on my previous post to organize my views with the following code:

this.items = new ArrayList<>();

if (listVertical.size() > 0) {
    items.add(listVertical.remove(0));
}

if (listHorizontal.size() > 0) {
    items.add(listHorizontal.remove(0));
}

while (listVertical.size() > 0 || listAd.size() > 0) {   

    if (listAd.size() > 0) {
        items.add(listAd.remove(0));
    }

    int count = 5;

    while (count > 0 && listVertical.size() > 0) {
        items.add(listVertical.remove(0));
    }
}

This is way to static for a feel-alive newsfeed. So I tried organizing my views with a position element in my JSON, like this:

{
"horizontal": [{...,position:"1",},{...,position:"3",...},{...,position:"4",...}] ,
"vertical": [{...,position:"2",},{...,position:"5",},{...,position:"6",}] 
"ad": [{...,position:"0",},{...,position:"7",},{...,position:"8",}] 
}

And with this code here it should be possible to get the Feed in position:

int length_tot= vertical.size() + horizontal.size() + ad.size();
this.items = new ArrayList<>();

for(int i = 0; i < lenth_tot; i++) {
    if(vertical.getallthepositions_somehow == i) {
        items.add(vertical.remove(0));
    }

    if(horizontal.getallthepositions_somehow == i) {
        items.add(horizontal.remove(0));
    }

    if(ad.getallthepositions_somehow == i) {
        items.add(ad.remove(0));
    }
}

As you can see, I'm stuck on looping through all Lists finding the next position to add in my items-List.

So my question is:

What's the fastest way to find the next position to add? My solution feels like it's a way too heavy, is there a more economical solution in your mind (from the point of view of the processor)?

Here is my onBindViewHolder:

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {

    if (holder.getItemViewType() == VIEW_TYPE_VERTICAL)
        verticalView((VerticalViewHolder) holder, position);

    if (holder.getItemViewType() == VIEW_TYPE_HORIZONTAL)
        horizontalView((HorizontalViewHolder) holder, position);
}

And the getItemViewType from the adapter:

@Override
public int getItemViewType(int position) {
    if (items.get(position) instanceof Vertical)
        return VIEW_TYPE_VERTICAL;

    if (items.get(position) instanceof Horizontal)
        return VIEW_TYPE_HORIZONTAL;
}

EDIT:

So what I'm trying to get out of my JSON from the Server:

{
"horizontal": [{...,position:"1",},{...,position:"3",...},{...,position:"4",...}] ,
"vertical": [{...,position:"2",},{...,position:"5",},{...,position:"6",}] 
"ad": [{...,position:"0",},{...,position:"7",},{...,position:"8",}] 
}

is a Newsfeed with the following view-order:

- [AD] (position=0)
- [HORIZONTAL] (position =1)
- [VERTICAL] (position=2)
- [HORIZONTAL]
- [HORIZONTAL]

and so on...

So the server basically decides the order of my views in my adapter due the psoition item in each json-array

Ben P - Code for the right order in my List:

    Object[] itemsObject = new Object[listVertical.size() + listHorizontal.size() + adList.size()];

    for (Vertical item : listVertical) {
        itemsObject[Integer.parseInt(item.getPosition())] = item;
    }

    for (Horizontal item : listHorizontal) {
        itemsObject[Integer.parseInt(item.getPosition())] = item;
    }

    for (Adfeed item : adList) {
        it
    ArrayList<Object> itemsPos = new ArrayList<>();
    itemsPos.addAll(listVertical);
    itemsPos.addAll(listHorizontal);
    itemsPos.addAll(adList);

    Collections.sort(items, new Comparator<Object>() {
        @Override
        public int compare(Object o1, Object o2) {
            return Integer.compare(o1.getPosition(),o2.getPosition())
        }
    });

Vertical, Horizontal and Adfeed contain .getPosition(). So how do I access them in my Comparator?


回答1:


When implementing a RecyclerView.Adapter, you should strive to first create a single ordered list (or array) that represents your data set. If you can successfully build this list, everything else will become dramatically easier to do.

In order to build your list from the JSON sample you posted, I'd consider two different strategies. The first is to use an array so that you can assign specific indexes:

Object[] items = new Object[vertical.size() + horizontal.size() + ad.size()];

for (VerticalItem item : vertical) {
    items[item.position] = item;
}

for (HorizontalItem item : horizontal) {
    items[item.position] = item;
}

for (AdItem item : ad) {
    items[item.position] = item;
}

The second option is to use a List and then sort the list with a Comparator that compares positions. This will require that each of your three item types have a common superclass with the position field or implements an interface that exposes getPosition() etc.

List<PositionalItem> items = new ArrayList<>();
items.addAll(vertical);
items.addAll(horizontal);
items.addAll(ad);

Collections.sort(items, new Comparator<PositionalItem>() {
    @Override
    public int compare(PositionalItem lhs, PositionalItem rhs) {
        return Integer.compare(lhs.getPosition(), rhs.getPosition());
    }
});

Either of these will result in a single, sorted list that has your items in the same order you want to display them. We can now use this to implement the rest of the Adapter.

(This example is assuming you use an Object[] for your list of items. If you use List<PositionalItem> or something similar, obviously the syntax will be slightly different.)

private class MyAdapter extends RecyclerView.Adapter {

    private final Object[] items;

    public MyAdapter(Object[] items) {
        this.items = items;
    }

    @Override
    public int getItemCount() {
        return items.length;
    }

    @Override
    public int getItemViewType(int position) {
        Object item = items[position];

        if (item instanceof VerticalItem) {
            return R.layout.newsfeed_vertical;
        } else if (item instanceof HorizontalItem) {
            return R.layout.newsfeed_horizontal;
        } else if (item instanceof AdItem) {
            return R.layout.newsfeed_ad;
        } else {
            throw new IllegalStateException("unexpected item type: " + item);
        }
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View itemView = inflater.inflate(viewType, parent, false);

        switch (viewType) {
            case R.layout.newsfeed_vertical: return new VerticalViewHolder(itemView);
            case R.layout.newsfeed_horizontal: return new HorizontalViewHolder(itemView);
            case R.layout.newsfeed_ad: return new AdViewHolder(itemView);
            default: throw new IllegalStateException("unexpected viewType: " + viewType);
        }
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        switch (holder.getItemViewType()) {
            case R.layout.newsfeed_vertical: verticalView((VerticalViewHolder) holder, position);
            case R.layout.newsfeed_horizontal: horizontalView((HorizontalViewHolder) holder, position);
            case R.layout.newsfeed_ad: adView((AdViewHolder) holder, position);
            default: throw new IllegalStateException("unexpected viewType: " + viewType);
        }
    }

    // ...
}

You'll notice that I've used a trick for view types: use the layout resource id instead of an int constant. There's no need to define your own constants; the resources framework has already created these constants for you!

Anyway, I'm happy to answer any other questions you have. But the major lesson here is to worry about building your list of items first, and then use the list in all of the Adapter methods.


Rather than use ArrayList<Object> it would be better to create an interface for all three of your item types to implement, and use that:

public interface NewsfeedItem {
    int getPosition();
}

Now you can add implements NewsfeedItem to the declaration of each of your classes:

public class Vertical implements NewsfeedItem {
    // ...
}
public class Horizontal implements NewsfeedItem {
    // ...
}
public class Adfeed implements NewsfeedItem {
    // ...
}

This will "just work" as long as each of your classes has an int getPosition() method in them. It looks like maybe getPosition() returns a String right now, so either you could change the interface definition to return String or you could change the classes to return int. Either way is fine, as long as all four match each other.

Then, once you have this interface set up, you can change your sorting code to use it:

ArrayList<NewsfeedItem> itemsPos = new ArrayList<>();
itemsPos.addAll(listVertical);
itemsPos.addAll(listHorizontal);
itemsPos.addAll(adList);

Collections.sort(items, new Comparator<NewsfeedItem>() {
    @Override
    public int compare(NewsfeedItem o1, NewsfeedItem o2) {
        return Integer.compare(o1.getPosition(),o2.getPosition());
    }
});



回答2:


Actually, in any way, you need to construct a list of items for being passed in your adapter to be shown in the RecyclerView. Hence, looping through all the elements from each list (i.e. horizontal, vertical & ad) is not a very bad idea. You are actually doing some preprocessing of your items to be shown in the RecyclerView so that it can perform accordingly.

I can suggest one small improvement while preprocessing your data. In your code, you have to go through all the elements again an again for each list, in your every iteration. Which can be optimized actually. Considering this preprocessing is outside of the adapter, you might do something like this.

int total = vertical.size() + horizontal.size() + ad.size();
int[] types = new int[total];

// Considering the positions are not overlapping.
for(int i = 0; i < vertical.size(); i++) {
    types[vertical[i].position] = VIEW_TYPE_VERTICAL;
}

for(int i = 0; i < horizontal.size(); i++) {
    types[horizontal[i].position] = VIEW_TYPE_HORIZONTAL;
}

for(int i = 0; i < add.size(); i++) {
    types[add[i].position] = VIEW_TYPE_ADD;
}

// The type array has the view types for all elements now. 
// Now add the views accordingly in your items list and then pass it to your adapter.
ArrayList<Item> items = new ArrayList<Item>();
for(int i = 0; i < type.length; i++) {
    items.add(getItemBasedOnType(type[i]));
}

// Populate your adapter or pass new elements
adapter.addListItems(items);

The getItemBasedOnType can be something like this.

private Item getItemBasedOnType(int type) {
    if(type == VIEW_TYPE_VERTICAL) return vertical.remove(0);
    else if(type == VIEW_TYPE_HORIZONTAL) return horizontal.remove(0);
    else return add.remove(0);
}

Hope that helps a little.



来源:https://stackoverflow.com/questions/57045195/dynamic-position-of-views-in-recyclerview

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!