android - RecyclerView items: how to save properties of Views in every row? - android

My problem is to understand how RecyclerView works.. I have RecyclerView with a little bit complicated item in every row, but the main thing is that Item has a child ImageView and LinearLayout. I want to press ImageView and set Visibility of LinearLayout to GONE or VISIBLE and rotate my ImageView. I tried to do this in my onBindViewHolder:
holder.mIVExpandBtn.setOnClickListener(new OnClickListener() {
boolean isOpen = false;
#Override
public void onClick(View v) {
if (isOpen) {
CounterListAdapter.this.notifyItemChanged(position);
holder.mLLDetails.setVisibility(GONE);
holder.mDivider.setVisibility(VISIBLE);
holder.setArrowUp(false);
isOpen = false;
counterItem.setDetailsOpened(false);
} else {
holder.mLLDetails.setVisibility(VISIBLE);
holder.mDivider.setVisibility(GONE);
holder.setArrowUp(true);
isOpen = true;
counterItem.setDetailsOpened(true);
}
}
});
And I have some problems here.
I have a boolean variable inside OnClickListener, I know its wrong, so it changes only one time when I expand my LinearLayout. If I make this boolean global variable, if I expand one row of RecyclerView isOpen = true for any other item and it doesn't expand itself when I click on ImageView.. Where should I place this boolean?
And the second question - how can I save the state of my RecyclerView rows on screen rotation? For example I expanded one of my rows, LinearLayout.setVisibility(VISIBLE), change screen orientation and its closed.

For your first problem, you should put your boolean variable where you also define your views, i.e., inside your ViewHolder, ir order that onClick you call the boolean this way
if(holder.isOpen)
In this way you keep the reference of each boolean to each row.
For your second problem, the solution is pretty simple. In your manifest, in the activity where you have your RecyclerView, define the following:
android:configChanges="keyboardHidden|orientation|screenSize"
This prevents your activity from being recreated on configuration change in case you rotate the screen, so the activity will keep it's state and your RecyclerView will therefor not be recreated along with your adapter.
Notice that this means that, if your activity is not recreated, onPause, onStop, etc, will not run. This is only for screen rotation, your activity will still run the method onConfigurationChanged() which is where you should define any changes you need in case the screen rotates.

You better put the OnClickListener in the Holder class, something like this:
private class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
#Override
public void onClick(View view) {
....
}
About how to save state I think all things that define the rows should be saved in the array you pass to the adapter contructor, you can add fields in the array item object that save VISIBILITY state of the row views.
On screen rotation then two options:
1 - as #Ricardo said avoiding Activity recreation
2 - onSaveInstanceState / onRestoreInstanceStates save/restore the array that define the rows .. my prefered method for that is to use JSON and put it in a String that can be saved/restored in the Bundle.

Related

How to set layout of any other item in RecyclerView invisible/gone when click current item

i need help.
I wanted to change button visibility in any other item inside recyclerview to dissapear, so other button except item that i selected become invisible ...
The trigger is when item get clicked/selected then any other item's button goes invisible/gone except the one i clicked... how do i do that? im using Kotlin btw.
Here is where i want to implement it inside "if (!b)" (if possible) in onBindViewHolder:
holder.mainLayout.setOnClickListener {
if (!b){
b = true
holder.addsLayout.isVisible = true
} else {
b = false
holder.addsLayout.isVisible = false
}
}
Edit:
Here is my viewholder:
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val itemTitle: TextView = itemView.findViewById(R.id.itemTitleRecordLocal)
val itemDelete: Button = itemView.findViewById(R.id.recordlocalDelete)
val itemTranscribe: Button = itemView.findViewById(R.id.transcribelocalTxt)
val mainLayout: RelativeLayout = itemView.findViewById(R.id.recordlocalTitleDateLayout)
val addsLayout: RelativeLayout = itemView.findViewById(R.id.addsRecordLocalLayout)
}
I just updated my code earlier, so its not button anymore but layout, but i think its similar bcuz i just need to change the visibility.
itemDelete and itemTranscribe are buttons that i want to hide, and i put it in addsLayout.
mainLayout is the item where i need to click to be selected.
As per your code, I understand that you are passing any data class to the recycler view adapter of type suppose myData
Here is what you can do:
Take one boolean variable in your data class item, say isSelected, default value will be false.
Then whenever you get click of your mainLayout, change the value of that boolean.
Check condition according to that boolean and manage visibility according to that.
Also, best practice is you manage your clickevents in activity/fragment. So my suggestion is give callback of click of mainLayout to the activity/fragment and perform the next operations there only.
After that, in activity/fragment you need to make for loop and make isSelected false for all data except the one you selected.
For example in my case I have one music player recyclerview and need to change icon if one is being played then all others should be pause. So I need to change icon based on that condition. My data class contain one variable isPlaying.
In adapter:
// Inside view holder
if (data.isPlaying) {
// set image of play
} else {
// set image of pause
}
In on click, I have make one mutable live data and observe that in activity/fragment and get callback of click there.
Inside activity/fragment:
adapter.getListItems().forEach {
it.isPlaying = false
}
data.isPlaying = true
notifyDataSetChanged()

