Saving SwitchCompat State in the recycler view while scrolling - android

I have a recyclerview which its items contain textView and switchCompat. And in the same activity I have also a textView that have a numerical value in it. The task is when the switchCompat turned on the text view above the recyclerview which contain the numerical value should increase by the value in the recyclerview item textview. I already did that but when scrolling in the recyclerview the switchCompat back to the default state and the value of the numerical textview backs to its old value,
Any help with that?
I Apology for not being able to post a part of the code now and I'll do this as soon as i can, I just posted it now in case anyone pass through something like this before
Thank you

The key to a recycler view or any adapter view in Android is to have the adapter adapt your models to the view. In your case your view is a TextView plus a Switch, so your adapter must adapt some model to this view. In this case I'd choose a simple model like this:
class ItemModel {
String text;
boolean on;
}
I've omitted getters and setters for simplicity
This model contains an string text which reflects the text in your text view and a boolean on that reflects the state of the switch. When true the switch is checked and when false it's unchecked.
There's tons of ways to represent this model. I've chosen this one, you may choose a different one. The point is, you need to save the state somewhere and this is what I mean by model - the view model.
Now let's build an adapter that can do 2 things - Update the models when the switch is clicked and tell the activity that the switch changed state. Here's one way to do this:
public class ItemsAdapter extends
RecyclerView.Adapter<ItemsAdapter.ViewHolder> {
#NonNull
private final List<ItemModel> itemModels;
#Nullable
private OnItemCheckedChangeListener onItemCheckedChangeListener;
ItemsAdapter(#NonNull List<ItemModel> itemModels) {
this.itemModels = itemModels;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.item, parent, false));
}
#Override
public void onBindViewHolder(#NonNull final ViewHolder holder, int position) {
ItemModel item = itemModels.get(position);
holder.text.setText(item.text);
holder.switchCompat.setChecked(item.on);
// Make sure we update the model if the user taps the switch
holder.switchCompat.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
int adapterPosition = holder.getAdapterPosition();
ItemModel tapped = itemModels.get(adapterPosition);
itemModels.set(adapterPosition, new ItemModel(tapped.text, isChecked));
if (onItemCheckedChangeListener != null) {
onItemCheckedChangeListener.onItemCheckedChanged(adapterPosition, isChecked);
}
}
});
}
#Override
public void onViewRecycled(#NonNull ViewHolder holder) {
super.onViewRecycled(holder);
holder.switchCompat.setOnCheckedChangeListener(null);
}
#Override
public int getItemCount() {
return itemModels.size();
}
public void setOnItemCheckedChangeListener(#Nullable OnItemCheckedChangeListener onItemCheckedChangeListener) {
this.onItemCheckedChangeListener = onItemCheckedChangeListener;
}
interface OnItemCheckedChangeListener {
/**
* Fired when the item check state is changed
*/
void onItemCheckedChanged(int position, boolean isChecked);
}
class ViewHolder extends RecyclerView.ViewHolder {
TextView text;
SwitchCompat switchCompat;
ViewHolder(View itemView) {
super(itemView);
text = itemView.findViewById(R.id.item_text);
switchCompat = itemView.findViewById(R.id.item_switch);
}
}
}
There's a lot to digest, but let's focus on the important bits - the method onBindViewHolder. The first 3 lines are the classic recycling of the view. We grab the model at the correct position and set the elements in the view that correspond to model's attributes.
Then it gets more interesting. We set a OnCheckedChangeListener to update the model and the activity every time the switch changes state. The first 3 lines change the model in the adapter and the rest uses the custom interface OnItemCheckedChangeListener to notify the listener about the switch change. It's important to notice that inside the method OnCheckedChangeListener you should no longer use position, but rather use holder.getAdapterPosition. This will give you the correct position in the adapter's data list.
Since now the adapter has always the correct models inside the data list, every time the method onBindViewHolder is called the adapter knows exactly how to setup the view. This means that while scrolling and recycling the views, it will preserve the state of each item within the models inside the data list.
It's important to remove the OnCheckedChangeListener when the view gets recycled - onViewRecycled. This avoids messing the count when the adapter is setting the value of switchCompat in the onBindViewHolder.
Here's an example of how the activity could look like:
public class MainActivity extends AppCompatActivity {
private int count = 0;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
List<ItemModel> data = new ArrayList<>();
for (int i = 1; i <= 100; i++)
data.add(new ItemModel("Item " + i, false));
ItemsAdapter adapter = new ItemsAdapter(data);
((RecyclerView) findViewById(R.id.recyclerview)).setAdapter(adapter);
final TextView countTextView = findViewById(R.id.count);
drawCount(countTextView);
adapter.setOnItemCheckedChangeListener(new ItemsAdapter.OnItemCheckedChangeListener() {
#Override
public void onItemCheckedChanged(int position, boolean isChecked) {
if (isChecked)
count++;
else
count--;
drawCount(countTextView);
}
});
}
private void drawCount(TextView countTextView) {
countTextView.setText(String.valueOf(count));
}
}
This code is meant to demonstrate the idea, not to follow :) In any case, we setup all the initial state and then set up the custom listener OnItemCheckedChangeListener to update the text view in the activity.
The layout files shouldn't be relevant here, but as you can imagine the activity has a text view with id count and there's a recycler view with the id recyclerview.
Hope this helps

