To show a list of items I usually use a RecyclerView but I have a requirement that makes it easier for me to use a vertical LinearLayout. I don't want to loose the good resource management of the RecyclerView so I'm looking for a different solution and hope someone can help me.
I think of a layout with some views on top, then a list of items followed by some more views on the bottom. Or a layout that contains more than one RecyclerView. So you have some Views on the top, a list of items followed by some more views and another list of items etc.
My layout could look like this:
View1
View2
RecyclerView
View3
RecyclerView
View4
Usually a RecyclerView works like a scrollable frame in which you can scroll some content. It means that if you start scrolling, your whole screen gets stuck in a certain position and you are just scrolling in the RecyclerView until its end. Then you can continue scrolling the whole screen.
What I want is a RecyclerView that is fully inflated so you are always scrolling the whole screen instead of just the RecyclerView but not loosing the resource management of the RecyclerView.
Does anybody know of a solution of this?
add below code to your custom adapter of recycler view
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE_NORMAL) {
View normalView = LayoutInflater.from(getContext()).inflate(R.layout.my_normal_row, null);
return new MyNormalViewHolder(normalView); // view holder for normal items
} else if (viewType == ITEM_TYPE_HEADER) {
View headerRow = LayoutInflater.from(getContext()).inflate(R.layout.my_header_row, null);
return new MyHeaderViewHolder(headerRow); // view holder for header items
}
}
What i suggest is to use a single recyclerview with custom adapter and inflate each row of recyclerview with diff layout ie.... based on your condition
In this situation i use only one RecycleView implementing my own getItemViewType
The best answer will depend on what behaviour you want for the screen as a whole.
"What I want is a RecyclerView that is fully inflated so you are always scrolling the whole screen instead of just the RecyclerView but not loosing the resource management of the RecyclerView."
This requirement makes me think that the best option indeed is a single RecyclerView using different ViewHolder types like #Rissmon Suresh and #betorcs mentioned.
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
class HeaderViewHolder extends RecyclerView.ViewHolder {
...
}
class NormalViewHolder1 extends RecyclerView.ViewHolder {
...
}
#Override
public int getItemViewType(int position) {
// use position to decide what kind of View you want
// can be fixed or you can access the data to check dinamically
if(position == 0)
return 0;
else
return 1;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 0: return new HeaderViewHolder(...);
case 1: return new NormalViewHolder1(...);
//...
}
}
}
Hope this helps =)
Related
The app I am working on contains lots of listviews. In one case, I have a recyclerView that leverages the GridLayoutManager to create a two column view. I haven't worked with recyclerViews yet as far as adapters go but here is the problem I am having. Each item in the view is being sized to the same height despite having the appropriate wrap_content attributes. I guess my question would be, is there a trick to pull this off where each child element has a different height? Is something going on behind the scenes with a recyclerView that causes all children to have a fixed height of the tallest child element? Does this sound like a recycler problem with the view holders?
My adapter logic is as follows
public ClubViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.onboard_club, parent, false);
return new ClubViewHolder(itemView, mRecyclerClickListener);
}
public void onBindViewHolder(final ClubViewHolder holder, int position) {
Item club = clubList.get(position);
if (null != club) {
holder.clubTitleText.setText(club.getName());
holder.subtitleText.setText(context.getResources().getString(R.string.club_members,club.getNumberMembers()+""));
// Do some imageView logic here
}
}
Do I need to set height in here programmatically?
EDIT: To clarify further, the layout I inflate is the layout in question here. It is a Vertical LinearLayout containing an imageView, and two text views. I need the LinearLayout to wrap the children XML attributes but it currently isn't doing that despite the LL having a height of wrap_content and all children height set to wrap_content as well
SOLUTION: Solution was posted by a user below. I will leave the update here for anyone struggling in the future.
Layout I need: Two columns whose children vary in height
Tie it in with an adapter as you normally would.
Set a StaggeredGridLayoutManager on the recyclerView widget
In the manager constructor pass in a spanCount of 2 and an Orientation of Vertical
Boom, close the sprint ticket and forget about it
Thanks again Stack Community, cheers!
The best way to control placement and size of item for RecyclerView is through its LayoutManager.
If you're looking for GridView with elements that resize themselves according to the content you can use StaggeredGridLayoutManager
Nice example of Staggered Grid Layout can be found here.
On the other hand if you need just few "groups" of different Items you can inflate different ViewHolders depending on some criteria. Here is the sample code for two types of views.
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == FILE_FLAG) {
ViewGroup view = (ViewGroup) LayoutInflater.from(parent.getContext())
.inflate(R.layout.file_item, parent, false);
return new FileViewH(view);
} else if (viewType == DIR_FLAG) {
ViewGroup view = (ViewGroup) LayoutInflater.from(parent.getContext())
.inflate(R.layout.dir_item, parent, false);
return new DirViewH(view);
}
}
I have a layout with order list and last row is the total price of those order. And I don't have an idea that how can I do those list items and last row with my desire custom item, together in a recycler view. Do I make some logic in onBindViewHolder? Or, does it have another way, one of the RecyclerView methods?
You can do this by using below code on your RecyclerAdapter class
#Override
public int getItemViewType(int position) {
if(position==(getItemCount()-1))return 1;
else return 2;
}
In onCreateViewHolder inflate your last layout according to your viewType.
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == 1) {
// inflate your first item layout & return that viewHolder
} else {
// inflate your second item layout & return that viewHolder
}
}
Yes, you can do that using ItemTypes,RecyclerView can render different type of child views, Please refer to this example : RecyclerView With Multiple Item Types
You can use simple relative or linear layout to display Total price of those orders.
And this layout will place below the recyclerview / Listview in XMl.
if you use above solution then you don't need to use last item of list for displaying total price of those orders.
I am trying to make layout like guardian app. I know what is gridview and how to design it and inflate it with data etc.
what i want to design?
This layout have items with images and not with and there is also lazy loading going on in it.
What are the problem i am facing?
1-Confused which viewi should i go with. GridView,ListView or
RecyclerView.
2-if i go with GridView then how to have different item layouts for
some items.
What i have tried?
I have tried using linear layout as seperate xml and then i add that xml to root layout on run time. it works somewhat but problem rise when i need to add clicklistener to show relevent post since there would be more than 100+ post data.
It would be a lot of help if somebody guide me in right direction. Thanks!
EDIT. After going through the answer here. I used this approach. I used to xml. Then i change the layout with getViewType in adapter but that doesn't give such results. I am still looking for more convincing solution.
Here is the code that i have tired.
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder>{
MainDTO mainDTO;
public RecyclerAdapter(MainDTO mainDTO){
this.mainDTO=mainDTO;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
ViewHolder viewHolder;
switch (viewType){
case 0:
view= LayoutInflater.from(parent.getContext()).inflate(R.layout.header,parent,false);
viewHolder=new ViewHolder(view,viewType);
return viewHolder;
default:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.box,parent,false);
viewHolder=new ViewHolder(view,viewType);
return viewHolder;
}
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
ImageLoader imageLoader = ImageLoader.getInstance();
if(position == 0){
imageLoader.displayImage(mainDTO.getPosts().get(position).getThumbnail_images().getFull().getUrl(),holder.thumbnail);
holder.title.setText(mainDTO.getPosts().get(position).getTitle());
}
else if (position > 0 ){
if(mainDTO.getPosts().get(position).getThumbnail_images()!=null)
imageLoader.displayImage(mainDTO.getPosts().get(position).getThumbnail_images().getFull().getUrl(),holder.thumbnail);
holder.title.setText(mainDTO.getPosts().get(position).getTitle());
}
}
#Override
public int getItemCount() {
return mainDTO.getPosts().size();
}
#Override
public int getItemViewType(int position) {
int viewType = 1; //Default is 1
if (position == 0) viewType = 0; //if zero, it will be a header view
return viewType;
}
public static class ViewHolder extends RecyclerView.ViewHolder{
public TextView title;
public ImageView thumbnail;
public ViewHolder(View itemView,int viewType) {
super(itemView);
if(viewType == 0){
title = (TextView)itemView.findViewById(R.id.tv_title);
thumbnail = (ImageView)itemView.findViewById(R.id.iv_thumbnail);
}else if(viewType == 1){
title = (TextView)itemView.findViewById(R.id.tv_title_2);
thumbnail = (ImageView)itemView.findViewById(R.id.iv_thumbnail_2);
}
}
}
}
You will need to use StaggeredGridLayoutManager with RecyclerView to achieve what is being done in the guardian app. See this link StaggeredGridLayoutManager Tutorial
Edit 1
I have written a small sample application which can demonstrate what guardian application is achieving. Here is the Github link. I will explain it along the way with each step:
I used a StaggerdGridLayoutManager since in guardian app you are referring to have occupied different cell heights. This layout enables us to have items with different height.
For every different view type we have to create different view holders. For instance I have created 3 different view holders for every different item type in the sample application.
Override getItemViewType to let the recyclerview adapter know which view to inflate.
For instance of sample, I stored my data objects in an List of type Object to store heterogeneous objects and checked every item if its an instance of a particular class. I created 3 different types:
#Override
public int getItemViewType(int position) {
// we check here which item type to return based on object type
if (items.get(position) instanceof ImageModel)
return ITEM_TYPE_IMAGE;
if (items.get(position) instanceof TextViewModel)
return ITEM_TYPE_TEXT;
if (items.get(position) instanceof ButtonModel)
return ITEM_TYPE_BUTTON;
return -1;
}
Get itemViewType for the current view holder in OnCreateViewHolder in order to determine which layout to inflate.
For the instance of sample:
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType){
case ITEM_TYPE_IMAGE:
View image = ((LayoutInflater)BaseApplication.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.item_image, parent, false);
return new ImageViewHolder(image);
case ITEM_TYPE_BUTTON:
View button = ((LayoutInflater)BaseApplication.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.item_button, parent, false);
return new ButtonViewHolder(button);
case ITEM_TYPE_TEXT:
View text = ((LayoutInflater)BaseApplication.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.item_text, parent, false);
return new TextViewHolder(text);
}
return null;
}
Make specific type of items cover full row span.
Since some posts types are occupying full span in guardian application, we can use below code in OnBindViewHolder method to make any item expand to full span of layout.
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder.getItemViewType() == ITEM_TYPE_IMAGE){
StaggeredGridLayoutManager.LayoutParams layoutParams = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams();
layoutParams.setFullSpan(true);
}
}
This makes the item cover all the span of layout like the biggest post in guardian application.
By following above steps, you can create a similar layout like this (image from sample github application):
Here there are 3 different item types: above two items are Buttons, middle one is ImageView and bottom are TextView.
You can use recycler view with GridLayoutManager. And In your adapter make different layout type as per your requirements.
GridLayoutManager manager = new GridLayoutManager(getActivity(), 6);
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
// return your span size as per your layout type.
return 6;
}
}
});
Go to this for more info.
Edit:
Follow my github demo
There is three different kinds of views, one large grid, two small grids and 3-4 list view items. It is hard to use only one kind of view to complete such a task.
I suggest you to create some custom views to handle the grids (large and small), and a list view to handle the list item. After that, you can reuse the custom views for the grids and the list view's custom adapter in other sessions.
If you really want to use one Grid view to handle different views, then create a generic view that has all the functions and disable/enable the functions when you needed. However, this is much more complicated.
You can use RecyclerView because it gives you method to define different item types. but still to create such view you have to do so much code on the basis of its layout.
You have to override getItemViewType method and try to find which view type will be next to display. example code
#Override
public int getItemViewType(int position) {
if (isPositionHeader(position))
return TYPE_HEADER;
return TYPE_ITEM;
}
Hope this will help
You require: Asymmetric Gridview
https://github.com/felipecsl/AsymmetricGridView
Above link will help.
You can define a common onClickListener
I will explain You How to do instead of writing the whole code we will use Recylerview for it
Recylervew which have four Items (Item1,Item2,Item3,Item4)
Item1 : it will contains the View1
Item2 : It will contains the VIew2
Item3 : It will contains the VIew3
Item4 : It will contains the VIew4
View1 : It will contains one Layout for text and other layout for comment and day section
View2 : It will contain the Image View
View3 : It will contains the tablelayout with one row and two columns
View4 : It will contains the linearlayout
TO achieve above layout design, you need to use recyclerView with StaggaredGridLayoutManager.
You have to use RecyclerView with StaggeredGridLayoutManager.
I'm trying to implement the below image. My first thought was to have everything above the grid layout be the first row of the grid and use SpanSizeLookup to set the span size to the number of columns in the RecyclerView, but this feels like something that will give me a lot of problems.
I've been reading about putting a RecyclerView inside a NestedScrollView, people say it works, but I can't seem to get it to work properly. The scrolling doesn't seem to work right, I can't get the grid to even show up without setting a minHeight, but then it just looks bad.
Is there another option I'm not considering or is one of these the direction I should be going?
What kind of problems are you anticipating from SpanSizeLookup? You can implement it with a few lines as follows (I'd recommend using values from integers.xml for flexibility).
GridLayoutManager glm = new GridLayoutManager(getContext(), 3);
glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override public int getSpanSize(int position) {
return (position == 0) ? 3 : 1;
}
});
If your header layout needs views and fields that your regular layout doesn't have, you'll want to create separate views and tell your adapter about them. Something like this.
#Override
public int getItemViewType(int position) {
if (position == 0)
return TYPE_HEADER;
else
return TYPE_REGULAR;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER) {
MyHeaderView view = (MyHeaderView) LayoutInflater
.from(parent.getContext())
.inflate(R.layout.my_header_view, parent, false);
return new MyHeaderViewHolder(view);
} else {
MyRegularView view = (MyRegularView) LayoutInflater
.from(parent.getContext())
.inflate(R.layout.my_regular_view, parent, false);
return new MyRegularViewHolder(view);
}
}
An example header view could be like this (you'd call bindTo() from MyHeaderViewHolder).
public final class MyHeaderView extends LinearLayout {
#Bind(R.id.image) ImageView imageView;
#Bind(R.id.title) TextView titleView;
public MyHeaderView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
ButterKnife.bind(this);
}
public void bindTo(String imageUrl, String title) {
Glide.with(getContext())
.load(imageUrl).into(imageView);
titleView.setText(title);
}
}
use StaggeredGridLayoutManager
use a different layout for your first item (the whole complex view)
get its layout params and setFullSpan
This makes the item as wide as the RecyclerView itself (similar to match_parent).
Set various click listeners in the specific ViewHolder that's responsible for this item. Using this approach would set the whole complex view to behave (scroll) as part of the RecyclerView while still making it available for (input) events.
You could have a look at Bookends
Or another(the way i usually do it) way would be to use a GridLayouManager with a SpanSizeLookUp. And use multiple ViewTypes i.e. one for Header,Footer and Items.
Go for 1 if you have only a single header and what a ListView-ish interface in your code.
Go for 2 if you are not sure about how many Custom ViewTypes you would be adding.It assures you have maximum scalability in the future.
If you are considering massive scalability,I suggest you read this article by Hannes Dorfman .
I'm using a custom CompoundView which extends LinearLayout to display items of a RecyclerView. Each item displays an article which contains multiple paragraphs and images. The CompoundView adds TextView or ImageView dynamically based on the data attached by CompoundView.setData(List<DataPiece> pieces), the number of which is unknown before data is attached. Each DataPiece object tells CompoundView whether it's a piece of text or an image. And here is the code for CompoundView.setData(List<DataPiece> pieces):
public void setData(List<DataPiece> pieces) {
removeAllViews();
for (DataPiece dataPiece : pieces) {
switch (dataPiece.getType()) {
case IMAGE:
ImageView imageView = new ImageView(getContext());
...
addView(imageView);
break;
case TEXT:
TextView textView = new TextView(getContext());
...
addView(textView);
break;
}
}
}
In the RecyclerView.Adapter.onBindViewHolder(), the data is attached to CompoundView by calling MyViewHolder.compoundView.setData(...). And it works fine when the RecyclerView is created.
However, for a CompoundView item with multiple ImageViews and TextViews, when I scroll away from it and then scroll back, the scroll becomes heavily unsmooth.
I guess it's because removeAllViews() in setData() is called, and the CompoundView creation for-loop is executed again by the recycler. But I don't know how to avoid this.
And I also wonder why the scroll is always smooth when using TextView(with Images) in a RecyclerView even it's recycled too.
Thanks in advance!
There are multiple considerations that could go into deciding what the best approach might be.
First, do you have an idea about the maximum number of items in the recycler's list? If it is just a handful, maybe you could ditch the RecyclerView approach and just add your CompoundView into a container hosted by a ScrollView.
Secondly - is the layout of each item fairly complicated (a.k.a. are there many TextViews, ImageViews etc. in it)? If yes, maybe you could take an approach that would resemble an ExpandableListView - show a summary as each list item and expand to the full layout of the item on click.
Thirdly - if none of the above is acceptable and you still want to go the current approach - don't construct/add your view in the binding method. Do it in the onCreateViewHolder, when the system expects you to construct your view (I don't know for sure but by the time you're called on onBindViewHolder your view might have been already added to the hierarchy and any hierarchical change to it has a ripple effect on its containers - but don't take my word for it, I don't actually know the view is already added, it is just an assumption). You will have to assign each item a different type, so that in onCreateViewHolder you could match the view type with the supporting data (for the addition of the corresponding number of child views); create the view from scratch each time - this way you don't need to call on removeAllViews. Something like(I left out parts of the adapter that are not relevant to the case):
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
ArrayList<DataPiecesList> mItems;
public RecyclerViewAdapter(ArrayList<DataPiecesList> items) {
mItems = items;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
CompoundView compoundView = new CompoundView();
List<DataPiece> dataPieces = mItems.get(viewType);
for (int i = 0; i < dataPieces.size(); i++)
{
// construct TextView or ImageView or whatever
compoundView.add(child);
}
MyViewHolder view = new MyViewHolder(compoundView);
return view;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
CompoundView compoundView = viewHolder.itemView;
DataPiece dataPiece = mItems.get(i);
for (int j = 0; j < compoundView.getChildCount(); j++)
{
compoundView.getChildAt(j) <- dataPiece.get(j);
}
}
#Override
public int getItemViewType(int position) {
return position;
}
#Override
public int getItemCount() {
return mItems.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
...
public MyViewHolder(View itemView) {
super(itemView);
}
}
}
RecyclerView is supposed to reuse the views. It will be slow if you throw away the already created TextView / ImageView objects and create new ones every time.
It sounds like you need a RecyclerView with multiple view types. The idea is to create multiple view holders - some of them with ImageView, the others with TextView. You'll have to override the getItemViewType(int position) method of your adapter - it should return different values for the IMAGE items and the TEXT items. The onCreateViewHolder(ViewGroup parent, int viewType) receives a viewType parameter so you know which type of ViewHolder to create there. In the onBindViewHolder(VH holder, int position) you could assume that the holder passed to you is the correct type (i.e. the type with TextView for TEXT items and the type with ImageView for IMAGE items), so there is no need to remove its child views and create them again.
There is nice article about RecyclerView's Adapters with multiple view types here.