RecyclerView Showing Random Data - android

I am having a RecyclerView with a Custom Adapter which extends RecyclerView.Adapter
I am Creating LinearLayout with Textview at Runtime and inflating it in each row of RecyclerView
For Example, 1st Row of RecyclerView will have 2 or 3 Textview created at runtime, 2nd Row will have 2 or 3 Textviews created at runtime, 3rd Row will have some Textviews...and so on...
Its working almost Prefect if I check my Log... But when I scroll it down, it just places some textview in wrong places, means I get previous Textviews again when I scroll down in wrong places
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
//Movie movie = mItems.get(i);
hm2 = new HashMap<String, ArrayList<PD_Data>>();
sub_rows2 = new ArrayList<PD_Data>();
hm2=categories.get(i);
String key=hm2.keySet().toArray()[0].toString();
sub_rows2=hm2.get(key);
Log.i(LOG_TKT,key);
viewHolder.textview_category.setText(key);
LayoutInflater inflater;
View new_sub_row;
for(int x=0;x<sub_rows2.size();x++){
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
new_sub_row = inflater.inflate(R.layout.recyclerview_pd, null);
TextView heading2 = (TextView)new_sub_row.findViewById(R.id.heading2);
heading2.setText(sub_rows2.get(x).sub_heading);
Log.i(LOG_TKT,sub_rows2.get(x).sub_heading);
TextView detail2 = (TextView)new_sub_row.findViewById(R.id.detail2);
detail2.setText(sub_rows2.get(x).value);
Log.i(LOG_TKT, sub_rows2.get(x).value);
viewHolder.linearlayout_recyclerview_pd.addView(new_sub_row);
}
//viewHolder.imgThumbnail.setImageResource(movie.getThumbnail());
hm2 = new HashMap<String, ArrayList<PD_Data>>();
sub_rows2 = new ArrayList<PD_Data>();
}
What am I doing wrong ?

It is obvious from your question that you aren't familier with how to use RecyclerViews. You need to read up on the subject. Here is a good start.
Basically, onBindViewHolder() is only responsible for binding your data to your viewHolders which hold your item layouts provided by you through onCreateViewHolder(). The reason for that is that RecyclerView recycles your views so it doesn't have to create new views every time you scroll.
In your case, it appears that you would need to use some technique that will tell the RecyclerView to use different viewHolders for different items. See how you can do it here.

I have given answer for better practice of binding data with ViewHolder in another question. You can check it here RecyclerView causes issue when recycling I hope this will help you. This will solve you problem well.
EDIT
Have you solved your problem yet? If not , consider my advice. You know the problem right? suppose you create a ViewHOlder for item 0 and add some text in it. While scrolling suppose this ViewHolder recycled for item number 10, then according to your code it will add new text rows along with some text rows you added for item number 0. You can solve it like
LayoutInflater inflater;
View new_sub_row;
//check here if the linear layout already has previusely added child, if yes remove them
if(viewHolder.linearlayout_recyclerview_pd.getChildCount()>0){
viewHolder.linearlayout_recyclerview_pd.removeAllViews();
}
for(int x=0;x<sub_rows2.size();x++){
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
new_sub_row = inflater.inflate(R.layout.recyclerview_pd, null);
TextView heading2 = (TextView)new_sub_row.findViewById(R.id.heading2);
heading2.setText(sub_rows2.get(x).sub_heading);
Log.i(LOG_TKT,sub_rows2.get(x).sub_heading);
TextView detail2 = (TextView)new_sub_row.findViewById(R.id.detail2);
detail2.setText(sub_rows2.get(x).value);
Log.i(LOG_TKT, sub_rows2.get(x).value);
viewHolder.linearlayout_recyclerview_pd.addView(new_sub_row);
}

Related

Dynamic items in viewholder on RecyclerView

