Como posso colocar um ListView em um ScrollView sem ele desmoronar?

351

Eu procurei por soluções para esse problema, e a única resposta que posso encontrar parece ser " não coloque um ListView em um ScrollView ". Ainda não vi nenhuma explicação real para o porquê . A única razão pela qual consigo encontrar é que o Google não acha que você deva fazer isso. Bem, eu faço, então eu fiz.

Portanto, a pergunta é: como você pode colocar um ListView em um ScrollView sem que ele desmorone à sua altura mínima?

DougW
fonte
11
Porque quando um dispositivo usa uma tela de toque, não há uma boa maneira de diferenciar entre dois contêineres roláveis ​​aninhados. Ele funciona em áreas de trabalho tradicionais, onde você usa a barra de rolagem para rolar um contêiner.
Romain Guy
57
Claro que existe. Se eles tocarem no interior, role isso. Se o interior for muito grande, é um design de interface do usuário ruim. Isso não significa que é uma má ideia em geral.
DougW 16/08/10
5
Só tropecei nisso no meu aplicativo para tablet. Embora não pareça muito pior no Smartphone, isso é horrível em um tablet onde você pode facilmente decidir se o usuário deseja rolar o ScrollView externo ou interno. @ DougW: design horrível, eu concordo.
água
4
@ Romano - este é um post bastante antigo, por isso estou me perguntando se as preocupações aqui já foram abordadas ou se ainda não há uma boa maneira de usar um ListView dentro de um ScrollView (ou uma alternativa que não envolva a codificação manual todas as qualidades de um ListView em um LinearLayout)?
Jim
3
@ Jim: "ou uma alternativa que não envolve a codificação manual de todas as qualidades de um ListView em um LinearLayout" - coloque tudo dentro do ListView. ListViewpode ter vários estilos de linha, além de linhas não selecionáveis.
CommonsWare

Respostas:

196

Usar um ListViewpara fazê-lo não rolar é extremamente caro e vai contra todo o propósito de ListView. Você não deve fazer isso. Basta usar um LinearLayout.

Romain Guy
fonte
69
A fonte seria eu, desde que eu sou responsável pelo ListView nos últimos 2 ou 3 anos :) O ListView faz muito trabalho extra para otimizar o uso de adaptadores. Tentar contornar isso ainda fará com que o ListView faça muito trabalho que um LinearLayout não precisaria fazer. Não vou entrar em detalhes, porque não há espaço suficiente aqui para explicar a implementação do ListView. Isso também não resolverá o comportamento do ListView em relação aos eventos de toque. Também não há garantia de que, se o seu hack funcionar hoje, ele ainda funcionará em uma versão futura. Usar um LinearLayout não seria muito trabalhoso.
Romain Guy
7
Bem, essa é uma resposta razoável. Se eu pudesse compartilhar algum feedback, tenho uma experiência muito mais significativa no iPhone e posso dizer que a documentação da Apple é muito melhor escrita com relação às características de desempenho e casos de uso (ou antipadrões) como este. No geral, a documentação do Android é muito mais distribuída e menos focada. Entendo que existem algumas razões por trás disso, mas essa é uma longa discussão; portanto, se você se sentir compelido a conversar sobre isso, entre em contato.
DougW 16/08/10
6
Você pode escrever facilmente um método estático que cria um LinearLayout a partir de um adaptador.
Romain Guy
125
Esta NÃO é uma resposta para How can I put a ListView into a ScrollView without it collapsing?... Você pode dizer que não deve fazê-lo, mas também dar uma resposta sobre como fazê-lo, é uma boa resposta. Você sabe que um ListView tem outros recursos interessantes além da rolagem, recursos que o LinearLayout não possui.
Jorge Fuentes González
44
E se você tiver uma região contendo um ListView + outros modos de exibição e quiser que tudo role como um? Parece um caso de uso razoável para colocar um ListView em um ScrollView. Substituir o ListView por um LinearLayout não é uma solução, pois você não pode usar os Adaptadores.
Barry Fruitman
262

Aqui está a minha solução. Sou bastante novo na plataforma Android e tenho certeza de que isso é um pouco tolo, especialmente na parte de chamar .measure diretamente e definir oLayoutParams.height propriedade diretamente, mas funciona.

Tudo o que você precisa fazer é ligar Utility.setListViewHeightBasedOnChildren(yourListView)e será redimensionado para acomodar exatamente a altura de seus itens.