How to maintain Android GridView position in all cases?

I'm new to Android and I'm trying to do the following task on my school project:
I have a grid view of movies which the user can scroll endlessly.
When the app starts I fetch the first 20 movies and each time the user scrolls to the bottom of the grid I execute an AsyncTask to fetch 20 more movies and add them to the Adapter.
When the user clicks on a movie he goes to a new child activity to see the movie details.
I'm having troubles maintaining the GridView's scroll position in the following cases:
When the user goes to the details activity and returns to the main activity of the movies.
When the user changes the device orientation.
And when dealing with theses 2 cases I also need to take in consideration that maybe the user scrolled a lot and had 100 movies in the adapter and when he goes back the activity start from the start with only the first 20 movies, so I would be able to scroll to his last position.
Can someone please tell me how can I give the best user experience in my project by not losing the user's scroll position at any case?
I don't know if this is the best practice, but in my case it is.
I decided to set my adapter as a global static variable, in this way I maintain the amount of data loaded via the API, and I don't need to perform a request for every time the user moves between activities.
For maintaining the scroll position I used the onItemClickListener when moving to the details activity and the savedInstanceState when changing orientation.
Here is my code for that:
//Static variables
private static MoviesAdapter mMoviesAdapter;
private static int mGridViewPosition = 0;
//Call this method when user clicks the back button
public static void ClearStaticData(){
mMoviesAdapter.clear();
mMoviesAdapter = null;
}
#Override
public void onSaveInstanceState(Bundle outState) {
int index = mGridView.getFirstVisiblePosition();
outState.putInt(GRID_VIEW_POSITION, index);
super.onSaveInstanceState(outState);
}
#Override
public View onCreateView(...) {
if (mMoviesAdapter == null) {
mMoviesAdapter = new MoviesAdapter(...);
} else {
RestoreGridPosition();
}
}
private void RestoreGridPosition(){
if(mGridViewPosition > 0 && mMoviesAdapter.getCount() >= mGridViewPosition)
mGridView.setSelection(mGridViewPosition);
}
Since I fill my adapter via API call, I think this is probably the best solution to save the data and not to perform requests every time.
Try not finishing mainActivity once a gridItem is clicked so when user navigates back to mainActivity (from detailsActivity) he will have all the data that was there before.
You can handle this situation with activity's lifecycle callbacks:
You can get currently visible GridView item's position like this:
int mCurrentPosition = gridview.getFirstVisiblePosition();
When an orientation change is occurring the activity is recreated and going through the following stages:
onSaveInstanceState
onRestoreInstanceState
You can then save the position before orientation change is happening and get it back when its being restored.
Save Your Activity State
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user's current scroll state
savedInstanceState.putInt(STATE_POSITION, mCurrentPosition);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
Restore Your Activity State
public void onRestoreInstanceState(Bundle savedInstanceState) {
// Always call the superclass so it can restore the view hierarchy
super.onRestoreInstanceState(savedInstanceState);
// Restore state members from saved instance
mCurrentPosition = savedInstanceState.getInt(STATE_POSITION);
}
Here once you have the previous position you can move to the desired position in the gridView:
gridview.smoothScrollToPosition(int mCurrentPosition)
This is taken from android docs: Recreating an Activity
Scrolling gridView to position GridView scrolling stackoverflow

