I want to iterate a list of items into a ListView. This code below is not enough to iterate all the items into the list because of the weird behaviour of getChildCount() function which only returns the visible item count.
for (int i = 0; i < list.getChildCount(); i++) {
item = (View)list.getChildAt(i);
product = (Product)item.getTag();
// make some visual changes if product.id == someProductId
}
My screen displays 7 results and when there are more than 7 items into the list, it's not possible to access to the 8th item or so.. Only visible items..
Should I use ListIterator instead?
Thanks.
You need to customize your list adapter's getView() method, and put your check inside it to check if the current item's id matches:
Product product = items.get(position);
if(product.id == someProductId) {
//make visual changes
} else {
//reset visual changes to default to account for recycled views
}
Since typically only the visible items only exist at a specific time, getView is called whenever more need to be seen. They're created at that time, typically recycling views from the now-invisible items in the list (hence why you want to reset the changes if the criteria does NOT match).
So #kcoppock solved your first problem it seems you got another problem. How to update the view item? The android SMS application shows one way:
create your own list view item like this:
public class MyListItem extends RelativeLayout {
...
}
in your list view item layout file:
< MyListItem android:layout_width=.....>
...
</ MyListItem >
and in your code, when your view item can be seen, register MyListItem as a data changed listener(the data is up to you). I mean, when your data changed, then you can update the item directly.
Check out the SMS application source code to read more.
The number of views in the ListView(7) is different from the number of items in the adapter(which is more than 7) .Try to use a BaseAdapter.
Related
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 have ReçycleView with multiple photos in grid view, and i want to accomplish Select All functionality,
when there are less items in grid that can be view on the screen without scroll, i can do the select all functionality without any problem as this all views are bind to the recycle view.
But the problem occurs when the items are more and are in scrollview and the items that are off screen i.e they are still not bind to the RecycleView and this time when the user press the select all, only the images that are viewed and Bind to Viewholder are getting selected.
I have gone through many trial and error but failed to do the same.
Request you to come-up with some solution.
Below is the RecycleView ImageItem Model and the data i need for the selection.
ImageItem imageItem = new ImageItem(path, imageView, layoutImage,
albumImage, selectionImage, uploadProgress, false, file, dbAlbumPhotos);
case SELECT_ALL:
if (imageHashMap != null && imageHashMap.size() > 0) {
for (ImageItem imageItem : imageHashMap.values()) {
if (!imageItem.isSelected()) {
imageItem.setSelected(true);
imageItem.getSelectionImage().setVisibility(View.VISIBLE);
selectedImageUrls.put(imageItem.getDbAlbumPhoto().getPhotoId(), imageItem.getDbAlbumPhoto().getUrlPhotoLarge());
}
}
}
Problem with your code is that you are updating only those items view which are currently visible.
Whenever the user chooses for functinolaity Select All, just iterate and update all the ImageItem or java bean or model values to true.
Call YourRecyclerView.notifyDataSetChanged() that the dataset has changed. Now if user unselect or select any single item then update only that bean object and call YourRecyclerView.notifyItemChanged(int pos) that item at position has changed.
Update 1: Don't put layoutImage in ImageItem bean. You need not put any ViewHolder in ImageItem. Now holder.layout image value can be set based upon if(imageItem.isSelected()) holder.layout image.setVisibilit(View.Visible) else //View.Invisible. holder.layout image will be always available in onBindViewHolder.
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.
Android and Java noob here, although I've dabbled in various languages over the years. This has been driving me bonkers all week.
Trying to write my first app, and it's the clichéd shopping list app, with a ListView made up of CheckedTextView items (as supplied by android.R.layout.simple_list_item_multiple_choice). The ListView is set to CHOICE_MODE_MULTIPLE.
The backend to the ListView is an ArrayList, called shoppingItems, where ShoppingListItem is simply defined as:
public class ShoppingListItem {
public String name;
public Boolean checked;
// The obvious constructors here...
}
and I have an ArrayAdapter with an over-ridden getView() method:
shoppingListAdapter = new ArrayAdapter<ShoppingListItem>
(this,
android.R.layout.simple_list_item_multiple_choice,
android.R.id.text1,
shoppingItems)
{
#Override
public View getView(int position, View convertView, ViewGroup parent) {
CheckedTextView rowView = (CheckedTextView)convertView;
if (rowView==null){
LayoutInflater inflater = getLayoutInflater();
rowView = (CheckedTextView)inflater.inflate(android.R.layout.simple_list_item_multiple_choice, parent, false);
}
rowView.setText(shoppingItems.get(position).name);
rowView.setChecked(shoppingItems.get(position).checked);
return rowView;
}
};
Everything works fine -- adding items, editing items, removing an individual item via its context menu -- except removing all the checked items via a "Remove" button at the bottom of the screen.
I must have tried writing my removeCheckedItems method half a dozen different ways, including various combinations of:
removing checked items using the list adapter (which from all I've read is the way that's supposed to work)
removing checked items directly from the ArrayList, then calling notifyDatasetChanged()
explicitly removing child views from the ListView
iterating over the results of the ListView's getCheckedItemPositions(), rather than the whole list
Here's my most naive attempt:
private void removeCheckedItems(){
ShoppingListItem item;
for (int i=0; i< adapter.getCount(); i++) {
item = shoppingListAdapter.getItem(i);
if (shoppingListView.isItemChecked(i)){
item = shoppingItems.get(i);
shoppingListAdapter.remove(item);
}
}
removeBtn.setEnabled(false);
}
However I do it, though: the checkboxes in the ListView just don't stay in sync with the data in the ShoppingItems ArrayList. Specifically, if I start off with:
Item one [ ]
Item two [ ]
Item three [ ]
in the list, then check Item one:
Item one [x]
Item two [ ]
Item three [ ]
then click my Remove button, which confirms the action via a popup dialog, the first item disappears, but the checkbox in the first row remains checked:
Item two [x]
Item three [ ]
At this point, I know by means of debugging messages etc. that the contents of the ArrayList are correct -- i.e. that it contains two ShoppingListItem items with the correct names, both of whose 'checked' fields are set to false.
I'm sure I'm missing something obvious, but despite reading numerous examples, and even more ListView-related answers on here, I can't see it for the life of me. (Complete code listing for the activity as it currently stands can be found here, if you need to see more.)
You just need to call the fillData() method again from your removeCheckedItems method.
Everytime you make a change to the data, you need to "fill" the data for the list again, refresh the list adapter.
Do a Google Search for 'ListActivity fillData' tutorial, and you get lots of great examples.
Here are a couple good ones:
http://www.vogella.de/articles/AndroidListView/article.html
http://www.vogella.de/articles/AndroidSQLite/article.html
http://thinkandroid.wordpress.com/2010/01/09/simplecursoradapters-and-listviews/
Let me know if you get stuck, I'll be able to help more tomorrow. I've done this a lot, so I can help you work the kinks out of it.
For the record, the only way I ever got this to work in the end was to use my own custom row layout and inflate that in getView(), rather than using android.R.layout.simple_list_item_multiple_choice. Everything worked just the way I'd expected it to all along when I did that, including the ListView updating instantly and correctly whenever I changed the data via the ArrayAdapter, and notifyDatasetChanged() doing likewise when I was changing the data directly.
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?