If we have N categories (between 1 and 100 according to a REST API) and each with X items (between 1 and 50, depending on the category), what is the best way to do a category RecyclerView?
To add do this I am adding card views for each item item of a category in the method "onBindViewHolder" like this:
#Override
public void onBindViewHolder(MoviesViewHolder holder, int i) {
holder.title.setText(items.get(i).getTitle());
// I add a CardView (item_category.xml) for each item in the list
for (ObjectShort object: items.get(i).getObjects()) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.item_category, holder.linearCategories, false);
ImageView img = (ImageView) view.findViewById(R.id.iv_wallpaper);
imageLoader.load(img, object.getImg().getPoster().getThumbnail());
holder.linearCategories.addView(view);
}
}
And when it is recycled I remove the ITEMS elements in "onViewRecycled" because if I do not do it when doing vertical scrolling and reloading a category, items are added again duplicating:
#Override
public void onViewRecycled(MoviesViewHolder holder) {
// delete all items
holder.linearCategories.removeAllViews();
}
Is there any better way to do it? This is not effective and the scroll is very slow
thanks to all
To answer your question, I'm going to go back to the old days of ListView. Let's imagine that I had a simple list where each row was just a title and an image. A really naive implementation of getView() to support that list might look like this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.itemview, parent, false);
TextView title = view.findViewById(R.id.title);
title.setText("hello world");
ImageView image = view.findViewById(R.id.image);
image.setImageResource(R.drawable.my_image);
return view;
}
There's two major sources of performance trouble in this implementation:
We're inflating a new view every time
We're calling findViewById() every time
Number 1 was solved by making use of the convertView parameter, and number 2 was solved by something called the "view holder pattern". When the Google dev team created the RecyclerView API to replace ListView, they made "view holders" a first-class citizen, and now everyone works with RecyclerView.ViewHolders by default.
It's important to remember that the performance gains of avoiding repeated view inflation and repeated view lookup were so valuable that Google baked them into the new system.
Now let's look at your code...
public void onBindViewHolder(MoviesViewHolder holder, int i) {
...
for (ObjectShort object: items.get(i).getObjects()) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.item_category, holder.linearCategories, false);
ImageView img = (ImageView) view.findViewById(R.id.iv_wallpaper);
imageLoader.load(img, object.getImg().getPoster().getThumbnail());
holder.linearCategories.addView(view);
}
}
This for loop inside your bind method has all the same problems that my naive getView() implementation above had. Inflating a new view for each item in the category, and invoking findViewById() to get ahold of its ImageView is going to be expensive, especially when the user flings the list.
To fix your performance problems, just apply the same logic to your "inner" list of content as you have to your "outer" content: use a RecyclerView for items (inside each ViewHolder for categories).
Here is a really simple sample app that illustrates my solution: https://gist.github.com/zizibaloob/2eb64f63ba8d1468100a69997d525a54

Update adapter items with add dynamic drawable on getview() lead to show wrong data