Items ImageButton drawable change upon click in ListView seems to change for 2 items at a time

I know this question has been asked before and got an understanding roughly of whats happening but i cant seem to find a solution.
In my Custom List Adapter and inside public View getView(int position, View convertView, ViewGroup parent) { ive setup a click function for the items ImageButton.
final ImageButton bookmark = (ImageButton)
convertView.findViewById(R.id.bookmarkthis);
bookmark.setTag(position);
bookmark.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
bookmark.setImageResource(R.drawable.ic_bookmarked);
bookmark.setTag(R.drawable.ic_bookmarked);
}
});
The Imagebutton is a clear star and when Clicked it changes that same drawable to a yellow star. It works Ok.
Problem is when i click Item in Position 0 Star Icon to make yellow , it also changes for item 8 further down the list not in View Yet. if i click position 1 changes also for position 9 and so on.
I had a look around and researched the issue and even tried a holder for the Imagebutton but no go. Something is preventing the drawable change for the ImageButton to its correct Position only.
Thanks
Solution is to initially Set a flag item in the array for each Item. Then onClick set the flag to true just for that item. Then in the getView its just a case of an if statement to check the flag as the Items get Cycled.
//in array creation
items.setFlag("false");
//in getView as you set Text and what ever get the Flag state
String flag = m.getFlag();
//check the flag state and take action in this case change the icon accordingly
if (Objects.equals(flag, "true")) {
bookmark.setImageResource(R.drawable.ic_bookmarked);
bookmark.setTag(R.drawable.ic_bookmarked);
}
else {
bookmark.setImageResource(R.drawable.ic_bookmark);
}
// and in the click function
#Override
public void onClick(View v) {
bookmark.setImageResource(R.drawable.ic_bookmarked);
m.setFlag("true");
}
You need to reset your bookmark image resource & tag values a non-null convertView is passed in to getView. Your onClick handler is setting them to these values:
bookmark.setImageResource(R.drawable.ic_bookmarked);
bookmark.setTag(R.drawable.ic_bookmarked);
So you'll need to reset them to the default values when showing a new list item.
ListView reuses the views that fall out of range. You don't reset the "bookmark" icon when reusing the views (when you get a non-null convertView). Make sure to always reset all properties of your views to the correct values, and you won't have a problem.

Is there a way to hide specific listview items from the main activity?

