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
Related
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.
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 an ArrayAdapter linked to a ListView.
mListView.setAdapter(mArrayAdapter);
Whenever I reset the ArrayList data to the ArrayAdapter:
mArrayAdapter.clear();
mArrayAdapter.addAll(mArrayList);
mArrayAdapter.notifyDataSetChanged()
the ListView gets correctly updated
however, if just after the above three lines, I call my custom method mListView.hasScrollbar() to detect whether the listview has a scrollbar or not, I get a null lastVisibleItem:
public boolean hasScrollbar() {
View lastVisibleItem = (View) getChildAt(getChildCount() - 1);
if (lastVisibleItem.getBottom()>=getHeight()) {
return true;
}
return false;
}
does it mean that the listview is still refreshing?
My main question is:
how can I test if the listview has the scrollbar after resetting the adapter with new data?
thank you for any help!
Using getLastVisiblePosition / getFirstVisiblePosition is a valid method of detecting wether you have scrolling or not within the list view (aslong as you compare it to getCount() and do your math ofc).
The problem you have as you already guess is that you are attempting to check out of sync.
In order to sync your query when the adapter already filled your List Data and updated changes, you need to issue a post request to the list, which will stack that petition to the message queue of the adapter.
yourAdapter.notifyDataSetChanged();
yourAdapter.getListView().post(new Runnable() {
#Override public void run() {
//your code here
}
});
Make sure to call that after notifySetDataChanged() of course. Because you want the list to update before the check.
I think your question equals to "how to tell if list view contains items need to displayed on more than one screen"?
So my suggestion is to use listView.getFirstVisiblePosition() and getLastVisiblePosition() to tell it.
Let's say a list has 4 items, how can I get a view from each menu item of a list by position?
Unfortunately the items that are in the ListView are generally only those that are visible. You should iterate on the ListAdapter instead.
For example, in some of my code, I have this:
SimpleCursorAdapter adapter = (SimpleCursorAdapter) this.getListAdapter();
int iNum = adapter.getCount();
for(int i=0; i<iNum; i++)
{
Cursor c = (Cursor) adapter.getItem(i);
// Now you can pull data from the cursor object,
// if that's what you used to create the adapter to start with
}
EDIT:
In response to jeffamaphone's comments, here's something else... if you are trying to work with each UI element then getChildAt is certainly more appropriate as it returns the View for the sub-item, but in general you can still only work with those that are visible at the time. If that's all you care about, then fine - just make sure you check for null when the call returns.
If you are trying to implement something like I was - a "Select All / Select None / Invert Selection" type of feature for a list that might exceed the screen, then you are much better off to make the changes in the Adapter, or have an external array (if as in my case, there was nowhere in the adapter to make the chagne), and then call notifyDataSetChanged() on the List Adapter. For example, my "Invert" feature has code like this:
case R.id.selectInvertLedgerItems:
for(int i=0; i<ItemChecked.length; i++)
{
ItemChecked[i] = !ItemChecked[i];
}
la.notifyDataSetChanged();
RecalculateTotalSelected();
break;
Note that in my case, I am also using a custom ListView sub-item, using adapter.setViewBinder(this); and a custom setViewValue(...) function.
Furthermore if I recall correctly, I don't think that the "position" in the list is necessarily the same as the "position" in the adapter... it is again based more on the position in the list. Thus, even though you are wanting the "50th" item on the list, if it is the first visible, getChildAt(50) won't return what you are expecting. I think you can use ListView.getFirstVisiblePosition() to account and adjust.
See here, this question answers the similar problem you mentioned here
In an android ListView, how can I iterate/manipulte all the child views, not just the visible ones?