ViewHolder in RecyclerView.Adapter not specific to position - android

The following is part of my code for onBindViewHolder (inside MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>)
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
// - get element from your dataset at this position
StatusItem item = mDataset.get(position);
//......
//Add content and timing to the textview
String content = item.getContent();
holder.mTextViewTime.setText(timing);
//Set the img
holder.imgViewIcon.setImageDrawable(item.getProfileDrawable());
//Set content image (for Instagram)
holder.mImageViewContentPic.setImageDrawable(item.getContentDrawable());
//HIDE THE VIEW Start
if(item.getContentDrawable() == null){
holder.mImageViewContentPic.setVisibility(View.GONE);
}
//HIDE THE VIEW End
}
The part HIDE THE VIEW is not working as expected.
When I am scrolling downwards, the views are working normally. However, when I start to scroll upwards, i.e. revisited the previous views, the ImageViews that are supposed to be VISIBLE becomes GONE, although I checked my dataset and verified that it has not been modified. Try calling other methods on the views also give erratic results(positions and items in dataset do not match).
It seems that the view holders are not binded to specific positions inside the RecyclerView.
The code works as expected if I remove the HIDE THE VIEW part.
Is there any way to solve this issue and dynamically hide views in my case?
Note: I used some AsyncTasks to update the dataset and call notifyDataSetChanged(), if that is relevant.

###This is the solution to your problem:###
holder.mImageViewContentPic.setVisibility(View.VISIBLE);
if(item.getContentDrawable() == null){
holder.mImageViewContentPic.setVisibility(View.GONE);
}

Because RecyclerView use recycle very well, ViewHolder A may be used to be ViewHolder B, so you need to specific every attribute of a ViewHolder in case of some attributes attach to a wrong object.

Related

Two items get selected in listview android

I'm trying to implement a custom listview. Everything works fine until I use a if () statement inside the getView() method
Without the if() condition a single item gets selected when I select an item but when I add the if() condition, the views are displayed properly but two items (non-adjacent) get selected (1st and last 1st or and second-last, any such combination).
View getView(...){
....
if (!item.getPriceTo().equals(""))
priceToTV.setText(item.getPriceTo());
else
priceToTV.setText(item.getPriceFrom());
return view;
}
Also I'm using saving the previous view to show the selection so the current selection has a red_border and when it is selected a black_border is set to it.:
subItemsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Log.d("New Order", "........");
if (previousViewOfSubItems != null && previousViewOfSubItems != view) {
previousViewOfSubItems.setBackgroundResource(R.drawable.black_border);
if (quantity.getText().toString().equals("xx") || quantity.getText().toString().equals("0")) {
viewForVisibility.setVisibility(View.GONE);
layoutForQuantity.setVisibility(View.GONE);
}
}
if (previousViewOfSubItems == view)
return;
previousViewOfSubItems = view;
previousViewOfSubItems.setBackgroundResource(R.drawable.red_border);
viewForVisibility = previousViewOfSubItems.findViewById(R.id.viewForVisibility);
viewForVisibility.setVisibility(View.VISIBLE);
layoutForQuantity = (LinearLayout) previousViewOfSubItems.findViewById(R.id.layoutForQuantity);
layoutForQuantity.setVisibility(View.VISIBLE);
quantity = (TextView) previousViewOfSubItems.findViewById(R.id.subTypeQuantity);
}
});
previousViewOfSubItems = view; seems to be causing the problem,
In Listviews with adapter you should avoid saving view instances, because views are reused by adapters so view can be same for two rows so rather than saving view instance's reference use ViewHolder Design pattern and use view tagging
You need to use ViewHolder Pattern and view tagging to properly identify every view in different position. ListView always recycle the view instead of re-inflating the view again and again.
You can refer to Android Training documentation on how to implement ViewHolder pattern.
ListViews recycle the views in the list. so as you scroll, top views are reused and content replaced using the methods.
Where you set background to Red etc, use Else statements to set it back to your default black.
its beacause it listview reuses view to display items, once the first view is scrolled out the the same view is reused to display the view at bottom of the listview. instead of comparing the view try compairing the position of the view clicked
I have explained about this abnormal behavior in my blog on recyclerview you refer that to solve this problem.
use pojo class to get the status and update the view accordingly