I have a custom adapter that list my items. in each Item I check database and draw some circles with colors.
As you see in code I check if convertView==null defines new viewHolder and draw my items. but when I scroll listview very fast every drawn data ( not title and texts) show wrongs!
How I can manage dynamic View creation without showing wrong data?!
UPDATE
This is my attempts:
I used ui-thread to update my list but the result is same and data drawing go wrong.
in second I try to load all data with my object so that there is no need to check db in adapter. but it problem is still remains...
finally I create the HashMap<key,LinearLayout> and cache every drawn layout with id of its item. So if it's drawn before I just load its view from my HashMap and every dynamic layout will create just once. But it still shows wrong data on fast scrolling! Really I don't know what to do next!
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder viewHolder;
final MenuStructureCase item = getItem(position);
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = this.mInflater.inflate(R.layout.fragment_menu_item, null);
viewHolder.menu_title = (TextView) convertView.findViewById(R.id.menu_title);
viewHolder.tag_list_in_menu_linear_layout = (LinearLayout) convertView.findViewById(R.id.tag_list_in_menu_linear_layout);
viewHolder.menu_delete = (ImageButton) convertView.findViewById(R.id.image_button_delete);
importMenuTags(viewHolder, getItem(position), viewHolder.tag_list_in_menu_linear_layout);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.menu_title.setText(item.getTitle());
}
return convertView;
}
and this is importMenuTags():
private void importMenuTags(ViewHolder viewHolder, MenuStructureCase item, LinearLayout layout) {
List<String> tags = db.getMenuTags(item.getTitle()); //this present list of string that contain my tags
for (String tag : tags) {
Drawable drawable = getContext().getResources().getDrawable(R.drawable.color_shape);
drawable.setColorFilter(Color.parseColor(each_tag_color), PorterDuff.Mode.SRC_ATOP);
RelativeLayout rl = new RelativeLayout(getContext());
LinearLayout.LayoutParams lparams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
lparams.setMargins(15, 15, 15, 15);
lparams.width = 50;
lparams.height = 50;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
rl.setBackground(drawable);
} else {
rl.setBackgroundDrawable(drawable);
}
rl.setLayoutParams(lparams);
layout.addView(rl);
}
}
You have to select data from db before adapter initialization. So that
getItem(position)
will return already a "ready" item-object.
You shouldn't set the values to Views inside
if (convertView == null) {
...
}
This code is only for a viewHolder initialization. You create a new one, if convertView is null or read it as tag.
Setting of values you have to do after viewHolder initialization, actually where you set the title.
But in order to increase a performance, you shouldn't select the values from db on each step of getView. You have to have everything prepared (already selected).
You can do this way:
First of all create method inside adapter class:
public void updateNewData(List<MenuStructureCase> newList){
this.currentList = newList;
notifyDataSetChanged();
}
Now call above method whenever you want to update ListView.
How to call with object of CustomAdapter:
mAdapter.updateNewData(YourNewListHere);
Hope this will help you.
Rendering of data takes times and may be that's causing the issue when you are scrolling fast.
You can restirct the scrolling ( like Gmail : use a pull to refresh ) so that a less amount to data is processed in a list view at single time .
use RecyclerView instead of listview for better performance
ListView recreates the view on scrolling .
May be you can explain more about your problem , then we can provide the inputs accordingly.

Android listview getting all items

In android, is it possible to get all items inside the list view. Lets say the list view has multiple rows and only 2 rows are visible on the screen while the rest are accessible using the scroll bar. Each row has a radio button and a text view. Is there a way to get all textview of the rows whose radio button is selected and not just the ones visible on the screen.
Your answer may be:
for(int item = 0; item < m_listitem.count(); item ++){
if(m_listitem[item].isSelected){
View view = ListView.getChildAt(i);
TextView textview = view.findViewById(your textView id);
// do some thing
}
}
You can use custom list view to show your list items with checkbox & textview.
I happened to have a similar requirement where I had multiple EditText inside a ListView and only few of them were visible on the screen. I needed to get the values of all EditText and not just the ones visible on the screen.
Well if you are using a default Adapter, then the way it will work is it will recycle the old views to create new ones. So there is no way to preserve values of those Views which are not visible.
So the only workaround is to create your own Adapter, maybe something like the following, which will not recycle any views, but every time inflate new ones.
public class ListViewAdapter extends ArrayAdapter {
public ListViewAdapter(Context context, ArrayList<Object> items) {
super(context, 0, items);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
return LayoutInflater.from(getContext()).inflate(R.layout.your_layout_for_list_view_item, parent, false);
}
}
After that, as in above answer Lãng Tử Bị Điên has mentioned, you can check in your java code, if your radio buttons are checked or not, and according to that, selected desired TextViews
for(int item = 0; item < m_listitem.count(); item ++){
if(m_listitem[item].isSelected){
View view = ListView.getChildAt(i);
TextView textview = view.findViewById(your textView id);
// do some thing
}
}
Hopefully this should do it.. It sure worked in my case!

Custom views inside ListView with itemViewType