public class Utility {
    public static void setListViewHeightBasedOnChildren(ListView listView) {
        ListAdapter listAdapter = listView.getAdapter();
        if (listAdapter == null) {
            // pre-condition
            return;
        }

        int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();

        for (int i = 0; i < listAdapter.getCount(); i++) {
            View listItem = listAdapter.getView(i, null, listView);
            if (listItem instanceof ViewGroup) {
                listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
             }

             listItem.measure(0, 0);
             totalHeight += listItem.getMeasuredHeight();
        }

        ViewGroup.LayoutParams params = listView.getLayoutParams();
        params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
        listView.setLayoutParams(params);
    }
}
DougW
fonte
66
Você acabou de recriou um LinearLayout muito caro :)
Romain Guy
82
Exceto LinearLayoutque a não possui todas as características inerentes a ListView- não possui divisórias, exibições de cabeçalho / rodapé, um seletor de lista que corresponda às cores do tema da interface do usuário do telefone etc. Honestamente, não há razão para que você não possa rolar o contêiner interno até chegar ao fim e interrompa a interceptação de eventos de toque para que o contêiner externo role. Todo navegador de desktop faz isso quando você está usando sua roda de rolagem. O próprio Android pode lidar com rolagem aninhada se você estiver navegando pelo trackball, por que não usar o touch?
Neil Traft
29
listItem.measure(0,0)lançará um NPE se listItem for uma instância do ViewGroup. Eu adicionei o seguinte antes listItem.measure:if (listItem instanceof ViewGroup) listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
Siklab.ph 21/02
4
Correção para ListViews com preenchimento diferente de 0:int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
Paul
8
Infelizmente, esse método é um pouco falho quando ListViewpode conter itens de altura variável. A chamada para getMeasuredHeight()retorna apenas a altura mínima desejada, em oposição à altura real. Tentei usar getHeight(), mas isso retorna 0. Alguém tem alguma idéia de como abordar isso?
Matt Huggins
89

Isto vai certamente funcionar ............
Você só tem que substituir o seu <ScrollView ></ScrollView>no layout do arquivo XML com este Custom ScrollViewcomo <com.tmd.utils.VerticalScrollview > </com.tmd.utils.VerticalScrollview >

package com.tmd.utils;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ScrollView;

public class VerticalScrollview extends ScrollView{

    public VerticalScrollview(Context context) {
        super(context);
    }

     public VerticalScrollview(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        public VerticalScrollview(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                    Log.i("VerticalScrollview", "onInterceptTouchEvent: DOWN super false" );
                    super.onTouchEvent(ev);
                    break;

            case MotionEvent.ACTION_MOVE:
                    return false; // redirect MotionEvents to ourself

            case MotionEvent.ACTION_CANCEL:
                    Log.i("VerticalScrollview", "onInterceptTouchEvent: CANCEL super false" );
                    super.onTouchEvent(ev);
                    break;

            case MotionEvent.ACTION_UP:
                    Log.i("VerticalScrollview", "onInterceptTouchEvent: UP super false" );
                    return false;

            default: Log.i("VerticalScrollview", "onInterceptTouchEvent: " + action ); break;
        }

        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        super.onTouchEvent(ev);
        Log.i("VerticalScrollview", "onTouchEvent. action: " + ev.getAction() );
         return true;
    }
}
Atul Bhardwaj
fonte
4
Isso é muito bom. Também possibilita a inserção de um fragmento do Google Maps no ScrollView, e ainda funciona com o zoom in / out. Obrigado.
Magnus Johansson
Alguém notou algum contras com essa abordagem? É tão simples que eu tenho medo: o
Marija Milosevic
11
Solução agradável, deve ser a resposta aceita +1! Eu misturei com a resposta abaixo (de djunod), e isso funciona muito bem!
tanou
11
Esta é a única correção que faz rolagem e clique em crianças que trabalham ao mesmo tempo para mim.
Muito
25

Em vez de colocar ListViewdentro de a ScrollView, podemos usar ListViewcomo a ScrollView. Coisas que precisam estar dentro ListViewpodem ser colocadas dentro do ListView. Outros layouts na parte superior e inferior ListViewpodem ser colocados adicionando layouts ao cabeçalho e rodapé de ListView. Assim, o todo ListViewlhe dará uma experiência de rolagem.

Uma corrida
fonte
3
Esta é a resposta mais elegante e adequada à sua imagem. Para obter mais detalhes sobre essa abordagem, consulte esta resposta: stackoverflow.com/a/20475821/1221537
adamdport 10/10/15
21

Existem muitas situações em que faz muito sentido ter os ListViews em um ScrollView.

Aqui está o código baseado na sugestão do DougW ... funciona em um fragmento, consome menos memória.

public static void setListViewHeightBasedOnChildren(ListView listView) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null) {
        return;
    }
    int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);
    int totalHeight = 0;
    View view = null;
    for (int i = 0; i < listAdapter.getCount(); i++) {
        view = listAdapter.getView(i, view, listView);
        if (i == 0) {
            view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth, LayoutParams.WRAP_CONTENT));
        }
        view.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
        totalHeight += view.getMeasuredHeight();
    }
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
    listView.requestLayout();
}

