I'm displaying a list alphabetically in a RecyclerView,
each letter group should have a letter displayed to the left side, once, at the top of each group.
The divider line is rendered in an ItemDecorator.
I'm trying to solve this in onBindViewHolder.
The initial layout works fine. As I scroll from the top to bottom everthing is as expected.
But when I scroll back up the the initial/capital letter goes missing or it gets reordered.
Scrolling down is showing the initial letter conditionally as expected:
After scrolling back up k is missing in this example, :
public void onBindViewHolder(WordItemViewHolder wordItemViewHolder, final int position) {
final WordModel wordModel = wordModels.get(position);
wordItemViewHolder.textView.setText(wordModel.getWord());
String word = wordModel.getWord();
String currentFirstLetter = word.substring(0,1);
if(maxListRendered <= position){
if(!previousLetter.contentEquals(currentFirstLetter) || position == 0){
wordItemViewHolder.initialView.setText(currentFirstLetter.toUpperCase());
wordItemViewHolder.initialView.setVisibility(View.VISIBLE);
previousLetter = currentFirstLetter;
}else{
wordItemViewHolder.initialView.setVisibility(View.INVISIBLE);
}
}
maxListRendered++;//initialised as 0 in attempt to track calls to onBindViewHolder
}
Any help appreciated, thank you.
Get rid of maxListRendered and previousLetter to begin with - that's dangerous to do and will cause issues.
Instead check the index above the current. Also instead of showing and hiding elements, I'd recommend having different view types by overriding getItemViewType - that makes them being recycled separately so they can have different views.
Related
I am using RecyclerView to list Items and In each single list displaying an image which will be Visible/Gone dynamically. I am using View.GONE to hide the view.
In a condition where the image should hide is not working always. It is still showing in screen,and also in debug mode i have checked that and when getting the
image.getVisiblity() it is giving me int value "8" which means the view is Gone,But still i can see that image in that list.
It happens only sometimes.
And i tried to use View.INVISIBLE and it is working all the time but it is taking the space in layout which is as expected
I am using sparseArray to store all the holders classes.I have written a method in Adapter and calling this from activity.I am trying to hide the replayIcon view
public void handleReplayButton(int pos,Boolean isDisplay) {
Holder holder = holderSparseArray.get(pos);
if(holder != null) {
if (isDisplay != null && isDisplay == true) {
holder.playIcon.setVisibility(View.GONE);
holder.pauseIcon.setVisibility(View.GONE);
holder.replayIcon.setVisibility(View.VISIBLE);
} else if(isDisplay != null && isDisplay == false) {
holder.playIcon.setVisibility(View.VISIBLE);
holder.pauseIcon.setVisibility(View.GONE);
holder.replayIcon.setVisibility(View.GONE);
} else {
holder.playIcon.setVisibility(View.GONE);
holder.pauseIcon.setVisibility(View.VISIBLE);
holder.replayIcon.setVisibility(View.GONE);
}
}
}
Here it is going to the last else statement what i want and it is setting the view to GONE.and when i call holder.replayIcon.getVisibility() it is giving me int 8 but,still i can see the icon
Try calling invisible at the end of one statement which makes it visible and vice versa.
Or
You can also try to put notifydatasetchanged().
You will have to call notifyDataSetChanged() to refresh the list in the recycler view.
But since you have to remove an item, you can also use notifyItemRemoved
Also, if you are using setVisibility() method to HIDE the view, then make sure you also set the view as VISIBLE for valid items, because the items are reused in a recycler view.
For more : https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter
If you will call notifyDataSetChanged() - it will update all the items in the list.
Don't do that if you need to update special items by index because it will take a lot of memory to redraw the all views.
Instead like the guys wrote before you should use notifyItemChanged(), notifyItemInserted() or notifyItemRemoved().
If you want to update couple views use can use notifyItemRangeChanged(), notifyItemRangeRemoved() or notifyItemRangeInserted().
You can read more about it here
Also there is one way to it. You can use DiffUtils callbacks.
Pretty good approach that work with animation already.
DiffUtils Calbacks
I have a tour list in android and I am trying to implementing add to favorites.
Favorite works for the list that is added in myTours but doesn't work for tours from search list.
This is the code:
private List<Tour> tourList;
holder.imgFavourite.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(tourList.get(position).getFav().equalsIgnoreCase("0")) {
tourList.get(position).setFav("1");
// listener.onFavourited(tourList.get(position), true);
holder.imgFavourite.setImageResource(R.drawable.faved);
} else {
tourList.get(position).setFav("0");
// listener.onFavourited(tourList.get(position), false);
holder.imgFavourite.setImageResource(R.drawable.not_faved);
}
}
});
Here when I click on Fav icon gets changed to faved. But if I search for different category and again come back to that category state doesn't persist.
Any help would be appreciated.
Listview/RecyclerView reuses views. If your list has 20 items and 4 are visible at one time, as you scroll, the views are reused to show the new visible views to save memory. So if you alter the view at position 5 and scroll down and scroll back up to position 5, the view that you changed in position 5 is not the same view that you are seeing after you scroll back up. Hence the view changes
To fix this, maintain a global variable in the adapter which stores the favorite position and in the onBindViewHolder, add a condition like
if(position = favPosition)
<Change to fav view>
else
<Change to Normal View>
The else condition is really important or else multiple views will have the Fav view
For recycler view always consider both cases only one case cause your list to change behaviour(like set same image for other list item because it reuses the cell for your view).the below mistake you are doing.
In bindviewholder you are getting data from model class and based on that you set resources for imageview.which is good.
But the problem is there no case for failing of the condition so you are losing the state and if you scroll down and again come to this item you lost your state or face behaviour that goes against your requirement.
So simply provide case for not matching your condition.
I am writing an android app where I am using a grid view to display some items. I want to give users a configurable view where they can change the no of columns on the activity by clicking floating action button. I am changing the column no using
gridView.setNumColumns(selectedColumnNo);
This is working fine But the problem is if a user changes no of column after some scrolling the First Visible Position is set to the first item of the array list, so the user has to scroll the view again. Can someone please tell me where I am doing wrong. Or Is this the proper way to do this or should I use a different approach.
A code snippets will be helpful
Thanks.
Update::
currently I am using the bellow snippets
findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int firstPosition = gv.getFirstVisiblePosition();
if(gv.getNumColumns()==2)
{
gv.setNumColumns(1);
gv.setSelection(firstPosition);
}
else {
gv.setNumColumns(2);
gv.setSelection(firstPosition);
}
}
});
Now the problem is on every 4th switch grid view is showing the first element of the arraylist
Right before you call setNumColumns(), save the GridView's first visible position:
int firstPosition = gridView.getFirstVisiblePosition();
Then, after you change the number of columns, pass that integer to setSelection():
gridView.setSelection(firstPosition);
"Selection", counter-intuitively, is not the same thing as "activation". It will ensure that the view is on-screen, but not visibly affect it in any other way.
While doing mobile UI Automation testing using Android UIAutomator, I need to find out all the elements present in the list view.
By using 'getChildCount()' method as shown below, I am getting the count of currently visible elements only, but more elements are present in the list view but are invisible.
Here is the sample code:
//Created UI Object for list view
UiObject listview_elements = new UiObject(new UiSelector().className("android.widget.ListView"));
//Printing the numbmer of child ements present in the List View by using getchildCount() method
System.out.println("List view elements : "+listview_elements.getChildCount());*
Could any one kindly help to get the count of all list view elements including invisible elements (i.e currently not displayed on the screen).
Note:
Kindly note that here I am not implementing android UI, rather I am just testing the third party android app's UI using Android's UIAutomator.
A bit late but here are some suggestions.
Remember that you can use UI Automator Viewer to identify resource ids and such.
AndroidSDKPath\tools\uiautomatorviewer
A couple considerations that change how we approach this.
Are items in the list clickable?
Are items in the list the same size?
Do the items contain distinct text fields?
Assuming the items in the list are clickable you can use a selector like this:
UiSelector selector = new UiSelector().clickable(true);
If the items do contain distinct text fields then you can perhaps use a Java Set to keep track of strings that represent each item.
If the items in the list are the same size then you can use a loop and scroll up by the same amount each time. Otherwise you can look at the next item in the list, get its bounds, get the top coordinate, and then scroll upward until you reach the top of the list (this might be the bottom of a toolbar). You can check if you hit the top by looking at the bounds.
When writing a loop the idea would be to only increment the index if you reach the end of the list since when you scroll up the top most item will become position 0.
Your loop would look something like this:
//UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());;
UiObject list = device.findObject(new UiSelector().resourceId(PACKAGE + ":id/" + resourceId));
int index = 0;
int count = 0;
while (index < list.getChildCount()) {
UiObject listItem = list.getChild(selector.index(index));
Set<String> examinedItems = new LinkedHashSet<>();
//if first item is a bit out of view (under toolbar) then skip it
if (listItem.getBounds().top < TOOLBAR_BOTTOM_Y) {
index++;
continue;
//this will only ever happen once = reached end of list
}
//get unique details from each item
//for example, you might get a text field for that list item list this
//UiObject textField = listItem.getChild(new UiSelector().resourceId(PACKAGE + ":id/" + childResourceId));
String itemDetails = ...;
//this would be relevent if the list was perfectly scrolled to the top and we don't know we are at the end of the list
if (examinedItems.contains(itemDetails)) {
index++;
continue;
}
//do any actual testing on the item here..
count++;
//if index > 0 we have reached the end of the list
if (index == 0) {
//you'll need to inherit from InstrumentationTestCase so you can pass an instance to this method
TouchUtils.drag(this, CENTER_X, CENTER_X, START_SCROLL_Y, START_SCROLL_Y - ITEM_HEIGHT, 150);
}
examinedItems.add(itemDetails);
}
//maybe return count here
If the list is of uniform size, (you know how tall each individual view is) you could do the following:
First get the ListView as a UiScrollable.
Call scrollToBeginning() to start at the top of the ListView.
Call getChildCount() to get the number of children on screen
scrollForward() until the bottom view is no longer visible.
Call getChildCount() again and repeat the process until you're at the bottom of the view.
You could find out how many swipes you need to do in order to get to the bottom by performing one such swipe, then seeing if scrollToEnd() returned false, (it was already at the bottom). If scrollToEnd() returned true, you would need to scroll back to the top and start over, this time increasing the amount of swipes you do by one. If you need to verify that a single element is in the listview (as this method of determining length & swiping through would be very slow) you could always use the getChildBy...() methods.
I have a 2 pane layout. On the left is a ListView, on the rights some content linked with the list. The content is scrollable.
If content moves forward, I want the list to go forward only if this item is not yet displayed. This prevents that the list has to update on every content change. I also want the list to go ahead one full page.
Is there something like
listView.isThisElementCurrentlyShown(int nr)
and
listView.moveAheadOnePage(int direction)
?
Thanks in advance.
Here is an example of isThisElementCurrentlyShown(Element e):
public boolean isThisElementCurrentlyShown(Element e){
ListView lv = getListView();
int start = lv.getFirstVisiblePosition();
for(int i=start, j=lv.getLastVisiblePosition();i<=j;i++){
if(e==lv.getItemAtPosition(i)){
return true;
}
}
return false;
}
This will tell you whether the element is visible or not.
For the moveAheadOnePage you should be able to use listview's built in functions getFirstVisiblePosition() and getLastVisiblePosition() to calculate the number of rows visible on that device, then advance the listview ahead that many positions. Something like this pseudocode (you will have to write this one):
public void moveAheadOnePage(int direction){//direction: 0-up, 1-down
int numVisibleRows = getLastVisiblePosition() - getFirstVisiblePosition();
this.setSelected(currentSelection + numVisibleRows) // +/- depending on direction
}
Mind you these are both expensive calls to make while scrolling through a listview, so you will definitely have to implement wisely~
EDIT: Updated code to include return false