It solved for me after adding the below method to the adapter:
#Override
public int getItemViewType(int position) {
return position;
}

Related

Recyclerview's item is selecting duplicate position and changing textcolor

if I select one item from list another item is also getting selected.
For example, I selected the first item then the first item's color is changing but along with this 13 other items color's are also changing. I am sharing the code files along with this post. Please review it.
Can someone please help me?
public class ArealistAdapter extends
RecyclerView.Adapter<ArealistAdapter.Pendingholder> {
Context context;
List<Area> pendingModels;
RecycleviewOnitemclick recycleviewOnitemclick;
public ArealistAdapter(Context context, List<Area> pendingModels, RecycleviewOnitemclick recycleviewOnitemclick) {
this.context = context;
this.pendingModels = pendingModels;
this.recycleviewOnitemclick = recycleviewOnitemclick;
}
class Pendingholder extends RecyclerView.ViewHolder {
TextView textView4;
public Pendingholder(#NonNull View itemView) {
super(itemView);
textView4=itemView.findViewById(R.id.cbCheck);
}
}
#NonNull
#Override
public ArealistAdapter.Pendingholder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view= LayoutInflater.from(context).inflate(R.layout.search,parent,false);
return new Pendingholder(view);
}
#Override
public void onBindViewHolder(#NonNull final ArealistAdapter.Pendingholder holder, int position) {
Area pendingModel=pendingModels.get(position);
holder.textView4.setText(pendingModel.getArea());
holder.textView4.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.textView4.setTextColor(Color.GREEN);
}
});
}
#Override
public int getItemCount() {
return pendingModels.size();
}
public interface RecycleviewOnitemclick{
void clickitem();
}
}
You need to store selected positions state in a data structure (selectedMap) like Map or SpareArray, and in onBindViewHolder function, just simply check whether item was selected or not:
private Map<Int,Boolean> selectedMap = new HashMap();
#Override
public void onBindViewHolder(#NonNull final ArealistAdapter.Pendingholder holder, int position) {
boolean isPositionSelected = selectedMap.get(position);
if(isPositionSelected ==null || isPositionSelected == false){
holder.textView4.setTextColor(defaultColor);
}else{
holder.textView4.setTextColor(Color.GREEN);
}
}
And inside onClick function, just update the selected state of that position:
holder.textView4.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
boolean isPositionSelected = selectedMap.get(getAdapterPosition());
if(isPositionSelected ==null || isPositionSelected == false){
selectedMap.set(getAdapterPosition(),true);
}else{
selectedMap.set(getAdapterPosition(),false);
}
}
notifyItemChanged(getAdapterPosition());
});
This is happening because recyclerview works on the basic logic of recycling the views. so if you set colour of a view at position 1 as blue but others are black, while recycling it can cause some issues.
Solution:
I personally add a is_selected boolean field in the model class of original array and set it as false by default. when it is selected, you can set the color of view as you want programmatically.
So, in your case:
Add is_selected in Area class.
in onBindViewHolder() class, if you get the is_selected as current position as true, you can set it as selected color or else the default color
in onClick of textview, set the is_selected as true/false for current position.
First of all, try not to put any thing other than setting text or so in your onBindViewHolder as this method is call repeatedly.
To implement a click listener, I would suggest adding it to the view holder class (PendingHolder) just after findViewById as this is called less often.
You can create a method such as :
Private void onClickTxt(Textview tv) {
tv.setOnClickListener..........
}
This method should be called from your view holder class. Not the onBindView.
Feel free to add a comment.
Happy coding.
PS if my answer helped you, a ☑ would be nice
You need to check if the text color is already green or not. If its not green only then change its color, Otherwise keep it as it is. It means you need to add an else condition with the if.