chame setListViewHeightBasedOnChildren (listview) em cada listview incorporada.

djunod
fonte
Não funcionará quando os itens da lista tiverem tamanhos variáveis, ou seja, alguma exibição de texto que se expanda por várias linhas. Qualquer solução?
Khobaib
Verificar o meu comentário aqui - ele dá a solução perfeita - stackoverflow.com/a/23315696/1433187
Khobaib
funcionou para mim com a API 19, mas não com a API 23: aqui ele adiciona muito espaço em branco abaixo do listView.
Lucker10
17

Na verdade, o ListView já é capaz de se medir para ser alto o suficiente para exibir todos os itens, mas não o faz quando você simplesmente especifica wrap_content (MeasureSpec.UNSPECIFIED). Isso será feito quando houver uma altura com MeasureSpec.AT_MOST. Com esse conhecimento, você pode criar uma subclasse muito simples para resolver esse problema, que funciona muito melhor do que qualquer uma das soluções postadas acima. Você ainda deve usar wrap_content com esta subclasse.

public class ListViewForEmbeddingInScrollView extends ListView {
    public ListViewForEmbeddingInScrollView(Context context) {
        super(context);
    }

    public ListViewForEmbeddingInScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ListViewForEmbeddingInScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 4, MeasureSpec.AT_MOST));
    }
}

Manipular o heightMeasureSpec como AT_MOST com um tamanho muito grande (Integer.MAX_VALUE >> 4) faz com que o ListView meça todos os seus filhos até a altura (muito grande) especificada e defina sua altura de acordo.

Isso funciona melhor que as outras soluções por alguns motivos:

  1. Mede tudo corretamente (preenchimento, divisores)
  2. Ele mede o ListView durante a passagem de medida
  3. Devido ao item 2, ele lida com alterações na largura ou no número de itens corretamente, sem nenhum código adicional

No lado negativo, você pode argumentar que fazer isso depende de um comportamento não documentado no SDK que pode mudar. Por outro lado, você pode argumentar que é assim que o wrap_content realmente deve funcionar com o ListView e que o comportamento atual do wrap_content está quebrado.

Se você está preocupado que o comportamento possa mudar no futuro, você deve simplesmente copiar a função onMeasure e as funções relacionadas do ListView.java e para sua própria subclasse, e fazer com que o caminho AT_MOST através do onMeasure seja executado também como NÃO ESPECIFICADO.

A propósito, acredito que essa é uma abordagem perfeitamente válida quando você trabalha com um pequeno número de itens da lista. Pode ser ineficiente quando comparado ao LinearLayout, mas quando o número de itens é pequeno, o uso do LinearLayout é otimização desnecessária e, portanto, complexidade desnecessária.

Jason Y
fonte
Também funciona com itens de altura variável na lista!
Denny
@ Jason Y o que isso significa, Integer.MAX_VALUE >> 4 significa ?? o que tem 4 ai ??
KJEjava48
@Jason Y, para mim, funcionou apenas depois que eu alterei Integer.MAX_VALUE >> 2 para Integer.MAX_VALUE >> 21. Por que é assim? Também funciona com ScrollView e não com NestedScrollView.
KJEjava48
13

Há uma configuração embutida para isso. No ScrollView:

android:fillViewport="true"

Em Java,

mScrollView.setFillViewport(true);

Romain Guy explica em detalhes aqui: http://www.curious-creature.org/2010/08/15/scrollviews-handy-trick/

TalkLittle
fonte
2
Isso funciona para um LinearLayout como ele demonstra. Não funciona para um ListView. Com base na data desse post, imagino que ele o escreveu para fornecer um exemplo de sua alternativa, mas isso não responde à pergunta.
DougW
esta é uma solução rápida
10

Você cria um ListView personalizado que não é rolável

public class NonScrollListView extends ListView {

    public NonScrollListView(Context context) {
        super(context);
    }
    public NonScrollListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public NonScrollListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
                    Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
            ViewGroup.LayoutParams params = getLayoutParams();
            params.height = getMeasuredHeight();    
    }
}

No arquivo de recursos de layout

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >

    <!-- com.Example Changed with your Package name -->

    <com.Example.NonScrollListView
        android:id="@+id/lv_nonscroll_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
    </com.Example.NonScrollListView>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/lv_nonscroll_list" >

        <!-- Your another layout in scroll view -->

    </RelativeLayout>
</RelativeLayout>

No arquivo Java

