Usando um ListAdapter para preencher um LinearLayout dentro de um layout ScrollView

95

Estou enfrentando um problema muito comum: criei uma atividade e agora descobri que ela deve exibir alguns itens dentro dela ScrollView. A maneira normal de fazer isso seria usar o existente ListAdapter, conectá-lo a um ListViewe BOOM eu teria minha lista de itens.

MAS você não deve colocar um aninhado ListViewem uma, ScrollViewpois isso atrapalha a rolagem - até o Android Lint reclama disso.

Então aqui está minha pergunta:

Como faço para conectar ListAdaptera a LinearLayoutou algo semelhante?

Eu sei que esta solução não escalará para muitos itens, mas minhas listas são muito curtas (<10 itens), portanto, a reutilização de visualizações não é realmente necessária. Em termos de desempenho, posso viver colocando todas as visualizações diretamente no LinearLayout.

Uma solução que encontrei seria colocar meu layout de atividade existente na seção headerView do ListView. Mas parece que estou abusando desse mecanismo, então estou procurando uma solução mais limpa.

Ideias?

ATUALIZAÇÃO: para inspirar a direção certa, adiciono um layout de amostra para mostrar meu problema:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/news_detail_layout"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:orientation="vertical"
              android:visibility="visible">


    <ScrollView
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="#FFF"
            >

        <LinearLayout
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:orientation="vertical"
                android:paddingLeft="@dimen/news_detail_layout_side_padding"
                android:paddingRight="@dimen/news_detail_layout_side_padding"
                android:paddingTop="@dimen/news_detail_layout_vertical_padding"
                android:paddingBottom="@dimen/news_detail_layout_vertical_padding"
                >

            <TextView
                    android:id="@+id/news_detail_date"
                    android:layout_height="wrap_content"
                    android:layout_width="fill_parent"
                    android:gravity="center_horizontal"
                    android:text="LALALA"
                    android:textSize="@dimen/news_detail_date_height"
                    android:textColor="@color/font_black"
                    />

            <Gallery
                    android:id="@+id/news_detail_image"
                    android:layout_height="wrap_content"
                    android:layout_width="fill_parent"
                    android:paddingTop="5dip"
                    android:paddingBottom="5dip"
                    />

            <TextView
                    android:id="@+id/news_detail_headline"
                    android:layout_height="wrap_content"
                    android:layout_width="fill_parent"
                    android:gravity="center_horizontal"
                    android:text="Some awesome headline"
                    android:textSize="@dimen/news_detail_headline_height"
                    android:textColor="@color/font_black"
                    android:paddingTop="@dimen/news_detail_headline_paddingTop"
                    android:paddingBottom="@dimen/news_detail_headline_paddingBottom"
                    />

            <TextView
                    android:id="@+id/news_detail_content"
                    android:layout_height="wrap_content"
                    android:layout_width="fill_parent"
                    android:text="Here comes a lot of text so the scrollview is really needed."
                    android:textSize="@dimen/news_detail_content_height"
                    android:textColor="@color/font_black"
                    />

            <!---
                HERE I NEED THE LIST OF ITEMS PROVIDED BY THE EXISTING ADAPTER. 
                They should be positioned at the end of the content, so making the scrollview smaller is not an option.
            ---->                        

        </LinearLayout>
    </ScrollView>
</LinearLayout>

ATUALIZAÇÃO 2 Mudei o título para torná-lo mais fácil de entender (recebi um downvote, doh!).

Stefan Hoth
fonte
1
Você já encontrou uma boa solução limpa para este problema? Vejo que o Google usa em sua loja de jogos para análises. Alguém sabe como eles fazem isso?
Zapnologica de

Respostas:

96

Você provavelmente deve apenas adicionar manualmente seus itens a 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);
}

EDITAR : Rejeitei essa abordagem quando precisei exibir cerca de 200 itens de lista não triviais, é muito lento - o Nexus 4 precisava de cerca de 2 segundos para exibir minha "lista", o que era inaceitável. Então eu me virei para a abordagem de Flo com cabeçalhos. Funciona muito mais rápido porque as visualizações de lista são criadas sob demanda quando o usuário rola a página, não no momento em que a visualização é criada.

