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.
Related
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 =)
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.
i'm new to android,
I've been working on a project, and in my news feeds page, I'm trying to include a modular feed RecyclerView, which shows a question with different answer forms, varrying according to the Question type. The way I was doing it so far was by using the include and turning the forms visible when needed. recently since i added more modules, the app started to slowdown segnificantly, so i'm trying to implement ViewStubs.
This is my RecyclerView adapter:
public class ReQuestionAdapter extends RecyclerView.Adapter<FeedItem> {
private ArrayList<Question> myQuestions;
public ReQuestionAdapter(Context context, ArrayList<Question> questions) {
myQuestions = questions ;
}
#Override
public FeedItem onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item_re_question, parent, false);
return new FeedItem(view);
}
#Override
public void onBindViewHolder(FeedItem holder, int position) {
Question q = myQuestions.get(position);
holder.bindQuestion(q);
}
#Override
public int getItemViewType(int position) {
return 0;
}
#Override
public int getItemCount() {
return myQuestions.size();
}
}
And this is the ViewHolder class for the adapter:
public class FeedItem extends RecyclerView.ViewHolder{
private Question mQuestion;
public TextView tvName;
public TextView tvTime;
public TextView tvContent;
public ProfilePictureView profilePictureView;
public ViewStub moduleView;
private int moduleType;
public FeedItem(View itemView) {
super(itemView);
}
public void bindQuestion(Question question) {
mQuestion = question;
tvTime = (TextView) itemView.findViewById(R.id.li_q_date);
tvContent = (TextView) itemView.findViewById(R.id.li_q_content);
moduleView = (ViewStub) itemView.findViewById(R.id.module_viewstub);
tvTime.setText(TimeHandler.When(mQuestion.publish_time));
tvContent.setText(mQuestion.content);
moduleType = question.type;
switch (moduleType) {
case Question.TYPE_YN:
moduleView.setLayoutResource(R.layout.module_yes_no);
moduleView.inflate();
break;
case Question.TYPE_CUSTOM:
moduleView.setLayoutResource(R.layout.module_custom);
moduleView.inflate();
break;
default:
break;
}
}
}
Now, the problem is that the ViewStub which contains a certain layout, cannot be reinflated with a new one, the reason for that is that it gets removed from the view hirarchy as soon as it leaves the screen, the symptoms:
When scrolling down the RecyclerView, the first list items that fill the screen are working perfect, but others to load when the previous leave the screen cause the FeedItem binding to bring a NullPointerException. (It canno't find it in the list item layout).
I'm looking for a solution as efficiant as ViewStubs, or a way to make them work properly, since I got many modules and inflating them all in each item as invisible would make my app slow.
In your bindQuestion() method you are referencing two different layouts to inflate, so in essence you have two different view types.
Adapter views have an efficient way way to handle this built right in.
Start by overriding getItemViewType(). When the item at position gets the module_yes_no layout, return 0. When it gets the module_custom layout, return 1.
Then in onCreateViewHolder(), when the viewType parameter is 0, inflate a list_item_re_question view complete with the module_yes_no layout. When viewType == 1, inflate the module_custom version of the view.
Now when you get a view in onBindViewHolder(), it will already have the correct subview, so you proceed to fill out that view as needed. By using getItemViewType(), the RecyclerView is working with you to recycle the exact view you need.
You can even have two FeedItem subclasses, one for module_yes_no and one for module_custom, so in onBindViewHolder(), you just check the class of the ViewHolder and branch accordingly.
That should help improve the performance of your app.
I am having a gridView and the data in the gridView is coming from server.
Now I am having some views in it that will show for some rows and will not show for some rows depends on the sever conditions.
Ex : I am having a LinearLayout which is having an imageView and 2 TextViews, this layout will be visible only for some rows based on server data.
First time it is coming fine but as I scroll down/up, the view of the rows get change.
For Ex: Like in the first row If I am not having this LinearLayout and in 2nd or 3rd row this layout is visible, the when I scroll down and then again scroll up, the first row also get that Layout exact same as the last scrolled position.
I am using Holder pattern, can you please help me here, I am stuck here.
Thank you so much in advanced.
The views are stateless so if you show the linearlayout on someviews you need to remember to hide it for the others.
onBindViewHolder will not give you a fresh view from xml but the view you mutated. Basically just remember to set the LinearLayout back to gone.
A better way would be to use multiple xml files and implement getItemViewType showing and hiding views can cause the scroll to gitter, although if heights remain the same you might get away with it.
public class ExampleAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
List<ContactsContract.Data> data;
private static final int TYPE_A = 0;
private static final int TYPE_B = 1;
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder;
if(viewType == TYPE_A) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.xml_a, parent, false);
viewHolder = new RecyclerView.ViewHolder(view);
} else {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.xml_b, parent, false);
viewHolder = new RecyclerView.ViewHolder(view);
}
return viewHolder;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
holder.setData(data.get(position);
}
#Override
public int getItemViewType(int position) {
if(data.get(position).youCondition()) {
return TYPE_A;
} else {
return TYPE_B;
}
}
#Override
public int getItemCount() {
return data.size();
}
}
This is a basic example of how it could be done. Will need to implement your own ViewHolders i'd suggest making a different one for each view type from a base class that has the set data method.