I\'m facing a very common problem:
I layed out an activity and now it turns out it should display a few items within this ScrollView
. The normal way to do that
You probably should just manually add your items to LinearLayout
:
LinearLayout layout = ... // Your linear layout.
ListAdapter adapter = ... // Your adapter.
final int adapterCount = adapter.getCount();
for (int i = 0; i < adapterCount; i++) {
View item = adapter.getView(i, null, null);
layout.addView(item);
}
EDIT: I rejected this approach when I needed to display about 200 non-trivial list items, it is very slow - Nexus 4 needed about 2 seconds to display my "list", that was unacceptable. So I turned to Flo's approach with headers. It works much faster because list views are created on demand when user scrolls, not at the time the view is created.
Resume: The manual addition of views to layout is easier to code (thus potentially less moving parts and bugs), but suffers from performance problems, so if you have like 50 views or more, I advise to use the header approach.
Example. Basically the activity (or fragment) layout transforms to something like this (no ScrollView needed anymore):
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/my_top_layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
Then in onCreateView()
(I'll use an example with a fragment) you need to add a header view and then set an adapter (I assume the header resource ID is header_layout
):
ListView listView = (ListView) inflater.inflate(R.layout.my_top_layout, container, false);
View header = inflater.inflate(R.layout.header_layout, null);
// Initialize your header here.
listView.addHeaderView(header, null, false);
BaseAdapter adapter = // ... Initialize your adapter.
listView.setAdapter(adapter);
// Just as a bonus - if you want to do something with your list items:
view.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// You can just use listView instead of parent casted to ListView.
if (position >= ((ListView) parent).getHeaderViewsCount()) {
// Note the usage of getItemAtPosition() instead of adapter's getItem() because
// the latter does not take into account the header (which has position 0).
Object obj = parent.getItemAtPosition(position);
// Do something with your object.
}
}
});
I use following code which replicate adapter functionality with ViewGroup
and TabLayout
. Good thing about this is that if you change your list and bind again, it will only affect changed items:
Usage:
val list = mutableListOf<Person>()
layout.bindChildren(list, { it.personId }, { bindView(it) }, {d, t ->bindView(d, t)})
list.removeAt(0)
list+=newPerson
layout.bindChildren(list, { it.personId }, { bindView(it) }, {d, t ->bindView(d, t)})
For ViewGroups
:
fun <Item, Key> ViewGroup.bindChildren(items: List<Item>, id: (Item) -> Key, view: (Item) -> View, bind: (Item, View) -> Unit) {
val old = children.map { it.tag as Key }.toList().filter { it != null }
val new = items.map(id)
val add = new - old
val remove = old - new
val keep = new.intersect(old)
val tagToChildren = children.associateBy { it.tag as Key }
val idToItem = items.associateBy(id)
remove.forEach { tagToChildren[it].let { removeView(it) } }
keep.forEach { bind(idToItem[it]!!, tagToChildren[it]!!) }
add.forEach { id -> view(idToItem[id]!!).also { it.tag = id }.also { addView(it, items.indexOf(idToItem[id])) } }
}
For TabLayout
I have this:
fun <Item, Key> TabLayout.bindTabs(items: List<Item>, toKey: (Item) -> Key, tab: (Item) -> TabLayout.Tab, bind: (Item, TabLayout.Tab) -> Unit) {
val old = (0 until tabCount).map { getTabAt(it)?.tag as Key }
val new = items.map(toKey)
val add = new - old
val remove = old - new
val keep = new.intersect(old)
val tagToChildren = (0 until tabCount).map { getTabAt(it) }.associateBy { it?.tag as Key }
val idToItem = items.associateBy(toKey)
remove.forEach { tagToChildren[it].let { removeTab(it) } }
keep.forEach { bind(idToItem[it]!!, tagToChildren[it]!!) }
add.forEach { key -> tab(idToItem[key]!!).also { it.tag = key }.also { addTab(it, items.indexOf(idToItem[key])) } }
}
Set your view to main.xml onCreate, then inflate from row.xml
main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="450dp" >
<ListView
android:id="@+id/mainListView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_above="@+id/size"
android:layout_below="@+id/editText1"
android:gravity="fill_vertical|fill_horizontal"
android:horizontalSpacing="15dp"
android:isScrollContainer="true"
android:numColumns="1"
android:padding="5dp"
android:scrollbars="vertical"
android:smoothScrollbar="true"
android:stretchMode="columnWidth" >
</ListView>
<TextView
android:id="@+id/size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:background="#ff444444"
android:gravity="center"
android:text="TextView"
android:textColor="#D3D3D3"
android:textStyle="italic" />
</EditText>
</RelativeLayout>
row.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingTop="3dp">
<TextView
android:id="@+id/rowTextView"
android:layout_width="0dip"
android:layout_height="41dp"
android:layout_margin="4dp"
android:layout_weight="2.83"
android:ellipsize="end"
android:gravity="center_vertical"
android:lines="1"
android:text="John Doe"
android:textColor="@color/color_white"
android:textSize="23dp" >
</TextView>
</LinearLayout>
I would stick with the header view solution. There's nothing wrong with it. At the moment I implementing an activity using the exact same approach.
Obviously the "item part" is more dynamically than static (varying item count vs. fix item count etc.) otherwise you won't think about using an adapter at all. So when you need an adapter then use the ListView.
Implementing a solution which populates a LinearLayout from an adapter is in the end nothing else than building a ListView with a custom layout.
Just my 2 cents.