I'm looking for a view known as LongListSelector on the Windows Phone. It's a list view with group headers. Tapping a group header displays only a list of groups. Tapping a group on the list of groups hides the list of groups and scrolls the view to the selected group. It's a very useful way of groupping long lists with easy navigation between groups. If there are alternatives fit for the same purpose that would be also great.
You can do this easily :)
The first thing you need to do is make sure your data source is a collection of collections. I would suggest an ObservableCollection> if you want maximum binding goodness. Then we can construct our listView as follows:
var listView = new ListView ();
listView.SetBinding (ListView.ItemsSourceProperty, "Data");
listView.ItemTemplate = new DataTemplate (typeof (MyCell));
listView.GroupHeaderTemplate = new DataTemplate (typeof (MyHeaderCell));
listView.IsGroupingEnabled = true;
listView.GroupShortNameBinding = new Binding ("Title");
In order, we first bind in our data, I am assuming the BindingContext here will be inherited from the page. Our data should be the collection of collections already mentioned.
Then we bind in our ItemTemplate as normal, we make a GroupHeaderTemplate, this will be the template shown in the list during normal scrolling. Next we enable grouping to tell the list to use the data as a grouped collection rather than a flat list.
Finally with all that done, we provide a binding for the GroupShortName. This binding is run against the collection for each group to grab out a string (or an object that will have ToString called on it) to produce the jump list as you showed in your screenshots.
For performance reasons you may want to ensure the ItemsSource is not set until everything else has been set to avoid the ListView attempting to realizing Cells in a partially configured state. This will not actually result in bugs, it just forces the ListView to do more work.
Related
I already have a list adapter that works properly. But I want to divide the object in the list into sections according to the date they were created. Something like this:
I found something called "sectioned recycler view" but couldn't find any documentation on that. I read all the related questions, but they all are either outdated or use a third-party library. What's the native way of implementing this feature?
There are a couple of approaches you could use. First the easy one:
make the header part of your item layout, but with GONE visibility by default
in onBindViewHolder, decide whether the header should be VISIBLE or GONE
The logic there depends on what you want, but it could be as simple as
val visible = position == 0 || items[position].date != items[position - 1].date
Basically you just need to work out what the condition is that would cause an item to be in a different "group" than the previous item, and then if it's met, show the header over that item.
The approach MarkL is talking about is more complex, but it's also more robust - by having separate Item and Header elements, you can treat them differently, and even do stuff like having the header collapse/show its children, select them all etc. You can do that with the other approach, but it requires more code since you're not really treating things as groups, it's more of a trick when it comes to displaying stuff.
Basically, ignoring the how for now, you need a list of headers and items. A sealed class is a good way to represent that:
sealed class ListElement {
data class Header(val date: Date) : ListElement()
data class Item(val itemData: YourItem) : ListElement()
}
I've made Item a wrapper class that holds your actual data object inside, since that's probably coming from elsewhere and you can't define it as part of this sealed class hierarchy - so sticking it inside one of the subclasses like this allows you to do that.
So now you can have a List<ListElement> containing Headers and Items in display order. Since you've mentioned creating the ViewHolders in a comment I won't explain all that, but basically when you're getting the item type for a position, you just need to check is Header or is Item and then handle it from there.
As for creating that list, there are lots of ways to do it - you could use groupBy to generate a Map of dates to lists of items, map each of those entries to a list of Header, Item, Item..., and flatten the whole thing into a single list:
items.map { Item(it) }
.groupBy { it.itemData.date }
.entries
.flatMap { (date, items) -> listOf(Header(date)) + items }
The advantage with a map like this is it's an actual grouped structure, so you can keep it around to generate flat lists for display - e.g. hiding a group's contents by only including the header in the list.
Or you could just build the list yourself, similar to the logic I mentioned in the first example - if the date has changed from the previous item, insert a Header first:
val list = mutableListOf<ListElement>().apply {
for (item in items) {
// add a header if the date changed - this handles the first header
// in an empty list too (where lastOrNull returns null, so the date is null)
val previousItemDate = (lastOrNull() as? Item)?.itemData?.date
if (previousItemDate != item.date) add(Header(item.date))
add(Item(item))
}
}
Or you could use fold. Don't forget to sort stuff!
You could create 2 types of view holders:
header which holds the date
data container which holds the other information.
And then create a list of objects which contain something like this:
listToBind = (header, data, data, header, data, data)
For your case, where header & info is the same object, you can do something like this:
take your object you receive from backend (example)
YourObject(val header: String, val info:InfoObject)
create 2 display objects, both inheriting from a type that your adapter accepts (say - AdapterDisplayEntity)
HeaderDisplayEntity(val header: String): AdapterDisplayEntity
InfoDisplayEntity(val info: InfoObject): AdapterDisplayEntity
now you can use your list that contains these items and submit to your adapter.
Use nested recycler view for this instead. You can check the example here.
Best solution for this scenario so far.
If you are using Jetpack Compose you can use the stickyHeader() as documented in the documentation
I want to create a custom ListView.
Initially, the custom ListView has one array of data, but when user taps one of the list items, it's then removed from current array and added to another. If the user taps on the second array, the list item is then added back over into the first array.
Please suggest how to apply logic to do this.
Updates : I wants to use only one listview/recyclerview.
Following are screen shots..
Regarding the object switch - this is a simple transfer between lists, , just know beforehand if the insertion and removal is index based, e.g:
contacts.add(iLocation, ContactObject);
favorites.remove(iOtherLocation);
Regarding the ListView stuff, I would suggest converting to RecyclerView, let's build a general scenario:
You have a screen (Activity or Fragment) that holds one list (the implementation can be ListView or Recycler), and another screen that holds the other list.
In both of your lists you have adapters in which you implement the logic for the clicks on the objects in the lists.
The click transfers the object, either directly to the other list, OR to a temporary Object holder (because you might need it for other stuff), in which case you will need to pull that object from the other view, either way you remove it from the current one.
you switch to the other view, and refresh it.
An easy way to go -
Assuming the screens are the same, use only one Activity, holding a single RecyclerView, and handle 2 adapters, each for every list, the adapters allow you to handle the clicks easily, with an index for the object clicked, the click executes the info swap action,the Activity handles the visual swap Action.
a very general example would be:
//init everything obviously ;)
List<ContactObject> contacts;
List<ContactObject> favoritesContacts;
//the AdapteListener is an interface declared inside the adapter
mContactsRecyclerAdapter = new ContactsRecyclerAdapter(this, contacts,new ContactsRecyclerAdapter.AdapterListener()
{
#Override
public void cellClicked(int iIndex, ContactObject object)
{
favoritesContacts.add(iIndex, ContactObject);
contacts.remove(iIndex);
mContactsRecyclerAdapter.notifyDataSetChanged();
mFavoritesRecyclerAdapter.notifyDataSetChanged();
mRecyclerView.swapAdapter(mFavoritesRecyclerAdapter, false);
}
});
And vice-versa for the other adapter.
Hope this helps, comment if you have problems and I'll update.
Please implements with custom view extend with Linearlayout
Custom view has 2 child Linearlayout in which will add with this custom view
First time add all the element in first Linearlayout and based on user action please remove from first Linearlayout and add it in another layout
I have ListView that is populated like so:
ArrayList<MyFullDataClass> myFullDataClassList = Utilities.getDataFromSQLite(getActivity()); // populate list from SQLite
ArrayAdapter<MyFullDataClass> adapter = new ArrayAdapter<MyFullDataClass>(getActivity(), android.R.layout.simple_list_item_1, MyFullDataClassList);
setListAdapter(adapter);
MyFullDataClass contains many things: Name, address, phone, email, web site, etc. So as it is, each row of the list contains all of this information. It's a little busy. I would like to make it so that each row in the list contains only, say, name and email (and then touching the row would popup with all information in MyFullDataClass)
I could do this by creating a class, call it MyPartialDataClass, that contains fields for only name and email, then create an ArrayList<MyPartialDataClass> and copy data from myFullDataClassList to myPartialDataClassList and use this partial class for the adapter. (Then when a row is clicked, use myFullDataClassList.)
Not particularly elegant, but it would work.
Is there a better way?
I don't think there's a need to have two separate classes which ultimately hold the same information.
Having one class to hold necessary information is fine, unless you absolutely need minute efficiency gains of having two classes. What I would do is make your Adapter take a MyFullDataClass object list, but only populate the views with name and email.
From there, you can listen for an onClick event on your Adapter and pass the MyFullDataClass object associated with the clicked view to a fragment which will display the rest of the information associated with the MyFullDataClass object (i.e. the fragment will display address, phone, etc in addition to name and email).
You wouldn't make two separate tables in a database to hold parts of the same information. You would select what rows/properties from each entry that you need. The same concept applies here, IMO.
I have an expandable listView which already has some data(from database). When user opens my application, a "loading" header is displayed during which new data is pulled from the internet.
Now when the new data is received , I store it in a database and update my cursor. New data is displayed above old data.
I also have a footer which asks whether i have to load more data from the internet. Now , the footer is displayed below the old data. I want a view in between the new data and old data, i.e. below new data , so that when clicks on that view , new data is pulled from the internet , which is pushed below new data but above old data. (You can imagine twitter timeline example for understanding my problem.)
How do i implement this ?
I don't know if it will work exactly as you expect, but try MergeAdapter (link below) with added View. You will have one ListView and two same adapters - one for the old and for the new data. Between will be some View - in your case loading Button.
MergeAdapter adapter = new MergeAdapter();;
adapter.addAdapter(oldAdapter);
adapter.addView(loadingLayout);
adapter.addAdapter(newAdapter);
MergeAdapter enables to show (something like) two ListViews in single activity with single ScrollBar. But there is/was some problem with removing added Views. Check it out if this option is enabled.
Link to MergeAdapter: https://github.com/commonsguy/cwac-merge
Maybe this will be in some case helpful: http://pivotallabs.com/users/joe/blog/articles/1759-android-tidbits-6-22-2011-hiding-header-views
My application uses a list of media files on the phone, i.e. images, audio and video. It also allows the user to filter the list via some checkboxes in a menu, so the user can choose to show or hide each type of files.
The way I've been doing this is by putting this in the adapter's getView():
// don't show unwanted file types
if (cmo.hasType(MediaType.AUDIO_FILE)){
if(!prefs.getBoolean(PREFS_SHOWAUDIO, true)){
return new ViewStub(mContext);;
}
}else if(cmo.hasType(MediaType.IMAGE_FILE)){
if(!prefs.getBoolean(PREFS_SHOWIMG, true)){
return new ViewStub(mContext);;
}
}else if( cmo.hasType(MediaType.VIDEO_FILE)){
if(!prefs.getBoolean(PREFS_SHOWVIDEO, true)){
return new ViewStub(mContext);;
}
}
which is quite effective in the sense that the list doesn't show those elements. However, the ListView still renders a 1px grey line between each View, even if they are ViewStubs, meaning I see a thick grey line whenever a group of consecutive items are filtered away.
How can I get rid of those lines? Should I create a new data array, containing only the elements that should show a view?
I think Adapter isn't a good place to add list logic. It's for fetching and displaying data, not for making decisions, what to show/hide. As you can see there is no way to not add a View for given index (in getView() method).
You should filter your list before you will pass it to an Adapter.