How to get child view from RecyclerView?

I am trying to get child view by position. I could get view when one item is clicked:
rvSellRecords.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
((MainActivity) getActivity()).showSellRecordFragment(position, view);
}
}));
Now I cannot get child view, without click - let's say by position for example:
rvSellRecords.someMagicalMethodWhichReturnsViewByPosition(5);
Question: How to get child view from RecyclerView?
EDIT FOR BOUNTY:
I have RecyclerView to show products list. When I click on it, I am adding new Fragment where I show product information. While opening I am updating toolbar with view from RecyclerView - this is working perfectly:
rvSellRecords.addOnItemTouchListener(new RecyclerItemClickListener(getContext(), new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
sellPresenter.onSellRecordSelected(position, view);
}
}));
When I click blue button with "+", I am incrementing quantity by 1.
public void onIncrementButtonClicked(){
sellRecord.setCount(sellRecord.getCount() + 1);
showQuantity();
bus.post(new SellRecordChangedEvent(sellRecord, sellRecordPosition));
}
Then I am posting updated sellRecord to first fragment using EventBus. There I am updating list data. I supposed that updating value(sell) automatically updates adapter. Now I am getting view from adapter using custom method(getView) which was created by me(you can find it below).
#Subscribe
public void onEvent(SellRecordChangedEvent event){
sell.getSellRecords().set(event.getSellRecordPosition(), event.getSellRecord());
sell.recalculate();
int position = event.getSellRecordPosition();
View view = adapter.getView(position);
bus.post(new TransactionTitleChangedEvent(null, view));
}
This is my adapter class - I changed adapter little bit to collect view in list and added method which returns view for respective position:
public class SellRecordsAdapter extends RecyclerView.Adapter<SellRecordsAdapter.ViewHolder> {
.....
.....
.....
List<View> viewList;
public SellRecordsAdapter(List<SellRecord> sellRecordList) {
.....
viewList = new ArrayList<>();
}
.....
.....
.....
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
.....
.....
.....
viewList.add(i, viewHolder.itemView);
}
public View getView(int position){
return viewList.get(position);
}
}
My problem: when I updating view in toolbar, I am getting old view. When quantity is 3, I am getting view with 2. When quantity 10 - view is with 9.
My question: how to get view from recycler view using position of item(without on click listener)?
Use recyclerView.findViewHolderForLayoutPosition(position) or
reyclerView.findViewHolderForAdapterPosition(position) to get the viewholder for postion. Then you can access any child from your viewholder.
Checkout Recyclerview
RecyclerView.ViewHolder holder = recycleView.findViewHolderForAdapterPosition(position);
ImageView imageView = holder.itemView.findViewById(R.id.iv_product);
This is a supplement to #Ravi Teja's answer. You can get the viewHolder from the recyclerView using position of the particular item, then get a particular view from the viewHolder as shown above
You can use RecyclerView's LayoutManager for it.
View view = layoutManager.findViewByPosition(position)
Hope this helps someone:
I was getting null pointer exceptions with:
recyclerView.findViewHolderForAdapterPosition
recyclerView.findViewHolderForItemId
layoutManager.findViewByPosition.
The reason was that there is a slight delay for the viewholder to be created.
I found the solution here: https://stackoverflow.com/a/33414430/7952427
I post an answer because which is really complex to findviews() from RecyclerView.
#Joe: After spending 4hours found one answer. Which gives me the proper view of the index.
mAdapter is adapter of RecyclerView
View v = recyclerView.findViewHolderForItemId(mAdapter.getItemId(index/position)).itemView;
Now just access your views by:
v.findViewById(R.id.edittext) OR any id.
it helped me, make a 100 ms delay before manipulate it, like this:
Handler handler = new Handler();
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
// rcv is my recyclerview
rcvStatus.getChildAt(1).setBackground(getActivity().getResources().getDrawable(R.drawable.disabled));
// or:
rcvStatus.getChildAt(1).setClickable(false);
}
}, 100);
Write this method in adapter.
public Object getItem(int position) {
return yourArrayList.get(position);
}
and you just need to call it like
yourAdapter.getItem(2);
pass your required position.
Hope it solves your problem.
just put this method in your code and you can call it as you likes
void someMagicalMethodWhichReturnsViewByPosition(int position){
//I assumes child views are CardView
CardView c = (CardView)rvSellRecords.getItem(int position);
///optional codes
//////////
}
now I understand your problem. you need to use interface for join recyclerview item and activity.
you must define an interface class like below:
public interface IViewClick {
public void onClickButtonAdd();
}
add this parameter to your adapter class:
private IViewClick mListener;
and initialize it in constructor with value that get from inputs.
when user click on PLUS button, you send event to activity by this line:
mListener.onClickButtonAdd();
in your activity class you must implements IViewClick interface and add your code there, like this:
#Override
public void onClickButtonAdd() {
/// TODO every thing that you want.
/// change your toolbar values.
}
it is not good solution for you.
RecyclerView.ViewHolder holder =
mRecyclerView.findViewHolderForItemId(mAdapter.getItemId(i));
I wouldn't recommend tracking the view list yourself. It could lead to weird issues with item updates, position updates, etc.
Instead on your SellRecordChangedEvent, use findViewHolderForAdapterPosition() instead of adapter.getView().
#Subscribe
public void onEvent(SellRecordChangedEvent event){
sell.getSellRecords().set(event.getSellRecordPosition(), event.getSellRecord());
sell.recalculate();
int position = event.getSellRecordPosition();
View view = yourrecyclerview.findViewHolderForAdapterPosition(position);
bus.post(new TransactionTitleChangedEvent(null, view));
}
http://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#findViewHolderForAdapterPosition(int)
And as a side note, it's better to implement an actual item click listener to the itemView on the ViewHolder instead of using touch listener. There's lots of examples of this online.
So the recyclerview and your product information are in 2 different fragments yes? You are expecting the recyclerview's views to update when they are not even in foreground? also you are changing adapter data item's data at position event.getSellRecordPosition() , but you are not notifying the adapter that its dataset changed, either by adapter.notifyDataSetChanged() or the other notifyItemChanged(position) methods.
I'd modify your onEvent() like so:
#Subscribe
public void onEvent(SellRecordChangedEvent event){
sell.getSellRecords().set(event.getSellRecordPosition(), event.getSellRecord());
sell.recalculate();
int position = event.getSellRecordPosition();
MyViewHolder holder = adapter.onCreateViewHolder(yourRecyclerView, 0);
adapter.onBindViewHolder(holder,position);
View view = adapter.getView(position);
bus.post(new TransactionTitleChangedEvent(null, view));
}
Calling on createViewHolder and next BindViewHolder on your adapter will definitely update the views for that position, then your adapter.getView(position) should return you the latest view.
Here MyViewHolder is your viewholder class and yourRecyclerview, is the reference to your recycler view
for (int i = 0; i < recycler_view.getAdapter().getItemCount(); i++) {
View viewTelefone = recycler_view.getChildAt(i);
}
If you want to replace text on a particular edit text for same position:
for (int i = 0; i < recycler_view.getAdapter().getItemCount(); i++) {
if(adpterPostion==i)
{
View viewTelefone = recycler_view.getChildAt(i);
EditText et_mobile = (EditText) viewTelefone.findViewById(R.id.et_mobile);
et_mobile.setText("1111111");
}
}

