I have created recyclerView and it's adapter. In adapter depend on my class that i pass to it, row item generate automatically .
This is recycler view item `add_row_item:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:orientation="horizontal">
</LinearLayout>
And this is a main layout :
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:background="#color/md_indigo_50">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical">
</android.support.v7.widget.RecyclerView>
<Button
android:id="#+id/save_to_grid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/recycler_view_item"
android:layout_centerHorizontal="true"
android:layout_margin="10dp"
android:background="#drawable/bg_round"
android:padding="5dp"
android:text="ذخیره"
android:textColor="#424242"
android:visibility="visible" />
</RelativeLayout>
</layout>
This is Adapter:
public class RevisitGridAdapter extends RecyclerView.Adapter<RevisitGridAdapter.GridHolder> {
private List<DataGridColumn> column;
public RevisitGridAdapter(List<DataGridColumn> column) {
this.column = column;
}
public int dip2pix(#NonNull Context context, int dip) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
context.getResources().getDisplayMetrics());
}
#NonNull
#Override
public GridHolder onCreateViewHolder(#NonNull ViewGroup parent, int i) {
Context context = parent.getContext();
View root = LayoutInflater.from(context).inflate(R.layout.add_row_item, parent, false);
return new GridHolder(root);
}
#Override
public void onBindViewHolder(#NonNull GridHolder holder, int position) {
holder.setPosition(holder, position);
}
#Override
public int getItemCount() {
return column.size();
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
class GridHolder extends RecyclerView.ViewHolder {
private TextView tv;
private LinearLayout parent;
public GridHolder(#NonNull View itemView) {
super(itemView);
parent = (LinearLayout) itemView;
}
public void setPosition(GridHolder holder, int position) {
if (!column.get(position).getName().startsWith("CI")) { // todo add EUM
EditText edt = createEditText();
holder.parent.addView(edt);
tv = createTextView();
tv.setText(column.get(position).getHeader());
holder.parent.addView(tv);
} else {
holder.parent.addView(createSpinner());
tv = createTextView();
tv.setText(column.get(position).getHeader());
holder.parent.addView(tv);
}
}
private Spinner createSpinner() {
final Spinner sp = new Spinner(itemView.getContext());
int padding = (int) itemView.getContext().getResources().getDimension(R.dimen.elevation_header);
final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(0,
LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f);
// layoutParams.setMargins(padding, padding, padding, padding);
sp.setPadding(padding, padding, padding, padding);
sp.setLayoutParams(layoutParams);
return sp;
}
private TextView createTextView() {
final LinearLayout.LayoutParams lparams = new LinearLayout.LayoutParams(dip2pix(itemView.getContext(), 120), LinearLayout.LayoutParams.WRAP_CONTENT);
final TextView textView = new TextView(itemView.getContext());
textView.setLayoutParams(lparams);
return textView;
}
private EditText createEditText() {
final LinearLayout.LayoutParams lparams = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f);
final EditText editText = new EditText(itemView.getContext());
editText.setLayoutParams(lparams);
ViewCompat.setBackground(editText,
ContextCompat.getDrawable(itemView.getContext(), com.safarayaneh.map.R.drawable.bg_round));
editText.getBackground().setColorFilter(ContextCompat.getColor(itemView.getContext(),
com.safarayaneh.map.R.color.md_indigo_100), PorterDuff.Mode.SRC_ATOP);
return editText;
}
}
}
When i run applicatin and when data is enough to view in one single page every think is ok but data is more than is shown in one page means when i scroll the page the problem is appeared!!
The problem is when for first time i scroll to end of the recyclerview everything is ok but when i scroll to first page of the recyclerview the view's and data repeatedly repeat and recycler view is completely mess up like this image:
As you can see in my picture data and view's on top of the image repeated and exit of 2 columns shape.
What is my mistake?
You are using onCreateViewHolder() and onBindViewHolder() wrongly.
onCreateViewHolder should be used to create list items, onBindViewHolder should only be used to fill already created views with data. The reason is, that the views that are created by onCreateViewHolder are repeatedly used for showing different items' data (hence the name RecyclerView).
If you need to create different types of views for different types of data, you need to implement getItemViewType().
Then the flow is this:
At first, your getItemViewType() method is called and depending on given position you return the needed type.
If there is already a view of that type, which can be reused, your onBindViewHolder() method is called directly with that view and the position.
Otherwise, your onCreateViewHolder() method is called and creates a new instance of the view needed for that type. Afterwards, again onBindViewHolder() is called with the created view and the position.
So, to repeat myself - onCreateViewHolder() is there to create views, onBindViewHolder() is there to bind data (i.e. set text etc.) to an already created view.
Ridcully is right and i could created 2 views and changed it by implementing getItemViewType() method. However in my scenario i changed onBindViewHolder method to this:
#Override
public void onBindViewHolder(#NonNull GridHolder holder, int position) {
holder.parent.removeAllViews();
DataGridColumn column = this.column.get(position);
holder.setPosition(holder, position);
}
everything now is good. holder.parent.removeAllViews(); remove view's that created before.
Related
I am trying to achieve a chatting app type layout in RecylcerView. Now the only problem is that even though I have set width of View to wrap around text. Inside RecyclerView sometime small words take whole line and sometime the word is fine.
Green highlight is expected behavior and red is unexpected behavior.
This is the code of cardView
<androidx.cardview.widget.CardView
android:id="#+id/cardView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
app:cardBackgroundColor="#00BCD4"
app:cardCornerRadius="12dp"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.123">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/textMessageSent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxWidth="260dp"
android:paddingBottom="8dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingTop="8dp"
android:text="oh noo"
android:textColor="#000000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:maxLines="10" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
Here is Adapter code
public class ChatAdapter extends ListAdapter {
String reciever,sender;
public ChatAdapter(String from,String to)
{
super(DIFF_CALLBACK);
this.reciever = from;
this.sender = to;
}
private static final DiffUtil.ItemCallback<Chat> DIFF_CALLBACK = new DiffUtil.ItemCallback<Chat>() {
#Override
public boolean areItemsTheSame(#NonNull Chat oldItem, #NonNull Chat newItem) {
return oldItem.getId() == newItem.getId();
}
#Override
public boolean areContentsTheSame(#NonNull Chat oldItem, #NonNull Chat newItem) {
return oldItem.getSender().equals(newItem.getSender()) &&
oldItem.getReciever().equals(newItem.getReciever()) &&
oldItem.getDate().equals(newItem.getDate()) &&
oldItem.getMessageIdSent() == newItem.getMessageIdSent() &&
oldItem.getMessageRecieved().equals(newItem.getMessageRecieved()) &&
oldItem.getTime().equals(newItem.getTime());
}
};
private final int TEXT_MESSAGE_SENT = 0;
private final int TEXT_MESSAGE_RECIEVED = 1;
#Override
public int getItemViewType(int position) {
Chat currentChats = (Chat) getItem(position);
if(!currentChats.getMessageRecieved().equals(""))
{
return TEXT_MESSAGE_RECIEVED;
}
else if(!currentChats.getMessageSent().equals(""))
{
return TEXT_MESSAGE_SENT;
}
return -1;
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view;
if(viewType == TEXT_MESSAGE_SENT)
{
view = layoutInflater.inflate(R.layout.me_text_message,parent,false);
return new TextChatSenderViewHolder(view);
}
//if reciever is text
view = layoutInflater.inflate(R.layout.other_text_message,parent,false);
return new TextChatRecieverViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
Chat currentChat = (Chat) getItem(position);
switch (holder.getItemViewType())
{
case TEXT_MESSAGE_RECIEVED:
//bind REcievedText viewholder
TextChatRecieverViewHolder viewHolder = (TextChatRecieverViewHolder) holder;
if(!currentChat.getMessageRecieved().equals(""))
{
viewHolder.textMessageRecieved.setText(currentChat.getMessageRecieved());
viewHolder.textTimeMessageRecieved.setText(currentChat.getTime());
}
break;
case TEXT_MESSAGE_SENT:
TextChatSenderViewHolder viewHolder2 = (TextChatSenderViewHolder) holder;
if(!currentChat.getMessageSent().equals("")) {
viewHolder2.textMessageSent.setText(currentChat.getMessageSent());
viewHolder2.textTimeMessageSent.setText(currentChat.getTime());
}
break;
}
}
class TextChatSenderViewHolder extends RecyclerView.ViewHolder
{
TextView textMessageSent,textTimeMessageSent;
public TextChatSenderViewHolder(#NonNull View itemView) {
super(itemView);
textTimeMessageSent = itemView.findViewById(R.id.textTimeMessageSent);
textMessageSent = itemView.findViewById(R.id.textMessageSent);
}
}
class TextChatRecieverViewHolder extends RecyclerView.ViewHolder
{
TextView textMessageRecieved,textTimeMessageRecieved;
public TextChatRecieverViewHolder(#NonNull View itemView) {
super(itemView);
textTimeMessageRecieved = itemView.findViewById(R.id.textTimeMessageRecieved);
textMessageRecieved = itemView.findViewById(R.id.textMessageRecieved);
}
}
}
NOTE: I have noticed that layout changes when the message goes out of screen and you scroll back up.
You need to change your onCreateViewHolder, keep below lines in else
view = layoutInflater.inflate(R.layout.other_text_message,parent,false);
return new TextChatRecieverViewHolder(view);
Also keep layout width to
wrap_content
rather than
match_parent.
you've messed up recycling pattern (link for ListView but it describes pattern better than official doc imho) by overriding getItemViewType method and letting this method return -1. thats not any of view types, but onCreateViewHolder MUST return ViewHolder, so you are returning R.layout.other_text_message. if any message would have 0-length then this empty layout will show up on list
another problem related to wrong recycling implementation is that you are not always calling setText inside onBindViewHolder, because of inconsistent view types and if check (probably unnecessary, as itemType is then -1). when you understand how recycling works (above linked) you will notice that when you won't ALWAYS call setText in onBindViewHolder then recycled item may contain text from previous iteration (before scroll) and this duplicated text will show at this position (wrongly recycler view)
now your list item view have android:layout_width="match_parent" which may "remember" length before recycling, showing set after recycling, something is messing up in here. if you want text wrapping then TextView should have set android:layout_width="wrap_content". keeping android:layout_width="match_parent" (ContraintLayout) inside android:layout_width="wrap_content" parent (CardView) is very unefficient and may cause multiple redrawings/remeasurements, parent probably should also have android:layout_width="match_parent"
my advise to you is to getting rid of empty messages BEFORE setting them to adapter - drawing mechanism isn't a place for filtering inproper items (empty messages in your case) not intended to be drawn. return always some known view type and always fully handle it (onCreate... and onBind... methods), use at least else setText(""); for clearing text after previous item/text set for this view
I'm trying to implement an EmptyView on my RecyclerView Adapter but I'm not getting any result.
I've followed this tutorial and this tip, but noone worked for me.
I've implemented:
if (viewType == EMPTY_VIEW) {
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.empty_view, parent, false);
EmptyViewHolder evh = new EmptyViewHolder(v);
return evh;
}
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.data_row, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
But it doesn't let me compile because they are differents ViewHolder, because I've created two ViewHolder classes but they extends Recycler.ViewHolder so I don't get it...
I'm trying to do this because I've got a SearchView and I want when the list is empty it shows an EmptyView, I've got it doing it programmatically but I prefer to add like a layout because I don't know that much how to put TextViews and Buttons programmatically.
Also if I put
return dataList.size() > 0 ? dataList.size() : 1;
It gives to me error because index is 0.
I've debugged the viewType and always is 1, then it won't join the if condition...
Deep on Android I found this :
/**
* Return the view type of the item at <code>position</code> for the purposes
* of view recycling.
*
* <p>The default implementation of this method returns 0, making the assumption of
* a single view type for the adapter. Unlike ListView adapters, types need not
* be contiguous. Consider using id resources to uniquely identify item view types.
*
* #param position position to query
* #return integer value identifying the type of the view needed to represent the item at
* <code>position</code>. Type codes need not be contiguous.
*/
public int getItemViewType(int position) {
return 0;
}
But the thing is that no changes the value.
EDIT
I almost done it, I did this :
#Override
public int getItemViewType(int position) {
return list.size() > 0 ? list.size() : 1;
}
But sometimes it returns 0 when the size() is 0... I don't get it, I'm using this SearchView, and sometimes when I type a letter that doesn't matches with any item of the list it doesn't show and sometimes it does...
Also other thing that happens is that when the layout popups it shows on the left of the screen when I put that is on center, but I think it's problem with RecyclerView because the layout puts inside of it.
RecyclerView layout :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:id="#+id/rtpew"
android:layout_centerInParent="true"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
>
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/linearpew">
<android.support.v7.widget.RecyclerView
android:id="#+id/rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
</RelativeLayout>
And this is my emptylayout :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/ImageViewSearchFail"
android:src="#drawable/sadface"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_gravity="center"
android:textSize="#dimen/15dp"
android:layout_marginTop="4dp"
android:text="foo"
android:layout_below="#+id/ImageViewSearchFail"
android:layout_centerHorizontal="true" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/ButtonAddEntity"
android:text="foo"
android:background="?android:selectableItemBackground"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
The other way that I thought is to implement it programmatically as follow :
#Override
public boolean onQueryTextChange(String query) {
final ArrayList<List> filteredModelList = filter(mModel, query);
mAdapter.animateTo(filteredModelList);
rv.scrollToPosition(0);
if(query.isEmpty()){
//Here
}
return true;
}
And :
private ArrayList<List> filter(ArrayList<List> models, String query) {
query = query.toLowerCase();
final ArrayList<List> filteredModelList = new ArrayList<List>();
for (List model : models) {
final String text = model.getRedName().toLowerCase();
if (text.contains(query)) {
filteredModelList.add(model);
}
}
if (filteredModelList.size()<0) {
//HERE
}
else{
//Delete the views added
}
return filteredModelList;
}
PROBLEMS
-I only add the view using the #Jimeux answer but I'd like to do this on the Adapter, I got it, but not always shows the view even if the list is empty.
-At the time to put the emptyview.xml it puts inside of the RecyclerView then since I've put all of this xml at the center it shows on the right. I've tried to add the xml programmatically but it's like a chaos....
Since you need to handle two different kind of views, it would be easier to use an intermediate list of business object for more easily binding them with views. Idea is to have a kind of placeholder in your list for representing empty state. Defining an intermediate layer is extremely useful in this sense for allowing you to consider eventual changes to be applied to your list in future (e.g. adding you element types). Moreover in this way you can more clearly separate your business model from ui representation (for example you can implement methods returning ui settings based on internal status of model objects).
You can proceed as follows:
Define a dedicated abstract type for List items (e.g. ListItem) to wrap your business objects. Its implementation could be something like this:
public abstract class ListItem {
public static final int TYPE_EMPTY = 0;
public static final int TYPE_MY_OBJ = 1;
abstract public int getType();
}
Define a class for each of your List element type:
public class EmptyItem extends ListItem {
#Override
public int getType() {
return TYPE_EMPTY;
}
}
public class MyObjItem extends ListItem {
private MyObj obj;
public ContactItem(MyObj obj) {
this.obj = obj;
}
public MyObj getMyObj() {
return obj;
}
// here you can also add methods for simplify
// objects rendering (e.g. get background color
// based on your object internal status)
#Override
public int getType() {
return TYPE_MY_OBJ;
}
}
Create your list.
List<ListItem> mItems = new ArrayList<>();
if (dataList != null && dataList.size() > 0) {
for (MyObj obj : dataList) {
mItems.add(new MyObjItem(obj));
}
} else {
mItems.add(new EmptyItem());
}
This is the most important part of code. You have many options for creating this list. You can do it inside your RecyclerView Adapter or outside, but it's extremely important to properly handle eventual modifications to it. This is essential for exploiting Adapter notify methods. For example, if you create list within the Adapter, it should probably provide also methods for adding or removing your model items. For example:
public void addObj(MyObj obj) {
if (mItems.size() == 1 && mItems.get(0).getType() == ListItem.EMPTY_TYPE) {
mItems.clear();
}
mItems.add(new MyObjItem(obj));
notifyDataSetChanged();
}
Define an adapter for your RecyclerView, working on List defined at point 3. Here what is important is to override getItemViewType method as follows:
#Override
public int getItemViewType(int position) {
return mItems.get(position).getType();
}
Moreover, type of ViewHolder should be RecyclerView.ViewHolder (unless you decide to create an intermediate class even in this case).
Then you need to have two layouts and ViewHolder for empty and business obj items. Adapter methods should take care of this accordingly:
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ListItem.TYPE_EMPTY) {
View itemView = mLayoutInflater.inflate(R.layout.empty_layout, parent, false);
return new EmptyViewHolder(itemView);
} else {
View itemView = mLayoutInflater.inflate(R.layout.myobj_layout, parent, false);
return new MyObjViewHolder(itemView);
}
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int position) {
int type = getItemViewType(position);
if (type == ListItem.TYPE_EMPTY) {
EmptyItem header = (EmptyItem) mItems.get(position);
EmptyViewHolder holder = (EmptyViewHolder) viewHolder;
// your logic here... probably nothing to do since it's empty
} else {
MyObjItem event = (MyObjItem) mItems.get(position);
MyObjViewHolder holder = (MyObjViewHolder) viewHolder;
// your logic here
}
}
Of course, as I wrote at the beginning you don't need to strictly define intermediate types for ui representation (EmptyItem and MyObjItem). You can even just use MyObj type and create a specific configuration for it that represent an empty placeholder. This approach is probably not the best in case in future you need to make your logic more complex by including for example new list item types.
Follow the below steps one by one
1). Since you have two types of views for your RecyclerView item, your adapter declaration should look like this a generic one
public class YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
and your ViewHolders for both listview item and empty view should extend RecyclerView.ViewHolder like this
static class ListItemViewHolder extends RecyclerView.ViewHolder {
public ListItemViewHolder(View itemView) {
super(itemView);
// initialize your views here for list items
}
}
static class EmptyViewViewHolder extends RecyclerView.ViewHolder {
public EmptyViewViewHolder(View itemView) {
super(itemView);
// initialize your views here for empty list
}
}
2). You have to Override getItemCount() and getItemViewType()
#Override
public int getItemCount() {
return yourList.size() > 0 ? yourList.size() : 1;// if size of your list is greater than 0, you will return your size of list otherwise 1 for the empty view.
}
#Override
public int getItemViewType(int position) {
if (yourList.size() == 0) {
return VIEW_TYPE_EMPTY;
}
return position;
}
3). Your onCreateViewHolder() will look alike this now
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_EMPTY) {
return new EmptyViewViewHolder(mLayoutInflater
.inflate(R.layout.empty_view_layout, parent, false));
} else {
return new ListItemViewHolder(mLayoutInflater
.inflate(R.layout.row_list_item, parent, false));
}
}
4). Same check you have to apply in your onBindViewHolder() as well
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (getItemViewType(position) == VIEW_TYPE_EMPTY) {
EmptyViewViewHolder emptyViewViewHolder = (EmptyViewViewHolder) holder;
// set values for your empty views
} else {
ListItemViewHolder listItemViewHolder = (ListItemViewHolder) holder;
// set values for your list items
}
}
5). At last Override your SearcView.setOnQueryTextListener()
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
return false;
}
#Override
public boolean onQueryTextChange(String newText) {
currentSearchKeyword = newText.trim();
if(currentSearchKeyword.iseEmpty()){
yourList.clear();
yourAdapter.notifyDataSetChanged();
}else{
// there will two cases again 1). If your currentSearchKeyword matchces with list results, add that items to your list and notify your adapter. 2) If the currentSearchKeyword doesn't matched with list results, clear your list and notify your adapter;
}
return false;
}
});
Hope it will help you, let me know if any issues.
The compilation error probably results because of you extending RecyclerView.Adapter with your main ViewHolder as the generic argument.
You should make it like
YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
And then cast your ViewHolders appropriately (you can reuse getViewType(position) here). Be sure to switch the ViewHolder type in your methods as well.
If I were you, I wouldn't put the empty view in the adapter at all. Put it under your linearpew layout that's holding the RecyclerView and hide/show it as your data changes. You can easily add a loading view, error view, etc. with this setup too.
Here's a bit of simplified code from one of my apps to give you some ideas. #Bind comes from Butter Knife if you're not familiar with it. You may also want to check out Jake Wharton's u2020 project for more RecyclerView ideas.
//fragment_layout.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="#+id/content">
</FrameLayout>
<include layout="#layout/status_views" />
</RelativeLayout>
//status_views.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<LinearLayout style="#style/ListStatusView"
android:id="#+id/empty_view"/>
<LinearLayout style="#style/ListStatusView"
android:id="#+id/error_view"/>
<LinearLayout style="#style/ListStatusView"
android:id="#+id/loading_view"
android:padding="30dp"/>
</LinearLayout>
//MyFragment.java
#Bind(R.id.content) protected ViewGroup contentView;
#Bind(R.id.loading_view) protected ViewGroup loadingView;
#Bind(R.id.empty_view) protected ViewGroup emptyView;
#Bind(R.id.error_view) protected ViewGroup errorView;
#Bind({R.id.loading_view, R.id.error_view, R.id.empty_view, R.id.content})
protected List<ViewGroup> stateViews;
protected void activateView(View view) {
for (ViewGroup vg : stateViews)
vg.setVisibility(View.GONE);
view.setVisibility(VISIBLE);
}
#Override
public void onActivityCreated(#Nullable Bundle state) {
super.onActivityCreated(state);
if (state == null) {
activateView(loadingView);
loadData();
} else if (data.isEmpty())
activateView(emptyView);
else
activateView(contentView);
}
Edit: Here's a simplified version without Butter Knife.
private ViewGroup contentView;
private ViewGroup emptyView;
#Override
protected void onCreate(#Nullable Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_main);
contentView = (ViewGroup) findViewById(R.id.content_view);
emptyView = (ViewGroup) findViewById(R.id.empty_view);
}
#Override
public boolean onQueryTextChange(String query) {
final ArrayList<List> filteredModelList = filter(mModel, query);
mAdapter.animateTo(filteredModelList);
rv.scrollToPosition(0);
if(query.isEmpty()){
contentView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
} else {
contentView.setVisibility(View.VISIBLE);
emptyView.setVisibility(View.GONE);
}
return true;
}
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rtpew"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true">
<LinearLayout
android:id="#+id/content_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView
android:id="#+id/rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<RelativeLayout android:id="#+id/empty_view">
<ImageView android:src="#drawable/sadface"/>
<TextView android:text="foo"/>
<Button android:id="#+id/ButtonAddEntity"/>
</RelativeLayout>
</RelativeLayout>
Here is what you can try:
1. Replace
EmptyViewHolder evh = new EmptyViewHolder(v);
with
RecyclerView.ViewHolder evh = new EmptyViewHolder(v);
This is probably why the compilation fails.
2. Replace
#Override
public int getItemViewType(int position) {
return list.size() > 0 ? list.size() : 1;
}
with
#Override
public int getItemViewType(int position) {
return list.get(position) != null ? 1 : 0;
}
For this to work, you must insert a null object whenever you want to show an EmptyView:
int progressPosition = list.size();
list.add(null);
adapter.notifyItemInserted(progressPosition);
and remove the null object when you want to hide the EmptyView:
int progressPosition = existingList.size() - 1;
existingList.remove(progressPosition);
adapter.notifyItemRemoved(progressPosition);
Also, you must modify your onCreateViewHolder() method as follows:
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == 1) {
// inflate your default ViewHolder here ...
} else {
// inflate the EmptyViewHolder here
}
}
I believe we have discussed this before ... see this question for a detailed discussion on this.
3. Instead of using a SearchView, consider using an AutoCompleteTextView with a Filter. This may be easier to integrate with your RecyclerView's Adapter. See this answer for an example of this.
I will update this answer as I understand your question better ... do try this and update me.
I have the following scenario: I have a LinearLayout on which I then add "cards" to which is a custom class which extends LinearLayout.
The problem is that each card contains an image. Now if I have too many cards to display I get an out of memory error because of the size of the images.
How can I dynamically see which cards are currently displayed on the screen and only load the images of those cards and keep the rest null?
I am struggling to detect which card is currently displayed on the screen and which ones are not. And then also to have an event load and clear images as the user scrolls though the list.
You have to implement a RecyclerView, which does the job for you.
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
final Adapter adapter = new Adapter();
recyclerView.setAdapter(adapter);
The adapter:
private class Adapter extends RecyclerView.Adapter<MyViewHolder> {
#Override
public MyViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.card_main, viewGroup, false);
return new MyViewHolder(view);
}
#Override
public void onBindViewHolder(final MyViewHolder myViewHolder, int i)
// set the content of the card
}
#Override
public int getItemCount() {
return // number of cards
}
}
The ViewHolder
private class MyViewHolder extends RecyclerView.ViewHolder {
public TextView text;
public TextView text2;
public ImageView imageView;
public MyViewHolder(View itemView) {
super(itemView);
text = (TextView) itemView.findViewById(/* your textView */);
text2 = (TextView) itemView.findViewById(/* another textView */);
imageView = (ImageView) itemView.findViewById(/* an image */);
}
}
The Layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:design="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivityFragment">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/recycler_view"
/>
</RelativeLayout>
For something like this, you should probably use a Recycler View. This way you can recycle the views and ideally not run into memory issues and not have to have hacky solutions that check what is on the screen and what isn't.
I know this problem has being asked numerous times relating to ListViews, However I have never been able to understand the solution truly. I am aware that the position parameter of onBindViewHolder() in RecyclerViewAdapter is similar to the position parameter in listAdapter getView(). I also am aware that the position is only relative to visible views. So how can I go about loading an Image for each row of the RecyclerView? The image may be different depending on position. The RecyclerView has a known number of rows (57)
I intend to have an array list of 'favourites'for example int[] fav = {2,6,30,40}. So if position equals one of these elements then ImageView sets one drawble, else it sets another.Could anyone help me with this?
At the moment position repeats itself and many extra rows are setting the drawable reserved for row IDs in the fav array
Thank You,
public class RecyclerViewAdapter extends ...... {
private List<StockItemRow> stockItems;
private Context mContext;
private int[] favourites = {2,3};
RecyclerViewAdapter(Context context, List modelData) {
if (modelData == null) {
throw new IllegalArgumentException("modelData must not be null");
}
this.stockItems = modelData;
this.mContext = context;
}
#Override
public ListItemViewHolder onCreateViewHolder(...)
{
View itemView =
LayoutInflater.fromLayoutInflater.
from(viewGroup.getContext()).
inflate(R.layout.listitem_row, viewGroup, false);
return new ListItemViewHolder(itemView);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public void onBindViewHolder(ListItemViewHolder viewHolder, int position) {
StockItemRow stockModel = stockItems.get(position);
for(int i = 0;i<favourites.length;i++) {
if (position == favourites[i]) {
viewHolder.star.setImageResource(R.drawable.star_pressed);
}
}
#Override
public int getItemCount() {
return stockItems.size();
}
public final static class ListItemViewHolder extends....
ImageView star;
public ListItemViewHolder(final View itemView) {
super(itemView);
star= (ImageView) itemView.findViewById(R.id.star);
}
}
}
XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<android.support.v7.widget.RecyclerView
android:layout_height="match_parent"
android:id="#+id/recyclerView"
android:divider="#drawable/list_selector"
android:dividerHeight="1dip"
android:layout_width="match_parent">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
#Deev is correct: the position is the position within the adapter, not on screen. The problem you are running into is because your onBindViewHolder() method is only handling the favorite case, but not the non-favorite case. The ViewHolder pattern is used for efficiency and it's re-using views. Change the code to be this:
int fav_res = R.drawable.star;
for(int i = 0;i<favourites.length;i++) {
if (position == favourites[i]) {
fav_res = R.drawable.star_presed;
break;
}
}
viewHolder.star.setImageResource(fav_res);
I am trying to determine the best way to have a single ListView that contains different layouts for each row. I know how to create a custom row + custom array adapter to support a custom row for the entire list view, but how can I implement many different row styles in the ListView?
Since you know how many types of layout you would have - it's possible to use those methods.
getViewTypeCount() - this methods returns information how many types of rows do you have in your list
getItemViewType(int position) - returns information which layout type you should use based on position
Then you inflate layout only if it's null and determine type using getItemViewType.
Look at this tutorial for further information.
To achieve some optimizations in structure that you've described in comment I would suggest:
Storing views in object called ViewHolder. It would increase speed because you won't have to call findViewById() every time in getView method. See List14 in API demos.
Create one generic layout that will conform all combinations of properties and hide some elements if current position doesn't have it.
I hope that will help you. If you could provide some XML stub with your data structure and information how exactly you want to map it into row, I would be able to give you more precise advise. By pixel.
I know how to create a custom row + custom array adapter to support a custom row for the entire list view. But how can one listview support many different row styles?
You already know the basics. You just need to get your custom adapter to return a different layout/view based on the row/cursor information being provided.
A ListView can support multiple row styles because it derives from AdapterView:
An AdapterView is a view whose children are determined by an Adapter.
If you look at the Adapter, you'll see methods that account for using row-specific views:
abstract int getViewTypeCount()
// Returns the number of types of Views that will be created ...
abstract int getItemViewType(int position)
// Get the type of View that will be created ...
abstract View getView(int position, View convertView, ViewGroup parent)
// Get a View that displays the data ...
The latter two methods provide the position so you can use that to determine the type of view you should use for that row.
Of course, you generally don't use AdapterView and Adapter directly, but rather use or derive from one of their subclasses. The subclasses of Adapter may add additional functionality that change how to get custom layouts for different rows. Since the view used for a given row is driven by the adapter, the trick is to get the adapter to return the desired view for a given row. How to do this differs depending on the specific adapter.
For example, to use ArrayAdapter,
override getView() to inflate, populate, and return the desired view for the given position. The getView() method includes an opportunity reuse views via the convertView parameter.
But to use derivatives of CursorAdapter,
override newView() to inflate, populate, and return the desired view for the current cursor state (i.e. the current "row") [you also need to override bindView so that widget can reuse views]
However, to use SimpleCursorAdapter,
define a SimpleCursorAdapter.ViewBinder with a setViewValue() method to inflate, populate, and return the desired view for a given row (current cursor state) and data "column". The method can define just the "special" views and defer to SimpleCursorAdapter's standard behavior for the "normal" bindings.
Look up the specific examples/tutorials for the kind of adapter you end up using.
Take a look in the code below.
First, we create custom layouts. In this case, four types.
even.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#ff500000"
android:layout_height="match_parent">
<TextView
android:id="#+id/text"
android:textColor="#android:color/white"
android:layout_width="match_parent"
android:layout_gravity="center"
android:textSize="24sp"
android:layout_height="wrap_content" />
</LinearLayout>
odd.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#ff001f50"
android:gravity="right"
android:layout_height="match_parent">
<TextView
android:id="#+id/text"
android:textColor="#android:color/white"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:textSize="28sp"
android:layout_height="wrap_content" />
</LinearLayout>
white.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#ffffffff"
android:gravity="right"
android:layout_height="match_parent">
<TextView
android:id="#+id/text"
android:textColor="#android:color/black"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:textSize="28sp"
android:layout_height="wrap_content" />
</LinearLayout>
black.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#ff000000"
android:layout_height="match_parent">
<TextView
android:id="#+id/text"
android:textColor="#android:color/white"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:textSize="33sp"
android:layout_height="wrap_content" />
</LinearLayout>
Then, we create the listview item. In our case, with a string and a type.
public class ListViewItem {
private String text;
private int type;
public ListViewItem(String text, int type) {
this.text = text;
this.type = type;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
After that, we create a view holder. It's strongly recommended because Android OS keeps the layout reference to reuse your item when it disappears and appears back on the screen. If you don't use this approach, every single time that your item appears on the screen Android OS will create a new one and causing your app to leak memory.
public class ViewHolder {
TextView text;
public ViewHolder(TextView text) {
this.text = text;
}
public TextView getText() {
return text;
}
public void setText(TextView text) {
this.text = text;
}
}
Finally, we create our custom adapter overriding getViewTypeCount() and getItemViewType(int position).
public class CustomAdapter extends ArrayAdapter {
public static final int TYPE_ODD = 0;
public static final int TYPE_EVEN = 1;
public static final int TYPE_WHITE = 2;
public static final int TYPE_BLACK = 3;
private ListViewItem[] objects;
#Override
public int getViewTypeCount() {
return 4;
}
#Override
public int getItemViewType(int position) {
return objects[position].getType();
}
public CustomAdapter(Context context, int resource, ListViewItem[] objects) {
super(context, resource, objects);
this.objects = objects;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
ListViewItem listViewItem = objects[position];
int listViewItemType = getItemViewType(position);
if (convertView == null) {
if (listViewItemType == TYPE_EVEN) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_even, null);
} else if (listViewItemType == TYPE_ODD) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_odd, null);
} else if (listViewItemType == TYPE_WHITE) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_white, null);
} else {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_black, null);
}
TextView textView = (TextView) convertView.findViewById(R.id.text);
viewHolder = new ViewHolder(textView);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.getText().setText(listViewItem.getText());
return convertView;
}
}
And our activity is something like this:
private ListView listView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // here, you can create a single layout with a listview
listView = (ListView) findViewById(R.id.listview);
final ListViewItem[] items = new ListViewItem[40];
for (int i = 0; i < items.length; i++) {
if (i == 4) {
items[i] = new ListViewItem("White " + i, CustomAdapter.TYPE_WHITE);
} else if (i == 9) {
items[i] = new ListViewItem("Black " + i, CustomAdapter.TYPE_BLACK);
} else if (i % 2 == 0) {
items[i] = new ListViewItem("EVEN " + i, CustomAdapter.TYPE_EVEN);
} else {
items[i] = new ListViewItem("ODD " + i, CustomAdapter.TYPE_ODD);
}
}
CustomAdapter customAdapter = new CustomAdapter(this, R.id.text, items);
listView.setAdapter(customAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView adapterView, View view, int i, long l) {
Toast.makeText(getBaseContext(), items[i].getText(), Toast.LENGTH_SHORT).show();
}
});
}
}
now create a listview inside mainactivity.xml
like this
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.example.shivnandan.gygy.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="#layout/content_main" />
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/listView"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="100dp" />
</android.support.design.widget.CoordinatorLayout>
In your custom array adapter, you override the getView() method, as you presumably familiar with. Then all you have to do is use a switch statement or an if statement to return a certain custom View depending on the position argument passed to the getView method. Android is clever in that it will only give you a convertView of the appropriate type for your position/row; you do not need to check it is of the correct type. You can help Android with this by overriding the getItemViewType() and getViewTypeCount() methods appropriately.
If we need to show different type of view in list-view then its good to use getViewTypeCount() and getItemViewType() in adapter instead of toggling a view VIEW.GONE and VIEW.VISIBLE can be very expensive task inside getView() which will affect the list scroll.
Please check this one for use of getViewTypeCount() and getItemViewType() in Adapter.
Link : the-use-of-getviewtypecount
ListView was intended for simple use cases like the same static view for all row items.
Since you have to create ViewHolders and make significant use of getItemViewType(), and dynamically show different row item layout xml's, you should try doing that using the RecyclerView, which is available in Android API 22. It offers better support and structure for multiple view types.
Check out this tutorial on how to use the RecyclerView to do what you are looking for.