Crie um objeto do seu customListview em vez do ListView, como: NonScrollListView non_scroll_list = (NonScrollListView) findViewById (R.id.lv_nonscroll_list);

Dedaniya HirenKumar
fonte
8

Não podemos usar duas rolagens simultaneamente. Teremos o comprimento total do ListView e expandiremos o listview com a altura total. Copie o método setListViewHeightBasedOnChildren (lv) no seu código e expanda o listview e use o listview dentro do scrollview. arquivo \ layout xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
 <ScrollView

        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
         android:background="#1D1D1D"
        android:orientation="vertical"
        android:scrollbars="none" >

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="#1D1D1D"
            android:orientation="vertical" >

            <TextView
                android:layout_width="fill_parent"
                android:layout_height="40dip"
                android:background="#333"
                android:gravity="center_vertical"
                android:paddingLeft="8dip"
                android:text="First ListView"
                android:textColor="#C7C7C7"
                android:textSize="20sp" />

            <ListView
                android:id="@+id/first_listview"
                android:layout_width="260dp"
                android:layout_height="wrap_content"
                android:divider="#00000000"
               android:listSelector="#ff0000"
                android:scrollbars="none" />

               <TextView
                android:layout_width="fill_parent"
                android:layout_height="40dip"
                android:background="#333"
                android:gravity="center_vertical"
                android:paddingLeft="8dip"
                android:text="Second ListView"
                android:textColor="#C7C7C7"
                android:textSize="20sp" />

            <ListView
                android:id="@+id/secondList"
                android:layout_width="260dp"
                android:layout_height="wrap_content"
                android:divider="#00000000"
                android:listSelector="#ffcc00"
                android:scrollbars="none" />
  </LinearLayout>
  </ScrollView>

   </LinearLayout>

Método onCreate na classe Activity:

 import java.util.ArrayList;
  import android.app.Activity;
 import android.os.Bundle;
 import android.view.Menu;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ArrayAdapter;
 import android.widget.ListAdapter;
  import android.widget.ListView;

   public class MainActivity extends Activity {

   @Override
   protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.listview_inside_scrollview);
    ListView list_first=(ListView) findViewById(R.id.first_listview);
    ListView list_second=(ListView) findViewById(R.id.secondList);
    ArrayList<String> list=new ArrayList<String>();
    for(int x=0;x<30;x++)
    {
        list.add("Item "+x);
    }

       ArrayAdapter<String> adapter=new ArrayAdapter<String>(getApplicationContext(), 
          android.R.layout.simple_list_item_1,list);               
      list_first.setAdapter(adapter);

     setListViewHeightBasedOnChildren(list_first);

      list_second.setAdapter(adapter);

    setListViewHeightBasedOnChildren(list_second);
   }



   public static void setListViewHeightBasedOnChildren(ListView listView) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null) {
        // pre-condition
        return;
    }

    int totalHeight = 0;
    for (int i = 0; i < listAdapter.getCount(); i++) {
        View listItem = listAdapter.getView(i, null, listView);
        listItem.measure(0, 0);
        totalHeight += listItem.getMeasuredHeight();
    }

    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight
            + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
      }
Ashish Saini
fonte
Good fix! Funciona conforme necessário. Obrigado. No meu caso, tenho várias visualizações antes da lista e a solução perfeita é ter apenas uma rolagem vertical na visualização.
Proverbio
4

Essa é uma combinação das respostas de DougW, Good Guy Greg e Paul. Descobri que tudo era necessário ao tentar usar isso com um adaptador de listview personalizado e itens de lista não padrão, caso contrário, o listview travou o aplicativo (também travou com a resposta do Nex):

public void setListViewHeightBasedOnChildren(ListView listView) {
        ListAdapter listAdapter = listView.getAdapter();
        if (listAdapter == null) {
            return;
        }

        int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
        for (int i = 0; i < listAdapter.getCount(); i++) {
            View listItem = listAdapter.getView(i, null, listView);
            if (listItem instanceof ViewGroup)
                listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
            listItem.measure(0, 0);
            totalHeight += listItem.getMeasuredHeight();
        }

        ViewGroup.LayoutParams params = listView.getLayoutParams();
        params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
        listView.setLayoutParams(params);
    }
Carrinho abandonado
fonte
Onde chamar esse método?
Dhrupal
Depois de criar a lista de exibição e configurar o adaptador.
Abandoned Cart
Isso fica muito lento em longas listas.
cakan
@cakan Também foi escrito 2 anos antes como uma solução alternativa para permitir o uso de certos componentes de interface do usuário que não devem ser combinados. Você está adicionando uma quantidade desnecessária de sobrecarga no Android moderno; portanto, é de se esperar que a lista diminua quando ficar grande.
Carrinho abandonado
3