Get the position of clicked item on Recycler View

I have implement a RecyclerView and it works fine. I have an ArrayList which contains the data for the recycler view. The layout of each item is complicated. It contains two frameLayout. The framelayout1 contains an image and a text and the framelayout2 contains an image and four texts. When the user clicks on the framelayout1 I want to open the Activity1 and when the users clicks on the framelayout2 I want to open the Activity2. I have already search for the onClick in Recycler View and I have found very useful this. But how can I get the position of the arrayList in order to pass it via Intent in the activity1 or activity2?
Try getAdapterPosition() from inside the view holder so that you may get the adapter position of the click the user made.
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public ViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View v) {
Toast.makeText(context, String.valueOf(getAdapterPosition()), Toast.LENGTH_SHORT).show();
}
}
For more in getAdapterPosition() follow this link
Try this
public class ClosetListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
ClosetListAdapter (CallBack callback){
this.callback = callback
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder baseholder, int position) {
ViewHolder holder = (ViewHolder) baseholder;
holder.setPosition(position);
holder.name.setText(product.getName());
}
static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public TextView name = null;
private OnProductClickListener onProductClickListener;
public ViewHolder(View itemView, OnProductClickListener onClickListener) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.item_name);
itemView.setOnClickListener(this)
}
public void setProdcut(Product product) {
this.product = product;
}
#Override
public void onClick(View v) {
if (callback!= null) {
callback.itemClicked(pos);
}
}
public void setPosition(int position){
this.pos = position;
}
}
interface CallBack {
void itemClicked(int position);
}
}
I've also faced the same problem.
I wanted to find of the position of the clicked/selected item of the RecyclerView() and perform some specific operations on that particular item.
getAdapterPosition() method works like a charm for these kind of stuff. I found this method after a day of long research and after trying numerous other methods.
int position = getAdapterPosition();
Toast.makeText(this, "Position is: "+position, Toast.LENGTH_SHORT).show();
You do not have to use any extra method. Just create a global variable named 'position' and initialize it with getAdapterPosition() in any of the major method of the adapter (class or similar).
Here is a brief documentation from this link.
getAdapterPosition
added in version 22.1.0
int getAdapterPosition ()
Returns the Adapter position of the item represented by this ViewHolder.
Note that this might be different than the getLayoutPosition() if there are pending adapter updates but a new layout pass has not happened yet.
RecyclerView does not handle any adapter updates until the next layout traversal. This may create temporary inconsistencies between what user sees on the screen and what adapter contents have. This inconsistency is not important since it will be less than 16ms but it might be a problem if you want to use ViewHolder position to access the adapter. Sometimes, you may need to get the exact adapter position to do some actions in response to user events. In that case, you should use this method which will calculate the Adapter position of the ViewHolder.
Happy to help. Feel free to ask doubts.