Just wondering if what I'm trying to do is possible. So i have a custom adapter for a listview. It contains a textview and two buttons. I would like one of the listview buttons to remain hidden unless a specific button is pressed on the main activity.
So far I have the listview buttons performing their intended function but I have no idea how I would even begin to get what I'm wanting.
Sorry, for clarification, I have one button completely separate from the listview that is just always there. When I press this button I would like to toggle the visibility of a button that is on each listview item all together. The best example of this that I can think of is having a list of items and a button that can toggle off the 1. 2. 3. 4. that comes in front of each item.
Create a method in your adapter for knowing you have clicked the button from your main activity like this
public void buttonIsClicked(){ //in your adapter
buttonhide.setVisibility(visibility?View.VISIBLE:View.GONE);
}
And call this method from your activity on btnclick.
like
yourAdapter.buttonIsClicked();
and call this method for notifying the adapter about the change.
yourAdapter.notifyDatasetChanged().
or
You can use an interface for listening to the clicks in main activity and implement that listener in your adapter
Set visibility gone to the button you want to hide by calling code
buttonhide.setVisibilty(VIEW.GONE);
hide it in oncreate() of your activity and make it shown on the button click event by calling code buttonhide.setVisibilty(VIEW.VISIBLE);
Below is the code
btnView.setVisibility(View.GONE);
yes it is possible to do that.
First have a Model class to back the listview data and keep a flag in that model which indicates whether to show the button in that row's data model. On certain condition change that model's flag and call notifyDataSetChanged() on adapter.
Ex:
class Model{
String label;
boolean showBtn;
}
in adapter's getView()
Model model = list.get(position)
if(model.showBtn){
btn.setVisibility(View.VISIBLE);
}else{
btn.setVisibility(View.GONE);
}
in Activity
disableButton(){
modelList.get(0).setShowBtn(false);
adapter.notifyDataSetChanged();
}
This code will hide button in first row
Add a Boolean value in your dataset which represent the Visibility state of the button.
public class Dataset {
private boolean visible;
public boolean isVisible() {
return this.visible;
}
public void setVisible(boolean visible) {
this.visible = visible;
}
//..more items
}
Then in your getView method of the Adapter check this Boolean value to show/hide the button.
boolean visibility = yourDataset.get(position).isVisible();
yourButton.setVisibility(visibility?View.VISIBLE:View.GONE);
And when the Button outside of your listview is clicked Update your dataset. And call yourAdapter.notifyDatasetChanged().
What you are attempting is: manipulating the visibility of the button declared in the Adapter from the containing activity. Simple, put a controlling variable in the activity and pass it a parameter to adapter.
Boolean mShowButton; //a controlling variable
void onCreate(Bundle savedInstanceState) {
...
mAdapter=new MyAdapter(...,mShowButton);
mButton.setOnClickListener(actionShow );
}
OnClickListener actionShow = new OnClickListener() {
#Override
public void onClick(View button) {
mShowButton=true;
mAdapter.notifyDataSetChanged();
mListView.invalidateViews();
}
};
And do this in your adapter,
Boolean showButton;
public MyAdapter(Context context, List<String> myList, Boolean showButton) {
...
this.showButton=showButton;
}
public View getView(int position, View rowView, ViewGroup parent) {
...
if(showButton){
mButtonTwo.setVisibility(View.GONE);
}else{
mButtonTwo.setVisibility(View.VISIBLE);
}
}

Reset background color in Android ListView