I have a ListView in my application. The adapter for this listview contains multiple item view types (around 5 till now), via which I can inflate different types of row views inside the listview.
All row views inflated inside the adapter are custom subclassed view/view group.
public class CustomView1 extends RelativeLayout {
Bundle bundle;
public CustomView1(Bundle bundle) {
super(context);
this.bundle = bundle;
addSubViews(bundle.getBundleList("list"));
}
private void addSubViews(ArrayList<Bundle> list) {
for(Bundle element : list) {
//add sub views via reflection
View view = (View) Class.forName(packageName + type).getConstructor(Bundle.class).newInstance(element);
addView(view);
}
}
//called from getView() in adapter when convertView != null
public void onRecycle(Bundle bundle) {
if(bundle != this.bundle) {
this.bundle = bundle;
removeAllViews();
addSubViews(bundle.getBundleList("list"));
}
}
}
Bundle passed to each custom view contains layout info for that view. In this way, I can create and add any view/viewgroup inside any viewgroup. All well till now.
Now the problem comes when this code runs inside ListView. Since all the view types are created by the adapter initially, scrolling jerks a lot because the adapter keeps on creating new custom views of different itemViewType. How to reduce those jerks in listview ? Any ideas? In the listview, all viewTypes are different at the top 5 positions, so the adapter has to create these views and that makes the experience sluggish.
Even when the adapter recycles similar view type convertViews after 5th index, I clear the container using removeAllViews() and run this loop again because the subView bundle list of the incoming bundle from 6th position onwards might be different. So in the end, adapter is only recycling empty ViewGroups. Since the subView list can possibly contain anything (maybe one more bundle list inside any element bundle), I have to do removeAllViews() to accommodate new subview tree in the recycled convertView.
I thought of using vertical ScrollView but that would take too much memory upfront, and the number of custom views inflated is dynamic, can increase to 20.
The app is running but the scroll is so bad there is hardly any usability left, so its looking like till now I have achieved nothing by adding so much dynamic behavior also. Please suggest me ways to counter this problem.
I am suspecting that the use of setLayoutParams inside CustomView classes may be stopping the scroll because I set the width/height of all views after they are created.
Update #1 getView() code using ViewHolder pattern
ViewHolder holder;
if(convertView == null) {
holder = new ViewHolder();
holder.customView1 = new CustomView1(bundle);
convertView = holder.customView1;
convertView.setTag(holder);
} else {
holder = (ViewHolder)convertView.getTag();
}
holder.customView1.onRecycle(bundle);
ListView has excellent support for different View types. Just make sure to use view holder pattern to avoid jerky scrolling and then override getViewTypeCount() and getItemViewType().
More detail http://android.amberfog.com/?p=296

What's the quickest way to add several views to a LinearLayout?