Accessing a list item's view and data at the same time

I am working on a messaging application and would like to set the chat bubble as coming from the left or the right depending on who the owner of the message is.
public class Message {
public String messageText;
public boolean mine;
// ...Constructor
}
Inside MessageAdapter I have:
#Override
public MessageHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(this.itemResource, parent, false);
return new MessageHolder(this.context, view);
}
#Override
public void onBindViewHolder(MessageHolder holder, int position) {
Message message = this.messages.get(position);
holder.bindMessage(message);
}
I have been following tutorials to migrate from ListView to RecyclerView and am now unclear as to how to access both an item's data and its view at the same time without getting NullPointerException. The code I want to use looks like this:
Drawable background = getResources().getDrawable(message.mine ? R.drawable.bubble_right : R.drawable.bubble_left);
background.setColorFilter(getResources().getColor(R.color.ColorPrimary), PorterDuff.Mode.SRC_IN);
view.findViewById(R.id.message_text).setBackgroundDrawable(background);
However, if I put it in onCreateViewHolder I can't figure out how to access the individual message to check ownership. If I put it in onBindViewHolder I have message but can't get its view. How do I solve this?
If you are moving from ListView to RecyclerView, then it's very easy to understand.
getView() (of ListView) == onCreateViewHolder() + onBindViewHolder();
If you remember we need to check view given by getView is null or not, and if it's null then we need to take care of instancing otherwise set the data to corresponding views.
RecyclerView are making our life easy, the onCreateView creates new instance of the view of each row(minimum required) and onBindViewHolder takes care of binding data to each view element.
I think now you are clear why and how to handle these two methods of RecyclerView.
As per your code, the line view.findViewById(R.id.message_text) should be moved to your ViewHolder class.
And the following lines will go in onBindViewHolder
Drawable background = getResources().getDrawable(message.mine ? R.drawable.bubble_right : R.drawable.bubble_left);
background.setColorFilter(getResources().getColor(R.color.ColorPrimary), PorterDuff.Mode.SRC_IN);
holder.your_view.setBackgroundDrawable(background);
Let me know if you want to know further clarifications.

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

Android multiple listviews with checkbox