In my QuestionsActivity, I am showing a question and bunch of answers. The question is displayed on TextView and answers are displayed on ListView that is composed of TextViews. There is an ActionButton named "Check Answer" and when it is clicked, it shows the correct answer by changing the background color of the TextView in the ListView.
The background changing code looks like this:
if (allAnswers.get(i).isCorrect())
{
mAnswerList.getChildAt(i).setBackgroundColor
(getResources().getColor(R.color.correct_answer_background));
return;
}
and now there are two Buttons at the footer section of this QuestionsActivity called PreviousQuestionButton and NextQuestionButton and they are basically navigation buttons between questions.
The problem is, when I go to the next question after clicking on "Check Answer" button, the answer background color doesn't go away and remains in the next question answer options. I tried invalidate(), refreshDrawableState() method of ListView but no luck!
This is the method which displays the answers for a given question:
private void showAnswers(int questionLocation)
{
int questionId = mAllQuestions.get(questionLocation).getQuestionId();
List<Answer> answers = mAnswerRepository.getAllByQuestionId(questionId);
mAnswerAdapter.clear();
for (int i = 0; i < answers.size(); i++)
{
mAnswerAdapter.add(mOptionLetters[i] + ". "
+ answers.get(i).getAnswerText());
}
mAnswerAdapter.notifyDataSetChanged();
}
My Question
What I want is that when I click on next or previous buttons, the background color of the correct answer in ListView should disappear so that next and previous question button can show non-selected answer options list to the user. Is there any method which resets ListView to a state which does not have any background applied?
For selected answer option, I am using mAnswerList.clearChoices() in order to unselect but it does not apply for correct answer background color.
Well, to reset the color you can very well hard-reset the adapter by creating a new one. So don't clear and add as that may keep the views in the state they were before. I am not too sure about this since I am not clearing or adding from an adapter, but always creating a new one to fulfill my new needs.
Anyway, another reason why things may not go in the direction you want is that the views may get recycled, since we're talking about a ListView. So if you want to highlight a list item, you should keep in the data model the information about highlight by initializing it to false and if the user selects one set the highlight state to true. I suppose the Answer class has as a minimum the following:
public class Answer {
private String data;
private boolean correct;
public String getData() {
return data;
}
public boolean isCorrect() {
return correct;
}
#Override
public String toString() {
return data;
}
}
So your adapter could look close to this - getView method is the most important to notice (don't forget to set to default background if the answer is incorrect or the adapter should not highlight correct answer):
public class MyAdapter extends ArrayAdapter<Answer> {
private boolean showCorrectAnswer;
private List<Answer> modelAnswers;
public MyAdapter(Context context, List<Answer> answers) {
super(context, android.R.layout.simple_list_item_1, answers);
this.modelAnswers = answers;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
if(showCorrectAnswer && modelAnswers.get(position).isCorrect()) {
view.setBackgroundColor(getContext().getResources().getColor(R.color.correct_answer_background));
} else {
view.setBackgroundColor(getContext().getResources().getColor(R.color.default_background));
}
}
public void setShowCorrectAnswer(boolean showCorrectAnswer) {
this.showCorrectAnswer = showCorrectAnswer;
notifyDataSetChanged();
}
}
What you need to do is to keep a reference to this custom adapter and if you need to highlight the correct answer or not simply call setShowCorrectAnswer(true / false);. It will trigger a redraw and in the getView() it will decide what to do based on adapter state and correct answer.
Hope it make sense ... I wrote all this while drinking a beer :)
Basically, I agree with the answer from payeli - you should change the background of selected answer in the "next/previous question" button.
But then there is this question:
"Is there any method which resets ListView to a state which does not have any background applied?"
Answer to this (as far as I know) is: not directly. But there are two workarounds.
If you call notfiyDataSetChange, not all views are completely redrawn. If possible, just the appropriate values are changed. It's mainly for performance reasons. (Imagine having 1000 contacts with images and names dumped and redrawn)
So how can you deal with this? (Other then reseting the background in the onClick method) Since you said these items are answers for a question, I assume you are not concerned about performance because there won't be too many of them.
Then you can simply:
A) Create new instance of Adapter instead of changing data in the old one. When you switch adapters, all views in list are removed, so, no recycling can happen in the new adapter.
B) Create custom Adapter, override getView method and make sure every time view is requested, you return a new view, so no recycling can happen.
Again: this really isn't "performance friendly" and should not be used with big lists with a lot of items!
If you want to know more, feel free to ask in comments or read the reference of Adapter class, especially the parameter convertView of getView method. (http://developer.android.com/reference/android/widget/Adapter.html)
As per my understanding U need to change color of text view in one of the list view row i.e row containing correct answer.
If this is your problem then On Click of button simply clear list view and recreate list view. Not when list view is recreated then check out position of row which contains correct answer. After getting row position simply change color of text view.
For Ex: In your Adapter class check as fallows in your getView();
for ( int i = 0; i < position.length ; i++ )
{
if(position == your_required_position){
{
textview.setColor(Color.BLUE);
}else{
textview.setColor(Color.BLACK);
}
}
In the onClickListener of "Check Answer" button, you need to reset the color:
void onClick (View v){
..............
mAnswerList.getChildAt(currentQuestion).setBackgroundColor
(getResources().getColor(R.color.default_answer_background));
}
you can save the default background somewhere, and when you press next/previous question button you should apply that color. Example:
TypedArray array = getTheme().obtainStyledAttributes(new int[] {
android.R.attr.colorBackground,
android.R.attr.textColorPrimary,
});
int backgroundColor = array.getColor(0, 0xFF00FF);
int textColor = array.getColor(1, 0xFF00FF);
array.recycle();
I found this code online to get background and text color (you can just keep the background part), when the onClick activates just set the background of your view to "backgroundColor"

Categories

Resources