I am creating a chatting application where in I need to populate a different Xml file based on the messageType in the chats page where all the conversation with a particular user is displayed. Example if the messageType is an image I want to populate a different xml and if the messageType is a video I want to populate a different xml file, and similarly for a text and audio a different xml file each time. However in my code, I have populated a single common xml in the onCreateViewHolder method. The messageType is retrieved from the model class.
Any help how to do this ?
Thanks in advance !!
You can do this by using different "item view type" values for each messageType. The root of this logic is the adapter's getItemViewType() callback:
#Override
public int getItemViewType(int position) {
if (isImage(position)) {
return R.layout.layout_for_image;
} else if (isVideo(position) {
return R.layout.layout_for_video;
} else {
// and so on...
}
}
We're using a trick here: getItemViewType() only cares that you return an int; it doesn't care what the actual int values are, so we're using the layout ids as the return value. This is nice because it means you don't have to define extra constants or keep track of which view type goes with 0 and which goes with 1, etc.
You also need to create a different kind of ViewHolder for each view type. The viewType parameter passed to this method will have whatever value you returned from getItemViewType(), and since we returned layout ids, we can just inflate whatever value we get. Of course, we still have to pass it to the right ViewHolder, but still it makes things a little easier:
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View itemView = inflater.inflate(viewType, parent, false);
switch (viewType) {
case R.layout.layout_for_image: return new ImageViewHolder(itemView);
case R.layout.layout_for_video: return new VideoViewHolder(itemView);
...
}
}
The last piece is binding:
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
switch (holder.getItemViewType()) {
case R.layout.layout_for_image:
bindImage(holder, position);
break;
case R.layout.layout_for_video:
bindVideo(holder, position);
break;
...
}
}
Override getViewType(int index) and return different values for object types, then you get it at onCreateViewHolder and inflate the correct view with different ViewHolders, the same type of viewholder will be returned at onBindViewHolder for equals viewTypes.
Related
I want to achieve something like this
And this
How can I show this kind of empty empty list as in udemy combined with some animation like in snapchat until whole list is loaded
Now that you explained a bit better I'm editing this answer.
In order to do something similar to SkyScanner, display fake placeholders because you can't retrieve any previous information about the data you must follow these steps:
For the animation have a look on Lottie library you can do a really nice animation, and make this animation run in loop until you have your call response, check the library here: https://github.com/airbnb/lottie-android
Lottie is a nice way to do vectors animation and it uses json file so it's really light, but you can also do the animations on the old style of course.
For the placeholder the logic is this: You are going to switch the content of your data set, the adapter is going to check, if the content inside the data set is the kind of real data you want to display it is going to fill with the real xml and use the real recyclerview holder, if the kind of that is not the real data you are going to inflate the row with the placeholder xml (the same size and disposition to achieve the result you are looking for) and a new recyclerview holder for placeholder so you can control the row behaviour, for example control your animation or intercale object behaviour based on position.
Consider an object YourCustomObject as the holder of your real data, but can be any kind as long you do a different kind of data for your fake placeholder, the logic inside the adapter is this:
#Override
public int getItemViewType(int position) {
return mDataSet.get(position) instanceof YourCustomObject ? VIEW_TYPE_DATA : VIEW_TYPE_PLACEHOLDER;
}
Then
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder holder;
if (viewType == VIEW_TYPE_DATA) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_real_data, parent, false);
holder = new RealDataHolder(v);
} else {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_placeholder, parent, false);
holder = new PlaceholderHolder(v);
}
return holder;
}
Don't forget to check the type of the Holder on onBindViewHolder
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof RealDataHolder) {
YourCustomObject data = (YourCustomObject) dataSet.get(position);
(...)
Don't forget that inside the adapter your dataSet is a list of Object (java class)
I hope it helps.
My problem is: I have a video streaming happening on one of the views inside the RecyclerView.
When the user scrolls, the view gets recycled and other cameras starts their own streaming on that recycled viewholder. This is bad for user interface since the streaming process takes some seconds to start.
How can I say to the RecyclerView: "Hey Recycler, please, do not recycle that exact position x and give that position ALWAYS the same viewholder you gave it the first time, instead of random one"?
Please someone help me =(
In your getItemViewType(int position) method of adapter, assign unique values for each video, so it will always return same ViewHolder for same video as you wish.
return unique positive number as type for each video type (here i used the adapter position as unique key)
return negative numbers for any non-video items. (nothing special here, just to avoid conflicts with video items, we use negative numbers for non-video items)
I hope you get the idea. cheers :)
#Override
public int getItemViewType(int position) {
// Just as an example, return 0 or 2 depending on position
// Note that unlike in ListView adapters, types don't have to be contiguous
if(dataList.get(position).isVideo()){
return position;
}else{
return -1;//indicates general type, if you have more types other than video, you can use -1,-2,-3 and so on.
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case -1: View view1 = LayoutInflater.from(parent.getContext())
.inflate(R.layout.general_item, parent, false);
return new GeneralViewHolder(view1);
default:View view2 = LayoutInflater.from(parent.getContext())
.inflate(R.layout.video_item, parent, false);
return new VideoViewHolder(view2);
}
}
Perform viewHolder.setIsRecyclable(false) on the ViewHolder you want not to be recycled.
From docs of ViewHolder#setIsRecyclable(boolean):
Informs the recycler whether this item can be recycled. Views which are not recyclable will not be reused for other items until setIsRecyclable() is later set to true.
This will cause only one ViewHolder to be created.
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
...
#Override
public void onViewAttachedToWindow(final RecyclerView.ViewHolder holder) {
if (holder instanceof VideoViewHolder) {
holder.setIsRecyclable(false);
}
super.onViewAttachedToWindow(holder);
}
#Override
public void onViewDetachedFromWindow(final RecyclerView.ViewHolder holder) {
if (holder instanceof VideoViewHolder){
holder.setIsRecyclable(true);
}
super.onViewDetachedFromWindow(holder);
}
...
}
RecyclerView uses one view multiple times, when it contains the list which is not displaying on the screen at a time(means a list contain large amount of items which is not displaying on screen at same time you need to scroll up and down). When user scroll the list the offscreen items are reused to display the remaining list items which is called recycling.
To Stop recycling the items call this method in your onBindViewHolder method:
viewHolder.setIsRecyclable(false);
This statement stop the recycling the views.
To Start recycling the items call this method in your onBindViewHolder method:
viewHolder.setIsRecyclable(true);
I hope this will solve your problem.
Thanks
Your problem comes from the viewholder itself. Viewholders keep reference to views, while the adapter don't. The adapter keeps the data collection only. So, add a field to the viewholder to keep a reference of the data element you used to populate the view in the viewholder. In other words:
public class SomeViewHolder extends RecyclerView.ViewHolder{
private View view;
private Data data;
public SomeViewHolder(View itemView) {
super(itemView);
view = itemView;
}
public void bindData(Data data){
view.setData(data);
this.data = data;
}
public void setData(Data data){
this.data = data;
}
public Data getData(){
return data;
}
public View getView(){
return view;
}
}
Now, the viewholder know which element of the adapter is using. Therefore, when overriding the binding method in the adapter, you can check if the holder has already bonded with some data, and, if the data contains video, you can avoid the binding and forcefully set an already loaded view.
#Override
public void onBindViewHolder(SomeViewHolder holder, int position) {
//videoViewData is a data field you have to put into the adapter.
//videoView is a view field you have to put into the adapter.
if(adapterData.get(position).equals(videoViewData)){
holder.setView(videoView);
holder.setData(adapterData.get(position));
}else{
holder.bindData(adapterData.get(position));
if(adapterData.get(position).isVideo()){
videoViewData = adapterData.get(position);
videoView = holder.getView();
}
}
}
Finally, you'll have to override the onViewRecycled method in the adapter, so, when a view containing a video gets recycled, you can get the view and put it somewhere else.
public void onViewRecycled(SomeViewHolder holder){
if(holder.getData().isVideo()){
videoViewData = holder.getData().
videoView = holder.getView();
videoView.pauseVideo();
}
}
keep in mind, this can cause some serious leaks if you don't manage the stored view. Also, you have to define methods for telling when your data is video, and a properly defined equals method.
Best way to handle item not to recycle in recyclerview this answer will resolve your problem.
Not to recycle item
Try using this for that particular position:
holder.setIsRecyclable(false);
Hope this may help.
If You are using query, you can use
query.limit(//no of items you want to show in your RecyclerView)
give it a try.
or Plese post your QueryCode
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 need to add a small strip in between items of a RecyclerView. This strip can come after different number of items in a list. This needs to be done dynamically.
I need to implement something like what FitBit has done:
I also need the first row i.e. the one saying "This Week" to stick on top even if the page scrolls down.
You should use the concept of different view types using getItemViewType(int). Then on onCreateViewHolder(ViewGroup, int) you can check which type you should inflate/create.
Example:
#Override
public int getItemViewType(int position) {
// you should return the view type, based on your own dynamic logic
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
// handle each view type accordingly
}
}
Use StickyHeaderRecyclerView library
It is very easy to use
You can use the concept of multiple view types in your RecyclerView, Just by using getItemViewType(), and take care of the viewType parameter in onCreateViewHolder().
For example you can use below model:
public class Data{
int field1;
float filed2;
int rowType // 1,2,2,...N this will fill by you whenever you will
//creating arraylist for your recyclerview
}
public class Custome Adapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
ArrayList<Data> mItems;
class ViewHolderRowType1 extends RecyclerView.ViewHolder {
...
}
class ViewHolderRowType2 extends RecyclerView.ViewHolder {
...
}
....
class ViewHolderRowTypeN extends RecyclerView.ViewHolder {
...
}
#Override
public int getItemViewType(int position) {
return mItems.get(position).rowType;
//or
//return positon%2; // This will based on your condition
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 0: return new ViewHolderRowType0(...);
case 1: return new ViewHolderRowType1(...);
...
case N: return new ViewHolderRowTypeN(...);
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder vh, int position) {
//Just check which view type is going to bind and then fill the data accordingly in your rows
if(vh instanceof ViewHolderRowType1){
// Fill the data for first view type
} else if (vh instanceof ViewHolderRowType2) {
// Fill the data for second view type
} else if (vh instanceof ViewHolderRowTypeN){
// Fill the data for Nth view type
}
}
For your sticky "this view weak", you can add it at top of your RecyclerView and then handle it by scroll Event of RecyclerView
There are two ways to implement such RecyclerView
Add header in every layout and hide/show based on your requirement(preferable).
Or use two different layouts for header and content(not preferable because it can cause problem in total count of items in adapter).
In your custom POJO / GetterSetter class, add one field for headerStatus(either boolean or int), to identify whether to show header or not.
Now in adapter override public int getItemViewType(int position).
static final int TYPE_ITEM = 0;
static final int TYPE_SEPARATOR = 1;
#Override
public int getItemViewType(int position) {
if (mData.get(position).getHeaderStatus() == 0)
return TYPE_ITEM;
else
return TYPE_SEPARATOR;
}
Now at the time of inflating the layout in getView() you can check the row type by
int rowType = getItemViewType(position);
For case 1, you need to visible the header and set appropriate data in it.
For case 2, you need to inflate that header layout and add appropriate data in it.
If you want to do it in "proper" way, without hacks, you should write your own LayoutManager, and handle those cases by hands. It is not as hard as it sounds, but will take some efforts.
I am creating a list which contains, different types of views. Like facebook does in showing their feeds in Mobile Application.
Example Some times scrollview or some times list inside list.
To do this what will be a good choice.
What if i add a fragment in each item of recyclerview. Like
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<fragment
android:layout_width="match_parent"
android:name="com.profile.EditProfileFragment"
android:layout_height="wrap_content"/>
and add this as a item in Reclycleview and keep different logic inside it.
Can any one suggest me how to go through this.
I don't think it's going to be possible using Fragment as a row. Fragment has its own lifecycle which I don't lies within what RecyclerView can control.
But you can do it with simple view.
Just override several methods in RecyclerView.Adapter
private ArrayList<Data> items = new ArrayList<>(); // data associated
// with each row
#Override
public int getItemViewType(int position) {
return items.get(position).getRowType();
// assuming this is the getter to get the type
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case Data.TYPE_A:
View convertView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.row_feed, parent, false);
return new ViewHolderTypeA(convertView);
case Data.TYPE_B:
...
default:
return null;
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Data data = items.get(position);
if (holder instanceof ViewHolderTypeA) {
ViewHolderTypeA holderA= (ViewHolderTypeA) holder;
// manipulate the views in view holder for this type
...
}else if (holder instanceof ViewHolderTypeB){
}
}
Edit: You don't need different Adapter for each item. You just need item that includes all your logic. So if you want to have two row types, one for status, one for photo like Facebook feed then your item will be:
class Data {
String status, photoUrl;
}
Of course some members will be empty depending on the row.