Você não deve colocar um ListView em um ScrollView porque um ListView já é um ScrollView. Então, seria como colocar um ScrollView em um ScrollView.

O que você está tentando realizar?

Cheryl Simon
fonte
4
Estou tentando realizar um ListView dentro de um ScrollView. Não quero que meu ListView seja rolável, mas não há uma propriedade simples para definir myListView.scrollEnabled = false;
DougW 16/08/10
Como Romain Guy disse, esse não é o objetivo de um ListView. Um ListView é uma exibição otimizada para a exibição de uma longa lista de itens, com muita lógica complicada para fazer carregamento lento de exibições, reutilizar exibições etc. Nada disso faz sentido se você estiver dizendo ao ListView para não rolar. Se você deseja apenas vários itens em uma coluna vertical, use um LinearLayout.
Cheryl Simon
9
O problema é que existem várias soluções que um ListView fornece, incluindo a mencionada. Grande parte da funcionalidade simplificada pelos adaptadores é sobre gerenciamento e controle de dados, não otimização da interface do usuário. Na IMO, deveria haver um ListView simples e um mais complexo que faz com que todos os itens da lista sejam reutilizados.
DougW 16/08/10
2
Absolutamente de acordo. Observe que é fácil usar um adaptador existente com um LinearLayout, mas quero todas as coisas boas que o ListView fornece, como divisores, e que não tenho vontade de implementar manualmente.
Artem Russakovskii
11
@ArtemRussakovskii Você pode explicar sua maneira fácil de substituir o ListView para LinearLayout por um adaptador existente?
happyhardik 12/09/12
3

Eu converti @ DougW Utilityem C # (usado no Xamarin). O seguinte funciona bem para itens de altura fixa na lista e será muito bom, ou pelo menos um bom começo, se apenas alguns dos itens forem um pouco maiores que o item padrão.

// You will need to put this Utility class into a code file including various
// libraries, I found that I needed at least System, Linq, Android.Views and 
// Android.Widget.
using System;
using System.Linq;
using Android.Views;
using Android.Widget;

namespace UtilityNamespace  // whatever you like, obviously!
{
    public class Utility
    {
        public static void setListViewHeightBasedOnChildren (ListView listView)
        {
            if (listView.Adapter == null) {
                // pre-condition
                return;
            }

            int totalHeight = listView.PaddingTop + listView.PaddingBottom;
            for (int i = 0; i < listView.Count; i++) {
                View listItem = listView.Adapter.GetView (i, null, listView);
                if (listItem.GetType () == typeof(ViewGroup)) {
                    listItem.LayoutParameters = new LinearLayout.LayoutParams (ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
                }
                listItem.Measure (0, 0);
                totalHeight += listItem.MeasuredHeight;
            }

            listView.LayoutParameters.Height = totalHeight + (listView.DividerHeight * (listView.Count - 1));
        }
    }
}

Obrigado @DougW, isso me tirou de uma situação difícil quando tive que trabalhar com o OtherPeople'sCode. :-)

Phil Ryan
fonte
Uma pergunta: quando você chamou o setListViewHeightBasedOnChildren()método? Porque não funciona o tempo todo para mim.
Alexandre D.
@AlexandreD, você chama Utility.setListViewHeightBasedOnChildren (yourListView) quando cria a lista de itens. ou seja, certifique-se de ter criado sua lista primeiro! Eu descobri que estava fazendo uma lista e até exibia meus itens no Console.WriteLine ... mas, de alguma forma, os itens estavam no escopo errado. Depois que eu descobri isso e garanti que tinha o escopo certo para minha lista, isso funcionou como um encanto.
Phil Ryan
O fato é que minhas listas são criadas no meu ViewModel. Como posso esperar que minhas listas sejam criadas em minha exibição antes de qualquer chamada para o Utility.setListViewHeightBasedOnChildren (yourListView)?
Alexandre D.
3

Esta é a única coisa que funcionou para mim:

no pirulito em diante, você pode usar

yourtListView.setNestedScrollingEnabled(true);

Isso habilita ou desabilita a rolagem aninhada para esta exibição. Se você precisar de compatibilidade com versões anteriores do sistema operacional, precisará usar o RecyclerView.

JackA
fonte
Isso não teve nenhum efeito nas minhas exibições de lista na API 22 em um DialogFragment em uma atividade AppCompat. Eles ainda entram em colapso. A resposta de @Phil Ryan trabalhou com um pouco de modificação.
Hakan Çelik MK1
3

Antes não era possível. Porém, com o lançamento das novas bibliotecas Appcompat e Design, isso pode ser alcançado.

Você só precisa usar o NestedScrollView https://developer.android.com/reference/android/support/v4/widget/NestedScrollView.html

Não sei que funcionará com o Listview ou não, mas funciona com o RecyclerView.

Fragmento de código:

<android.support.v4.widget.NestedScrollView 
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v7.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

</android.support.v4.widget.NestedScrollView>
Shashank Kapsime
fonte
Legal, eu não tentei, então não posso confirmar que um ListView interno não entra em colapso, mas parece que essa pode ser a intenção. Profundamente entristecido por não ver Romain Guy no
livro de
11
Isso funciona com o recyclerView, mas não com o listView. Thanx
IgniteCoders
2

Ei, eu tive um problema semelhante. Eu queria exibir uma exibição de lista que não rolasse e descobri que manipular os parâmetros funcionava, mas era ineficiente e se comportaria de maneira diferente em dispositivos diferentes. Como resultado, esse é um pedaço do meu código de programação que realmente faz isso com muita eficiência .

db = new dbhelper(this);