I'm new to Android programing. I need to create a list of 1000 goods for users to click and check or the one they want to buy. I've created the array list and added it to my custom adapter and I have also added it to my list view. my problem is how to get the position for each item selected and I need clarification on the getView and the ViewHolder. I'm not working with toast
See the below tutorials,
http://sunil-android.blogspot.in/2013/04/android-listview-checkbox-example.html
http://developerandro.blogspot.in/2013/09/listview-with-checkbox-android-example.html
http://aboutyusata.blogspot.in/2013/11/how-to-make-listview-with-checkbox-in.html
Hope it helps.
You have to use ListView getCheckedItemPositions()
/**
* Returns the set of checked items in the list. The result is only valid if
* the choice mode has not been set to {#link #CHOICE_MODE_NONE}.
*
* #return A SparseBooleanArray which will return true for each call to
* get(int position) where position is a checked position in the
* list and false otherwise, or <code>null</code> if the choice
* mode is set to {#link #CHOICE_MODE_NONE}.
*/
public SparseBooleanArray getCheckedItemPositions() {
if (mChoiceMode != CHOICE_MODE_NONE) {
return mCheckStates;
}
return null;
}
getView() method is called when new list item that is adapted is being shown on your screen. That is why you need to take good care of the memory and setTag() for every item you inflate. Then when old item is viewed again you will not render it like a new one but get it by the tag you submitted for that item.
Example: If you have 1000 items only couple of those will be shown on the screen and your program will call getView() for those items that are visible plus for couple of items bellow those that are visible so you don't see the lag in inflation while scrolling.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if(view==null)
{
LayoutInflater inflater=getLayoutInflater();
view=inflater.inflate(R.layout.list, parent, false);
TextView textView=(TextView)view.findViewById(R.id.text_hint);
ImageView imageView=(ImageView)view.findViewById(R.id.img);
// here we will setTag() our view later
}
//... (here we will manage the views and set some click listeners)
Now, what is a viewHolder?
The ViewHolder basically is the private class inside your adapter that is used to keep you inflated layout elements together and to let you manipulate those view after you got the using findViewById(int resId).
Some code:
private class ViewHolder{
public ImageView imageView;
public TextView textView;
public ViewHolder(ImageView imageView,TextView textView) {
this.imageView = imageView;
this.textView = textView;
}
}
Now to setTag() as promised.
view.setTag(new Holder(imageView,textView));
Now with this in mind you can get your ViewHolder using the code above and views that you got in the first part of my code. (this is what we will write instead of 3 dots)
ViewHolder h = (Holder) view.getTag();
h.textView.setText(ar[position]);
int resID = getResources().getIdentifier(pic[position], "drawable", getPackageName());
h.imageView.setImageResource(resID);
// here I did set some source to my imageView that is in my layout, but
// you can do whatever you want with these views. And that is what I'm
// going to explain later in this text.
return view;
}
Okay, what next?
After getting the ViewHolder from tag you have your entire layout as a View that can get his OnClickListener easily. Or just a checkBox can get OnCheckedChangeListener.
Inside methods for this listeners you can send the data to you controller (in case you are using MVC model) or to your activity that hosts this View, where you can save the state of the checkbox and title of the item that has been clicked.
For example you can do something like this on your corresponding listener method:
(MainActivity)context.markAsChecked(String title);
But in this case you will also need to have the opposite method for unchecking
(MainActivity)context.markAsUnchecked(String title);
and you will have to handle this in your MainActivity properly by browsing through the array of data that has been selected.
The second solution is to have :
(MainActivity)context.toggleState(String title);
And to handle both events checked and unchecked.
Your method in your Activity would need to do something similar to this:
public void toggleState(String title){
if (data.contains(title))
data.remove(title);
else data.add(title);
}
Then after your user checks what he wants you will have all the checked elements in your data array that in this case is ArrayList. You can also you HashMap for this if you like or something else too.
Hope this helps.
I will be more than happy to answer all of your questions if have some more.
Maybe controller implementation is something that you would like to consider in this case. That would mean that you would be using MVC model for better control of your app and data, and to delegate the tasks to responsible classes and methods. Not to put everything in one Activity :)
Bye

Bugs in custom ListView while scrolling

In myadapter.java I have following code:
public View getView(int position,View convertView,ViewGroup parent) {
View view=null;
if(convertView!=null)view=convertView;else view=newView(context,parent);
HashMap<String,String> d=new HashMap<String,String>();
d=data.get(position);
String _r=d.get("r");
String out=d.get("out");
Typeface mf=Typeface.createFromAsset(context.getAssets(),"fonts/mf.ttf");
TextView txt=(TextView)view.findViewById(R.id.c_n);
txt.setText(_r);
txt.setTypeface(mf);
if(out.equals("yes") && !d.get("sid").equals("-1")) {
ImageView imag=(ImageView)view.findViewById(R.id.myimage);
imag.setVisibility(imag.VISIBLE);//This fires sometimes while scroll, while
//I scroll & where I don't need it.
//view.setBackgroundDrawable(context.getResources().getDrawable(R.drawable.c_c));
//^ same as setVisibility.
}
...
return view;
}
When I start my app, this list is OK. But, while I scroll, imag.setVisibility(imag.VISIBLE); fires sometimes where I don't need it, like listview generates every scroll event. Some ImageViews become visible, that weren't at apps start.
How do I fix this?
The problem is caused by convertView and the way it is used for re-cycling existing views.
Example - suppose your list adapter has 20 items but the ListView can only display 5 on the screen. Those 5 list item 'views' will be re-cycled by being passed as the convertView parameter when the ListView is scrolled.
Once you set the visibility of the ImageView, it will remain set in the convertView. In other words you need to set it to INVISIBLE or GONE if you don't want it visible...
ImageView imag=(ImageView)view.findViewById(R.id.myimage);
if (d.get("ms").equals("yes") && !d.get("sid").equals("-1")) {
imag.setVisibility(View.VISIBLE);
}
else
imag.setVisibility(View.INVISIBLE); // Or use View.GONE depending on what you need

Categories

Resources