RecyclerView set views to be recycled

in my app, I'm implementing a recyclerview. My dataset for this recyclerview will have varying sizes according to the options that I set for the data to be displayed on the recyclerview.
One of the actions that I take with my recyclerview is to "expand" an item when a click is done on it, displaying further options in it. When pressing on this "expanded" item, I perform the action of "closing" it. Also, there can only be on "expanded" item at maximum at any moment.
The thing is that I understand that recyclerview recycles its row-views when they get out of sight for improved performance. However, because I am trying to have only one "expanded" item at a time, this recycling messes it up quite a lot.
What happens right now is that when I "expand", say the item related to position 1 of my dataset, as shown in the image below.
When I scroll down, I will see that the rowview for this item being recycled at a random chance since I will see this "expanded" view on items that I have not set to be "expanded", as shown in the image below.
And of course, when this happens, then when I scroll back to the item that I have selected to "expand", it will be "closed" as you would have expected.
So I have been thinking that I could resolve this problem by setting the possible number of views to be something like 80% of my dataset size will decrease the possibility of this problem occurring while still reduced, but enjoy some improved performance.
Another solution I thought about was disabling this "expanded" view from being recycled for other views and when this "expanded" item's position comes into screen, it gets bounded to this specific view. I thought of this solution after seeing that there is a concept of "scrap" and "recycle" for recyclerview, but I am not so sure if this method is even possible because I think I have only vaguely understood this side of recyclerview.
That being said, my question is are there ways for me to set the number of views to be recycled for a recycled view? Or even better, having one view from being recycled for items other than the "expanded" item?
Thanks in advance.
EDIT:
here's my (I know it's very messy I'm sorry...) code for my adapter:
public class DrinkMenuItem extends RecyclerView.Adapter<DrinkMenuItem.ViewHolder> {
private Context context;
private ViewGroup parent;
private ArrayList<Drink> menu;
private ArrayList<DrinkSelected> selected;
private DrinkMenuBasketItem selectedAdapter;
public int expanded = -1;
public boolean expandedVisible = false;
private DrinkMenuDropdownItem dropdownAdapter;
public static class ViewHolder extends RecyclerView.ViewHolder {
public RelativeLayout layout;
public TextView name, price;
public ListView dropdown;
public RelativeLayout basket;
public boolean tabbed = false;
public ViewHolder(View itemView) {
super(itemView);
layout = (RelativeLayout)itemView.findViewById(R.id.drink_menu_layout);
name = (TextView)itemView.findViewById(R.id.drink_menu_name);
price = (TextView)itemView.findViewById(R.id.drink_menu_price);
dropdown = (ListView)itemView.findViewById(R.id.drink_menu_dropdown_list);
basket = (RelativeLayout)itemView.findViewById(R.id.drink_menu_basket_button);
}
}
public DrinkMenuItem(Context context, ArrayList<Drink> menu, ArrayList<DrinkSelected> selected, DrinkMenuBasketItem selectedAdapter) {
this.context = context;
this.menu = menu;
this.selected = selected;
this.selectedAdapter = selectedAdapter;
this.dropdownAdapter = null;
}
public void updateDropdown(int requestedOption, int responsedOptionitem) {
dropdownAdapter.updateSelectedOption(requestedOption, responsedOptionitem);
notifyDataSetChanged();
}
// Create new views (invoked by the layout manager)
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
this.parent = parent;
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_drink_menu, parent, false);
return new ViewHolder(itemView);
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Drink d = menu.get(position);
holder.name.setText(d.getName());
holder.price.setText(d.getPrice() + d.totalAdditionalPrice() + "원");
if(position == expanded) {
//delete dropdown
holder.dropdown.setAdapter(null);
menu.get(position).returnToUnselected();
holder.price.setText(menu.get(position).getPrice() + "원");
setListViewHeight(holder.dropdown);
//reset dropdown-related stuff
holder.tabbed = false;
holder.basket.setVisibility(View.GONE);
}
setOnClickEvent(holder, position, parent);
}
private void setOnClickEvent(final ViewHolder holder, final int position, final ViewGroup parent) {
holder.layout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (!holder.tabbed) {
//close dropdown of expanded view
if(expanded != -1) notifyItemChanged(expanded);
//make dropdown
dropdownAdapter = new DrinkMenuDropdownItem(context, menu, position, holder.price);
holder.dropdown.setAdapter(dropdownAdapter);
setListViewHeight(holder.dropdown);
//set dropdown-related stuff
holder.tabbed = true;
holder.basket.setVisibility(View.VISIBLE);
expanded = position;
expandedVisible = true;
((RecyclerView) parent).smoothScrollToPosition(position);
} else {
//delete dropdown
holder.dropdown.setAdapter(null);
menu.get(position).returnToUnselected();
holder.price.setText(menu.get(position).getPrice() + "원");
setListViewHeight(holder.dropdown);
expanded = -1;
expandedVisible = false;
//reset dropdown-related stuff
holder.tabbed = false;
holder.basket.setVisibility(View.GONE);
}
}
});
...
}
}
I have exactly same issue in my project. I did not succeed solving it with recyclerView. But the solution would be one of the following:
Create an expandableListView instead of recyclerView and everything will work great.
Create a ScrollView, and put a LinearLayout with android:orientation="vertical". Then, create a loop and insert all your custom views, and set click listener where you wish to expand.
Use an Expandable RecyclerView Library like one of these:
https://github.com/h6ah4i/android-advancedrecyclerview
https://github.com/bignerdranch/expandable-recycler-view