 cursor = db.dbCursor();
int count = cursor.getCount();
if (count > 0)
{    
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.layoutId);
startManagingCursor(YOUR_CURSOR);

YOUR_ADAPTER(**or SimpleCursorAdapter **) adapter = new YOUR_ADAPTER(this,
    R.layout.itemLayout, cursor, arrayOrWhatever, R.id.textViewId,
    this.getApplication());

int i;
for (i = 0; i < count; i++){
  View listItem = adapter.getView(i,null,null);
  linearLayout.addView(listItem);
   }
}

Nota: se você usar isso, notifyDataSetChanged();não funcionará como pretendido, pois as visualizações não serão redesenhadas. Faça isso se precisar de uma solução alternativa

adapter.registerDataSetObserver(new DataSetObserver() {

            @Override
            public void onChanged() {
                super.onChanged();
                removeAndRedrawViews();

            }

        });
PSchuette
fonte
2

Há dois problemas ao usar um ListView dentro de um ScrollView.

1- O ListView deve expandir totalmente para a altura dos filhos. este ListView resolva isso:

public class ListViewExpanded extends ListView
{
    public ListViewExpanded(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        setDividerHeight(0);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST));
    }
}

A altura do divisor deve ser 0, use preenchimento em linhas.

2- O ListView consome eventos de toque, para que o ScrollView não possa ser rolado normalmente. Este ScrollView resolve este problema:

public class ScrollViewInterceptor extends ScrollView
{
    float startY;

    public ScrollViewInterceptor(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e)
    {
        onTouchEvent(e);
        if (e.getAction() == MotionEvent.ACTION_DOWN) startY = e.getY();
        return (e.getAction() == MotionEvent.ACTION_MOVE) && (Math.abs(startY - e.getY()) > 50);
    }
}

Esta é a melhor maneira que encontrei para fazer o truque!

Todos
fonte
2

Uma solução que uso é adicionar todo o conteúdo do ScrollView (o que deve estar acima e abaixo do listView) como headerView e footerView no ListView.

Por isso, funciona como, também, o convertview é redimensionado como deveria ser.

brokedid
fonte
1

graças ao código de Vinay, aqui está o meu código para quando você não pode ter um listview dentro de um scrollview, mas precisa de algo assim

LayoutInflater li = LayoutInflater.from(this);

                RelativeLayout parent = (RelativeLayout) this.findViewById(R.id.relativeLayoutCliente);

                int recent = 0;

                for(Contatto contatto : contatti)
                {
                    View inflated_layout = li.inflate(R.layout.header_listview_contatti, layout, false);


                    inflated_layout.setId(contatto.getId());
                    ((TextView)inflated_layout.findViewById(R.id.textViewDescrizione)).setText(contatto.getDescrizione());
                    ((TextView)inflated_layout.findViewById(R.id.textViewIndirizzo)).setText(contatto.getIndirizzo());
                    ((TextView)inflated_layout.findViewById(R.id.textViewTelefono)).setText(contatto.getTelefono());
                    ((TextView)inflated_layout.findViewById(R.id.textViewMobile)).setText(contatto.getMobile());
                    ((TextView)inflated_layout.findViewById(R.id.textViewFax)).setText(contatto.getFax());
                    ((TextView)inflated_layout.findViewById(R.id.textViewEmail)).setText(contatto.getEmail());



                    RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);

                    if (recent == 0)
                    {
                        relativeParams.addRule(RelativeLayout.BELOW, R.id.headerListViewContatti);
                    }
                    else
                    {
                        relativeParams.addRule(RelativeLayout.BELOW, recent);
                    }
                    recent = inflated_layout.getId();

                    inflated_layout.setLayoutParams(relativeParams);
                    //inflated_layout.setLayoutParams( new RelativeLayout.LayoutParams(source));

                    parent.addView(inflated_layout);
                }