Resumo: A adição manual de visualizações ao layout é mais fácil de codificar (portanto, potencialmente menos partes móveis e bugs), mas sofre de problemas de desempenho, portanto, se você tiver cerca de 50 visualizações ou mais, aconselho usar a abordagem de cabeçalho.

Exemplo. Basicamente, o layout da atividade (ou fragmento) se transforma em algo assim (não é mais necessário ScrollView):

<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"/>

Em seguida, em onCreateView()(usarei um exemplo com um fragmento), você precisará adicionar uma visualização de cabeçalho e, em seguida, definir um adaptador (presumo que o ID do recurso de cabeçalho seja 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.
    }
  }
});
fumaça
fonte
Sim, essa era a segunda idéia que eu tinha, mas eu não tinha certeza do ListAdaptere ListViewfaz mais magia por trás das telas que eu então não fazer manualmente.
Stefan Hoth
Claro que ListView faz alguma mágica, pelo menos divisores entre os elementos, você teria que adicioná-los manualmente, eu acho. ListAdapter (se implementado corretamente) não deve fazer mais mágica.
fumante em
2
Este é um voto negativo para mim. Alguém tem problemas de memória? Há uma razão para usarmos adaptadores ...
Kevin Parker
1
Alguém mais tem problemas para atualizar as visualizações usando o notifyDataSetChangedmétodo do adaptador ? Isso funciona bem em um adaptador diferente que conectei a a ListView, mas não parece estar funcionando com o que conectei a a LinearLayout.
jokeefe
2
esse é um dos motivos pelos quais odeio o Android. Não vejo por que você precisa implementar soluções complicadas ou sofre de problemas de desempenho.
IARI
6

Eu ficaria com a solução de exibição de cabeçalho. Não há nada de errado nisso. No momento estou implementando uma atividade usando exatamente a mesma abordagem.

Obviamente, a "parte do item" é mais dinâmica do que estática (contagem de itens variados vs. contagem de itens fixos etc.), caso contrário, você não vai pensar em usar um adaptador. Portanto, quando você precisar de um adaptador, use o ListView.

Implementar uma solução que preenche um LinearLayout a partir de um adaptador não é nada mais do que construir um ListView com um layout personalizado.

Apenas meus 2 centavos.

Flo
fonte
Uma desvantagem dessa abordagem é que você precisa mover quase todo o layout de sua atividade (veja acima) para outra parte do layout para que possa referenciá-lo como ListHeaderView e criar outro layout, incluindo apenas o ListView. Não tenho certeza se gosto deste trabalho de patch.
Stefan Hoth
1
Sim, eu sei o que você quer dizer, antes de começar a usar essa abordagem, também não gostava da ideia de ter meu layout de atividades separado em vários arquivos. Mas quando vi que o layout geral parecia como eu esperava, não me importei com a separação, pois ele está separado apenas em dois arquivos. Uma coisa que você deve estar ciente: o ListView não deve ter uma visualização vazia, pois isso ocultará o cabeçalho da lista quando não houver itens da lista.
Flo
3
Além disso, se você deseja ter várias listas em uma página, isso realmente não funciona. Alguém tem um exemplo de visualização do adaptador personalizado que produz itens em uma lista "plana", ou seja, uma que não rola?
murtuza de
0

Defina sua visualização como main.xml onCreate e aumente a partir de 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>
fasheikh
fonte
Eu acho que você perdeu entendeu a pergunta. Ele não quer preencher uma lista com LinearLayouts.
Flo
"Como faço para conectar um ListAdapter a um LinearLayout ou algo semelhante?" Apenas respondendo o que o OP perguntou
fasheikh
2
Não, ele não quer usar um ListView. Ele deseja apenas usar um adaptador e usá-lo para preencher um LinearLayout com entradas.
Flo
Obrigado pela sua resposta, mas @Flo está correto. Não posso usar um ListView porque o layout externo já é um ScrollView. Por favor, verifique meu texto e o layout de amostra.
Stefan Hoth
por que esse scrollView é necessário?
fasheikh
0

Eu uso o seguinte código que replica a funcionalidade do adaptador com ViewGroupeTabLayout . O bom sobre isso é que, se você alterar sua lista e vincular novamente, isso afetará apenas os itens alterados:

Uso:

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)})

Para 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])) } }
}

Pois TabLayouteu tenho isso:

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])) } }
}
M-WaJeEh
fonte