I have a LinearLayout view that already contains several elements. I want to add a lot more Views to it, programmatically. And because this is inside a ScrollView, everything will be scrolled.
So what I do is go through my list, and add new instances of my custom View to it. That custom view inflates a XML layout and adds a few methods.
This approach works well. The problem is that it's super slow, even without any crazy code... a list with 10 items takes around 500ms to instantiate. As an user experience standpoint, this is hard to swallow.
My question is, is this the correct/best approach? Android seems to take a lot of time inflating the layout, even though "R.layout.my_list_item" is super simple. I wonder if there's a way to maybe to reuse "inflated" layouts for additional views, kinda caching the more complex parsing?
I've tried doing this with a ListView (and adapter and a wrapper) and it seems to be much faster. The problem is that I can't use a simple ListView; my layout is more complex than a simple list (the LinearLayout itself contains additional custom icons, and it has another parent with even more Views before it's wrapped by the ScrollView).
But is there a way to use an adapter for a LinearLayout? Would that be faster than trying to add the views myself?
Any help is appreciated. I'd love to make this faster.
Code follows.
Main Activity:
// The LinearLayout that will contain everything
lineList = (LinearLayout) findViewById(R.id.lineList);
// Add a lot of items for testing
for (int i = 0; i < 10; i++) {
addListItem("Item number " + i);
}
protected void addListItem(String __title) {
MyListItem li;
li = new MyListItem(this);
li.setTitle(__title);
lineList.addView(li);
}
MyListItem:
public class MyListItem extends RelativeLayout {
protected TextView textTitle;
public MyListItem(Context __context) {
super(__context);
init();
}
public MyListItem(Context __context, AttributeSet __attrs) {
super(__context, __attrs);
init();
}
public MyListItem(Context __context, AttributeSet __attrs, int __attrsdefStyle) {
super(__context, __attrs, __attrsdefStyle);
init();
}
protected void init() {
// Inflate the XML layout
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.my_list_item, this);
// Create references
textTitle = (TextView)findViewById(R.id.textTitle);
}
public void setTitle(String __text) {
textTitle.setText(__text);
}
}
What I'm trying to accomplish is this. Consider this layout:
This layout is a FrameLayout (outer box) containing a ImageView (in gray), a TextView (inner rectangle, on top) and a LinearLayout (inner rectangle, on bottom). This LinearLayout rectangle is the one I'm dynamically populating with a few items.
After I populate it, I want the final result to be this (where every new rectangle is a new MyListItem instance):
That is, everything is scrollable (the background image, for example, is aligned on top). The LinearLayout isn't scrollable by itself (everything else follows) hence why a ListView, from what I know, wouldn't work very well
in my case.
3 Options:
Replace everything with a ListView, with the other parent and custom icons as a header view for the ListView. ListView is faster, because it only creates Views as it needs them.
Programatically create the contents of my_list_item instead of inflating, might be quicker
Use of ViewStubs may allow you to load views on-demand.
Maybe it isn't loading the views but the data? in which case prepare the data in a background thread.
A ListView is the way to go.
You say that your layout is too complex. But it is completely okay to inflate a complex layout as a child. For example a layout that has text and then an icon:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
Could be inflated in your adapter as so:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
LinearLayout root = null;
ImageView editImageView;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
root = (LinearLayout)inflater.inflate(R.layout.item, null);
} else {
root = (LinearLayout)convertView;
}
}
You can also be a little more clever in order to support a header. Just add a check if the index is the root and inflate a different view. Since the header is the only one that is different you will still take advantage of all the other rows being reusable. You can even pre-inflate and store the header and reuse it to completely get rid of inflation.
Just use a ListView!
It's the easiest to set up and easiest to maintain. You define an XML layout for the List-Row, and an XML layout for the View which holds the entire List. The ListAdapter does the rest for you.
Just create a:
List<HashMap<String, String>> services = new ArrayList<HashMap<String, String>>();
...and loop through your data to add as many items as you like to the Map. Then set this map to your ListAdapter. Whether 10 items or 100 items the ListAdapter will create a List with that many items.
Example:
public void updateProductList(String searchTerm) {
createOrOpenDB(this);
Cursor cursor = (searchTerm!=null)? dbAdapter.fetchWhere(TBL_NAME, KEY_NAME+" LIKE '%"+searchTerm+"%'")
: dbAdapter.fetchAll(TBL_NAME);
int rows = cursor.getCount();
if (rows<=0) return;
services.clear(); //clear current list
for (int i=0; i<rows; i++) {
HashMap<String, String> map = new HashMap<String, String>();
cursor.moveToPosition(i);
map.put(KEY_NAME, "" + cursor.getString(cursor.getColumnIndex(KEY_NAME)));
map.put(KEY_DESC, "" + cursor.getString(cursor.getColumnIndex(KEY_DESC)));
map.put(KEY_COST, "" + cursor.getDouble(cursor.getColumnIndex(KEY_COST)));
services.add(map);
}
cursor.close();
closeDB();
ListAdapter adapter = new SimpleAdapter(this, services, R.layout.products_view_row,
new String[] {KEY_NAME, KEY_DESC, KEY_COST},
new int[] {R.id.listViewText1, R.id.listViewText2, R.id.listViewText3});
setListAdapter(adapter);
}

Categories

Resources