o relativeLayout permanece dentro de um ScrollView, para que tudo se torne rolável :)

max4ever
fonte
1

Aqui é pequena modificação na @djunod 's resposta que eu preciso para fazê-lo funcionar perfeitamente:

public static void setListViewHeightBasedOnChildren(ListView listView)
{
    ListAdapter listAdapter = listView.getAdapter();
    if(listAdapter == null) return;
    if(listAdapter.getCount() <= 1) return;

    int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);
    int totalHeight = 0;
    View view = null;
    for(int i = 0; i < listAdapter.getCount(); i++)
    {
        view = listAdapter.getView(i, view, listView);
        view.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
        totalHeight += view.getMeasuredHeight();
    }
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
    listView.requestLayout();
}
Eng.Fouad
fonte
: Olá, sua resposta funciona na maioria das vezes, mas não funciona se a exibição em lista tiver uma exibição de cabeçalho e uma exibição de pé. Pode verificar?
Hguser
Preciso de uma solução quando os itens da lista tiverem tamanhos variáveis, ou seja, alguma exibição de texto que se expanda por várias linhas. Alguma sugestão?
Khobaib
1

tente isso, isso funciona para mim, esqueci onde o encontrei, em algum lugar no estouro da pilha, não estou aqui para explicar por que não funciona, mas esta é a resposta :).

    final ListView AturIsiPulsaDataIsiPulsa = (ListView) findViewById(R.id.listAturIsiPulsaDataIsiPulsa);
    AturIsiPulsaDataIsiPulsa.setOnTouchListener(new ListView.OnTouchListener() 
    {
        @Override
        public boolean onTouch(View v, MotionEvent event) 
        {
            int action = event.getAction();
            switch (action) 
            {
                case MotionEvent.ACTION_DOWN:
                // Disallow ScrollView to intercept touch events.
                v.getParent().requestDisallowInterceptTouchEvent(true);
                break;

                case MotionEvent.ACTION_UP:
                // Allow ScrollView to intercept touch events.
                v.getParent().requestDisallowInterceptTouchEvent(false);
                break;
            }

            // Handle ListView touch events.
            v.onTouchEvent(event);
            return true;
        }
    });
    AturIsiPulsaDataIsiPulsa.setClickable(true);
    AturIsiPulsaDataIsiPulsa.setAdapter(AturIsiPulsaDataIsiPulsaAdapter);

EDIT !, finalmente descobri onde obtive o código. aqui ! : ListView dentro do ScrollView não está rolando no Android

Bhimbim
fonte
Isso mostra como evitar a perda da interação da exibição de rolagem, mas a pergunta era sobre como manter a altura da exibição de lista.
Carrinho abandonado
1

Embora os métodos setListViewHeightBasedOnChildren () sugeridos funcionem na maioria dos casos, em alguns casos, especialmente com muitos itens, notei que os últimos elementos não são exibidos. Então, decidi imitar uma versão simples do comportamento do ListView para reutilizar qualquer código do adaptador, aqui está a alternativa do ListView:

import android.content.Context;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ListAdapter;

public class StretchedListView extends LinearLayout {

private final DataSetObserver dataSetObserver;
private ListAdapter adapter;
private OnItemClickListener onItemClickListener;

public StretchedListView(Context context, AttributeSet attrs) {
    super(context, attrs);
    setOrientation(LinearLayout.VERTICAL);
    this.dataSetObserver = new DataSetObserver() {
        @Override
        public void onChanged() {
            syncDataFromAdapter();
            super.onChanged();
        }

        @Override
        public void onInvalidated() {
            syncDataFromAdapter();
            super.onInvalidated();
        }
    };
}

public void setAdapter(ListAdapter adapter) {
    ensureDataSetObserverIsUnregistered();

    this.adapter = adapter;
    if (this.adapter != null) {
        this.adapter.registerDataSetObserver(dataSetObserver);
    }
    syncDataFromAdapter();
}

protected void ensureDataSetObserverIsUnregistered() {
    if (this.adapter != null) {
        this.adapter.unregisterDataSetObserver(dataSetObserver);
    }
}

public Object getItemAtPosition(int position) {
    return adapter != null ? adapter.getItem(position) : null;
}

public void setSelection(int i) {
    getChildAt(i).setSelected(true);
}

public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
    this.onItemClickListener = onItemClickListener;
}

public ListAdapter getAdapter() {
    return adapter;
}

public int getCount() {
    return adapter != null ? adapter.getCount() : 0;
}

