I have a RecylcerView which loads it's data from an external server. And each of my items have a TextView. My problem is that some of my items don't have their related values for TextView when both English and Persian texts are used, and it shows nothing in the TextView. But it perfectly works fine when just one language, either English or Persian is used. By one language I mean all of the data in adapter be in one language not just one item.
Besides, my adapter gets it's data from external server, so I can't store my texts in /res folder. In addition, getText() in my code always shows the correct value.
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
if(viewHolder instanceof ItemViewHolder) {
final CategoryItem nature = mItems.get(i);
String itemName = mItems.get(i).getName();
//This line of code doesn't work for some items when both English and Arabic texts are used
((ItemViewHolder) viewHolder).dsc.setText(itemName);
//This line of code shows the correct values at all times
System.out.println("itemName " + ((ItemViewHolder) viewHolder).dsc.getText());
}else{
((ProgressViewHolder)viewHolder).progressBar.setIndeterminate(true);
}
}
As shown in the picture, there are some items which have empty TextView. The three values for empty items of this picture should be: bb, سلام , bbسلام
A short example of multiple viewholders.
May be after this your code will be clearer
public class yourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
class ItemViewHolder extends RecyclerView.ViewHolder {
...
}
class ProgressViewHolder extends RecyclerView.ViewHolder {
...
}
#Override
public int getItemViewType(int position) {
//here is the place where viewholder may choose
return position;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 0: return new ItemViewHolder(...);
case 1: return new ProgressViewHolder(...);
...
}
}
}
After researching I figured out that my TextViews become visible after pressing Request Layout button in Hierarchy Viewer tool in Android Studio. So, I added one line to my code after setText()
((ItemViewHolder) viewHolder).dsc.requestLayout();
And after that, everything worked fine.
I would be really thankful If anyone could explain the reason.
Related
I am trying to make a text change when a button located along with the text (layoutPasswd) in recycler view and to change it back if the button is again pressed.Like a password hiding button. The values to the adapter is from a static class object as arraylist. The problem occurring now is that the value for all the items (only for layoutPasswd) in recycler view is same.
public void onBindViewHolder(#NonNull final viewHolder holder, int position) {
holder.layoutUName.setText(users.get(position).getUserName());
pos = position;
holder.layoutPasswd.setText("********");
holder.btnViewChanger.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (holder.view1) {
holder.layoutPasswd.setText(users.get(pos).getPasswd());
holder.btnViewChanger.setText("hide");
holder.view1 = false;
} else {
holder.layoutPasswd.setText("********");;
holder.btnViewChanger.setText("Show");
holder.view1 = true;
}
}
});
You cannot rely on the ViewHolders or Views in a RecyclerView to hold any state, because they are recycled. Every time a view scrolls onto the screen, first it calls your onBindViewHolder function to update the contents of that ViewHolder to match the data.
Any configuration you set on the views or the ViewHolder instance in onBindViewHolder cannot be relied on to stay the same if the view scrolls off the screen, because the original ViewHolder might be recycled to be used for some other data, and when it scrolls back on screen, you might be looking at some other view that has been recycled from other data that just scrolled off the screen.
So if your views have configuration that you want to "stick", you have to back it up when you change it, and restore it in onBindViewHolder. The way you accomplish this will depend on how you are managing the data that you pass to the adapter.
If you can modify your User class, you can add a Boolean to it that stores whether it should show the password. Then in your onBindViewHolder, you restore the state based on this Boolean. And you also update this Boolean when the state changes.
I also updated the way the click listener works to simplify it for toggling. I removed the pos = position line, because almost certainly that is not something you should be doing.
public void onBindViewHolder(#NonNull final viewHolder holder, int position) {
final User user = users.get(position)
holder.layoutUName.setText(user.getUserName());
holder.layoutPasswd.setText(user.isShowPassword() ? user.getPasswd() : "********");
holder.btnViewChanger.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
user.setShowPassword(!user.isShowPassword());
holder.layoutPasswd.setText(user.isShowPassword() ? user.getPasswd() : "********");
holder.btnViewChanger.setText(user.isShowPassword() ? "hide" : "show");
}
});
// ...
}
If you cannot modify the User class, this is more complicated. Then the adapter should have its own ArrayList<Boolean> to store the state by position index, but you need to keep this list at least as long as the data that is bound, and reset everything to false if the whole list of data is refreshed.
I have Recycler View with a lot of items in. What I want to do is to change the text in TextView inside item that was clicked. I did it in that way:
wordList.set(position, newWord);
MyProgressActivityAdapter newAdapter = new MyProgressActivityAdapter(wordList, this);
newAdapter.notifyItemChanged(position);
recyclerView.setAdapter(newAdapter);
And everything works fine except of the fact that the screen goes to the top every time I click item. What can I do to avoid that?
You should use the payload version of notifyItemChanged, here is a simple example for you to get the hang of it:
adapter.notifyItemChanged(position, "updateText");
And then in your RecyclerAdapter override the payload version of onBindViewHolder:
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position, #NonNull List payloads) {
if (payloads.isEmpty()) onBindViewHolder(holder, position);
else if ("updateText".equals(payloads.get(0))) {
if (holder instanceof YourViewHolder) {
((YourViewHolder) holder).textView.setText(dataProvider.get(position).getNewText());
}
}
}
Note that this approach prevents RecyclerView from creating a new ViewHolder and then binding your data, so you should just call the notifyItemChanged without resetting the adapter and so.
notifyItemChanged(position) should work if you handle it correctly. Try to handle this inside onBindViewHolder like below:
override fun onBindViewHolder(holder: RecyclerHolder, position: Int) {
holder.itemView.text_view.text = items[position]
holder.itemView.button.setOnClickListener {
items[position] = "New Text"
notifyItemChanged(position)
}
}
This question already has answers here:
How to create RecyclerView with multiple view types
(23 answers)
Closed 3 years ago.
I want to display in same recycle-view which is some post have image and some post does not have images.
I can retrieve all the post with image and non-image,
but i want to change the size of the post when the user only post text(no image).
I expect the output like twitter feed..some post with image and without image have their own size.
Simple way to achieve this scenario is, All you have to do is create a view with both image and text, in recycler adapter check if image data is available make visibility of image visible else Image visibility gone.
Second Approach for this to make multiple view for RecyclerView.
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Log.d(TAG, "onBindViewHolder called");
ContentItem item = mContentItems.get(position);
if(item.getName()!=null){
holder.textName.setVisibility(View.Visible);
holder.textName.setText(item.getName());
}else{
holder.textName.setVisibility(View.GONE);
}
if(item.getPreviewImageDefault()!=null){
holder.imageIcon.setVisibility(View.Visible)
Picasso.with(mContext).load("file://" + item.getPreviewImageDefault()).into(holder.imageIcon);
}else{
holder.imageIcon.setVisibility(View.GONE)
}
}
Another possible solution is create 2 xml layouts and use ViewType in your RecyclerView.
look this How to create RecyclerView with multiple view type?
If you want to hide the image when it is ic_launcher you could do that (suppposing that data.getImage() returns the id of the drawable as integer):
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
if(mItems!=null){
AdapterData data = mItems.get(i);
viewHolder.text.setText(data.getText());
viewHolder.image.setImageResource(data.getImage());
if(TextUtils.isEmpty(data.getText())){
viewHolder.text.setVisibility(View.GONE);
}else{
viewHolder.text.setVisibility(View.VISIBLE);
}
if(data.getImage()==R.drawable.ic_launcher){
viewHolder.image.setVisibility(View.GONE);
}else{
viewHolder.image.setVisibility(View.VISIBLE);
}
}
}
One possible solution, like some people have already said, is to hide/show the ImageView.
You could do that in the ViewHolder that you use for your RecyclerView.
class OptionalImageViewHolder extends RecyclerView.ViewHolder {
private ImageView image;
private TextView text;
// any other views you have
public OptionalImageViewHolder(View itemView) {
super(itemView);
image = itemView.findViewById(R.id.yourImageViewIdHere);
text = itemView.findViewById(R.id.yourTextViewIdHere);
// same for any other views you have
}
public void bindView(Tweet tweet) {
// This is where the magic happens
// Note: I make the assumption that you have a class called "Tweet"
// that has a field for "text", a field for "image" (that can be
// null if there's no image), and any other necessary fields.
text.setText(tweet.getTweetText());
if (tweet.hasImage() /* function that returns whether or not there is an image */) {
image.setVisibility(View.VISIBLE);
image.setImageBitmap(tweet.getImage()); // or however you are setting the image
} else {
// else just make the image invisible
image.setVisibility(View.GONE);
}
}
}
Hopefully this gives you an idea.
RecyclerView supports different viewTypes (layouts) which is the proper way in such scenario. E.g.,
class MyAdapter : RecyclerView.Adapter<MyViewHolder>() {
override fun getViewTypes(position:Int) =
if (mydata[position].hasImage) return R.layout.mylayout_with_image
else R.layout.mylayout_no_image;
override fun onCreateViewHolder(viewType:Int, parent:ViewGroup) : MyViewHolder =
// here viewType = layout id
MyViewHolder(layoutInflater.inflate(viewType, parent))
override fun onBindViewHolder(viewHolder:MyViewHolder, position:Int) {
// guaranteed viewHolder.itemView is the view you want for that position
}
}
In my onBindViewHolder of my RecyclerView.Adapter<SearchAdapter.ViewHolder> when user clicks on cardview a button becomes visible. But when I'm scrolling recyclerview some other items buttons are shown as visible too. Why is this happening?
this is my code:
#Override
public void onBindViewHolder(final ViewHolder viewHolder, final int position) {
viewHolder.card.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (viewHolder.content_layout.getVisibility() == View.VISIBLE) {
viewHolder.content_layout.setVisibility(View.GONE);
viewHolder.address.setMaxLines(2);
viewHolder.attribute.setMaxLines(2);
} else {
viewHolder.content_layout.setVisibility(View.VISIBLE);
viewHolder.address.setMaxLines(8);
viewHolder.attribute.setMaxLines(8);
}
}
});
...
}
Once you start scrolling down the list your views get recycled. This means a previously inflated ViewHolder (some that gets created in onCreateViewHolder) is reused.
So what you have to do is to remember the clicked positions (e.g. via a SparseBooleanArray) and check in onBindViewHolder whether the view should be visible (previously clicked) or not.
You can find a basic usage example of the SparseBooleanArray in this StackOverflow post
The 'other' visible items buttons are the ones using the same viewholder that was modified in the callback. So because viewholders (and views) are recycled :
They should only store information that can be retrieved each time the viewholder is bound to a position.
Anything that may be changed in the views state should be refreshed in onBindViewHolder()
In your case you should store the 'is selected' somewhere else and reset the visibility and maxlines in onBindViewHolder() (not only in the callback)
Good idea is to make a class object with all data you need for one item in recycler view, also add there one boolean isItemWasClicked and inside onBindViewHolder() check this boolean and make buttons visible or not.
For example:
public class OneItemOfList{
int priceToDisplay;
String name;
String date;
boolean wasClicked;
}
public class YourAdapter extends RecyclerView.Adapter<OneItemOfList.ViewHolder> {
ArrayList<OneItemOfList> items;
...
#Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
viewHolder.view.setText(items.get(position).name);
if (items.get(position).wasClicked)
viewHolder.button.setVisible(View.VISIBLE);
else
viewHolder.button.setVisible(View.GONE);
viewHolder.view2.setOnClickListener(...
OnClick(...){
items.get(position).wasClicked = !items.get(position).wasClicked;
});
}
...
}
create an array for example Boolean array, and when each position clicked, set true in same position of array. and in onBindViewHolder check if that array[position] is true set that item visible if.
I'm new to this android/java stuff. I have this onItemSelected that will toast what has been selected in the spinner. I want to have the string resumeTableName accessible throughout my entire class that has the value of the selected spinner object. Right now it toasts the selected value however at other places in my class the resumeTableName remains null. I thought the public modifier would make it visible. How do I make this visible, do I use some sort of return?
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position,
long id) {
// On selecting a spinner item
String resumeTableName = parent.getItemAtPosition(position).toString();
// Showing selected spinner item
Toast.makeText(parent.getContext(), resumeTableName,
Toast.LENGTH_LONG).show();
}
Use a static String resumeTableName; declared in your class and
in your onItemSelected write
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position,
long id) {
// On selecting a spinner item
resumeTableName = parent.getItemAtPosition(position).toString();
// Showing selected spinner item
Toast.makeText(parent.getContext(), resumeTableName,
Toast.LENGTH_LONG).show();
}
In this way the value of resumeTableName updated by the method will be the same for the whole class.
If You are new in Android programming there is a nice solution for the problems with visibility of variables.
You have to create class in your package:
public class GlobalVar extends Application{
private int dummy1;
public int getDummy1() {
return dummy1;
}
public void setDummy1(int dummy1) {
this.dummy1 = dummy1;
}
}
then in any place in Your application You can get/set this data, by using:
GlobalVar gV = (GlobalVar)GetApplicationContext();
and then You just modify them by using Getters/Setters or any public methods from this class. I think it's very good solution for set of variables that You use often from different places of code.