How to Keep RecyclerView's Buttons Clicked Respectfully - Android

I have a RecyclerView that is populated with CardViews. On each of the CardViews there is a button, which up votes the post.
Here is what the button looks when it is not pressed,
Here is what the button looks when it is pressed,
My code works for achieving this but I have a problem since it is a RecyclerView. When I scroll down the posts the RecyclerView recycles the previous posts that have been up voted. So a post will show that it was up voted even though a user never up voted it.
How can I keep the buttons pressed respectfully for each CardView?
This is my Adapter
public class DiscoverRecyclerAdapter
extends RecyclerView.Adapter<DiscoverRecyclerAdapter.ViewHolder> {
private String[] mDataset;
Typeface customFont;
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTitle;
public TextView mVoterCounter;
public ImageButton mVoterButton;
public ViewHolder(android.support.v7.widget.CardView v) {
super(v);
mTitle = (TextView) v.findViewById(R.id.title);
mVoterCounter = (TextView) v.findViewById(R.id.voter_counter);
//Initialize voter button
mVoterButton = (ImageButton)v.findViewById(R.id.voter);
mVoterButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mVoterButton.setImageResource(R.drawable.ic_voter_pressed);
}
});
}
}
// Provide a suitable constructor (depends on the kind of dataset)
public DiscoverRecyclerAdapter(String[] myDataset, Typeface passedFont) {
mDataset = myDataset;
customFont = passedFont;
}
// Create new views (invoked by the layout manager)
#Override
public DiscoverRecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// create a new view
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_discover, parent, false);
// set the view's size, margins, paddings and layout parameters
return new ViewHolder((android.support.v7.widget.CardView)v);
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.mTitle.setText(mDataset[position]);
holder.mTitle.setTypeface(customFont);
holder.mVoterCounter.setTypeface(customFont);
}
// Return the size of your dataset (invoked by the layout manager)
#Override
public int getItemCount() {
return mDataset.length;
}
}
along with mDataset you will also need a boolean array say mIsSelected
now size of this will be equal to size of array mDataSet or create class if you want.
Then in onBindViewHolder do as
if(mIsSelected[position]
mVoterButton.setImageResource(R.drawable.ic_voter_pressed);
else
mVoterButton.setImageResource(R.drawable.ic_voter_unpressed);
and move button onclick inside onBindViewHolder as below
holder.mVoterButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mVoterButton.setImageResource(R.drawable.ic_voter_pressed);
mIsSelected[position] = true;
}
});
You need to clear the previous rows view of all previous data at the beginning of the onBindViewHolder.
In your case it seems you need to clear all the visibility params of the view components to whatever you deem to be the default. After that go ahead and populate the card with the data.
Being that your passed in dataset is only a string you will need to either make a call to your own API to get the up-vote count / status. Or change your dataset to a custom object array that tracks all of the different components you need to setup and record the data of each card.
In short: As the views get recycled you need to clean them up before re-use.

Categories

Resources