private void syncDataFromAdapter() {
    removeAllViews();
    if (adapter != null) {
        int count = adapter.getCount();
        for (int i = 0; i < count; i++) {
            View view = adapter.getView(i, null, this);
            boolean enabled = adapter.isEnabled(i);
            if (enabled) {
                final int position = i;
                final long id = adapter.getItemId(position);
                view.setOnClickListener(new View.OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        if (onItemClickListener != null) {
                            onItemClickListener.onItemClick(null, v, position, id);
                        }
                    }
                });
            }
            addView(view);

        }
    }
}
}
Alécio Carvalho
fonte
1

Todas essas respostas estão erradas !!! Se você estiver tentando colocar uma exibição de lista em uma exibição de rolagem, deve repensar seu design. Você está tentando colocar um ScrollView em um ScrollView. Interferir com a lista prejudicará o desempenho da lista. Foi projetado para ser assim pelo Android.

Se você realmente deseja que a lista esteja no mesmo pergaminho que os outros elementos, tudo o que você precisa fazer é adicionar os outros itens ao topo da lista, usando uma instrução switch simples no seu adaptador:

class MyAdapter extends ArrayAdapter{

    public MyAdapter(Context context, int resource, List objects) {
        super(context, resource, objects);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
         ViewItem viewType = getItem(position);

        switch(viewType.type){
            case TEXTVIEW:
                convertView = layouteInflater.inflate(R.layout.textView1, parent, false);

                break;
            case LISTITEM:
                convertView = layouteInflater.inflate(R.layout.listItem, parent, false);

                break;            }


        return convertView;
    }


}

O adaptador de lista pode lidar com tudo, pois apenas renderiza o que é visível.

TacoEater
fonte
0

Todo esse problema desapareceria se LinearLayout tivesse um método setAdapter, porque, quando você dissesse a alguém para usá-lo, a alternativa seria trivial.

Se você realmente deseja um ListView de rolagem dentro de outra exibição de rolagem, isso não ajudará, mas, caso contrário, isso lhe dará uma idéia.

Você precisa criar um adaptador personalizado para combinar todo o conteúdo que você deseja rolar e definir o adaptador do ListView para isso.

Não tenho código de exemplo à mão, mas se você quiser algo parecido.

<ListView/>

(other content)

<ListView/>

Então você precisa criar um adaptador que represente todo esse conteúdo. Os ListView / Adaptadores também são inteligentes o suficiente para lidar com diferentes tipos, mas você mesmo deve escrever o adaptador.

A API da interface do usuário do Android não é tão madura quanto praticamente tudo o que existe, por isso não tem as mesmas gentilezas que outras plataformas. Além disso, ao fazer algo no android, você precisa estar em uma mentalidade android (unix), na qual espera que, para fazer qualquer coisa, você provavelmente precisará montar a funcionalidade de partes menores e escrever um monte de seu próprio código para obtê-lo. trabalhos.

majinnaibu
fonte
0

Quando colocamos ListViewdentro, ScrollViewdois problemas surgem. Um ScrollViewmede seus filhos no modo NÃO ESPECIFICADO, então ListViewdefine sua própria altura para acomodar apenas um item (não sei por quê); outro é ScrollViewintercepta o evento de toque paraListView não rolar.

Mas nós pode colocar ListViewdentro ScrollViewcom alguma solução alternativa. Este post , por mim, explica a solução alternativa. Por essa solução alternativa, também podemos reter ListViewo recurso de reciclagem.

Durgadass S
fonte
0

Em vez de colocar a visualização de lista dentro do Scrollview, coloque o restante do conteúdo entre o visualizador de listas e a abertura do Scrollview como uma exibição separada e defina essa exibição como o cabeçalho da exibição da lista. Por fim, você acabará apenas na exibição de lista, encarregando-se do Scroll.

Saksham
fonte
0

Essa biblioteca é a solução mais fácil e rápida para o problema.

Kashif Nazar
fonte
-1

Aqui está minha versão do código que calcula a altura total da exibição em lista. Este funciona para mim:

   public static void setListViewHeightBasedOnChildren(ListView listView) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null || listAdapter.getCount() < 2) {
        // pre-condition
        return;
    }

    int totalHeight = 0;
    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(BCTDApp.getDisplaySize().width, View.MeasureSpec.AT_MOST);
    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);

    for (int i = 0; i < listAdapter.getCount(); i++) {
        View listItem = listAdapter.getView(i, null, listView);
        if (listItem instanceof ViewGroup) listItem.setLayoutParams(lp);
        listItem.measure(widthMeasureSpec, heightMeasureSpec);
        totalHeight += listItem.getMeasuredHeight();
    }

    totalHeight += listView.getPaddingTop() + listView.getPaddingBottom();
    totalHeight += (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight;
    listView.setLayoutParams(params);
    listView.requestLayout();
}
myforums
fonte