from - world of listview android




Como atualizar o listview do Android? (17)

Como atualizar um Android ListView depois de adicionar / excluir dados dinâmicos?


Por favor, ignore todas as respostas invalidate() , invalidateViews() , requestLayout() , ... para esta questão.

A coisa certa a fazer (e felizmente também marcada como resposta correta) é chamar notifyDataSetChanged() em seu adaptador .

Solução de problemas

Se chamar notifyDataSetChanged() não funcionar, todos os métodos de layout também não ajudarão. Acredite, o ListView foi atualizado corretamente. Se você não encontrar a diferença, precisará verificar de onde vêm os dados no seu adaptador.

Se esta é apenas uma coleção que você está mantendo na memória, verifique se você realmente excluiu ou adicionou o (s) item (ns) à coleção antes de chamar o notifyDataSetChanged() .

Se você estiver trabalhando com um banco de dados ou back-end de serviço, terá que chamar o método para recuperar as informações novamente (ou manipular os dados da memória) antes de chamar o notifyDataSetChanged() .

A coisa é que este notifyDataSetChanged só funciona se o conjunto de dados foi alterado. Então esse é o lugar para procurar se você não encontrar mudanças. Depure se necessário.

ArrayAdapter vs BaseAdapter

Eu achei que trabalhar com um adaptador que permite gerenciar a coleção, como um BaseAdapter funciona melhor. Alguns adaptadores, como o ArrayAdapter, já gerenciam sua própria coleção, dificultando a obtenção da coleção adequada para atualizações. É realmente apenas uma desnecessária camada extra de dificuldade na maioria dos casos.

Thread de interface do usuário

É verdade que isso deve ser chamado a partir do thread da interface do usuário. Outras respostas têm exemplos de como conseguir isso. No entanto, isso é necessário apenas se você estiver trabalhando com essas informações fora do thread da interface do usuário. Isso é de um serviço ou de um segmento não da interface do usuário. Em casos simples, você atualizará seus dados a partir de um clique de botão ou outra atividade / fragmento. Então, ainda dentro do segmento da interface do usuário. Não há necessidade de sempre colocar esse runOnUiTrhead em.

Projeto de exemplo rápido

Pode ser encontrado em https://github.com/hanscappelle/so-2250770.git . Apenas clone e abra o projeto no Android Studio (gradle). Este projeto tem uma MainAcitivity construindo um ListView com todos os dados aleatórios. Essa lista pode ser atualizada usando o menu de ação.

A implementação do adaptador que criei para este exemplo ModelObject expõe a coleta de dados

public class MyListAdapter extends BaseAdapter {

    /**
     * this is our own collection of data, can be anything we 
     * want it to be as long as we get the abstract methods 
     * implemented using this data and work on this data 
     * (see getter) you should be fine
     */
    private List<ModelObject> mData;

    /**
     * our ctor for this adapter, we'll accept all the things 
     * we need here
     *
     * @param mData
     */
    public MyListAdapter(final Context context, final List<ModelObject> mData) {
        this.mData = mData;
        this.mContext = context;
    }

    public List<ModelObject> getData() {
        return mData;
    }

    // implement all abstract methods here
}

Código da MainActivity

public class MainActivity extends Activity {

    private MyListAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ListView list = (ListView) findViewById(R.id.list);

        // create some dummy data here
        List<ModelObject> objects = getRandomData();
        // and put it into an adapter for the list
        mAdapter = new MyListAdapter(this, objects);
        list.setAdapter(mAdapter);

        // mAdapter is available in the helper methods below and the 
        // data will be updated based on action menu interactions

        // you could also keep the reference to the android ListView 
        // object instead and use the {@link ListView#getAdapter()} 
        // method instead. However you would have to cast that adapter 
        // to your own instance every time
    }

    /**
     * helper to show what happens when all data is new
     */
    private void reloadAllData(){
        // get new modified random data
        List<ModelObject> objects = getRandomData();
        // update data in our adapter
        mAdapter.getData().clear();
        mAdapter.getData().addAll(objects);
        // fire the event
        mAdapter.notifyDataSetChanged();
    }

    /**
     * helper to show how only changing properties of data 
     * elements also works
     */
    private void scrambleChecked(){
        Random random = new Random();
        // update data in our adapter, iterate all objects and 
        // resetting the checked option
        for( ModelObject mo : mAdapter.getData()) {
            mo.setChecked(random.nextBoolean());
        }
        // fire the event
        mAdapter.notifyDataSetChanged();
    }
}

Mais Informações

Outro post interessante sobre o poder de listViews é encontrado aqui: http://www.vogella.com/articles/AndroidListView/article.html


Além disso, você pode usar isto:

myListView.invalidateViews();

As soluções propostas por pessoas neste post funcionam ou não dependendo principalmente da versão Android do seu dispositivo. Por exemplo, para usar o método AddAll você tem que colocar android: minSdkVersion = "10" no seu dispositivo Android.

Para resolver estas questões para todos os dispositivos eu criei meu próprio método no meu adaptador e uso dentro do método add e remove herda do ArrayAdapter que atualiza seus dados sem problemas.

Meu Código: Usando minha própria classe de dados RaceResult, você usa seu próprio modelo de dados.

ResultGpRowAdapter.java

public class ResultGpRowAdapter extends ArrayAdapter<RaceResult> {

    Context context;
    int resource;
    List<RaceResult> data=null;

        public ResultGpRowAdapter(Context context, int resource, List<RaceResult> objects)           {
        super(context, resource, objects);

        this.context = context;
        this.resource = resource;
        this.data = objects;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        ........
        }

        //my own method to populate data           
        public void myAddAll(List<RaceResult> items) {

        for (RaceResult item:items){
            super.add(item);
        }
    }

ResultsGp.java

public class ResultsGp extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {

    ...........
    ...........
    ListView list = (ListView)findViewById(R.id.resultsGpList); 

    ResultGpRowAdapter adapter = new ResultGpRowAdapter(this,  R.layout.activity_result_gp_row, new ArrayList<RaceResult>()); //Empty data

   list.setAdapter(adapter);

   .... 
   ....
   ....
   //LOAD a ArrayList<RaceResult> with data

   ArrayList<RaceResult> data = new ArrayList<RaceResult>();
   data.add(new RaceResult(....));
   data.add(new RaceResult(....));
   .......

   adapter.myAddAll(data); //Your list will be udpdated!!!


Considere que você passou uma lista para o seu adaptador.
Usar:

list.getAdapter().notifyDataSetChanged()

para atualizar sua lista.


Depois de excluir dados da exibição de lista, você precisa chamar refreshDrawableState() . Aqui está o exemplo:

final DatabaseHelper db = new DatabaseHelper (ActivityName.this);

db.open();

db.deleteContact(arg3);

mListView.refreshDrawableState();

db.close();

e deleteContact método na classe DatabaseHelper será um pouco parece

public boolean deleteContact(long rowId) {

   return db.delete(TABLE_NAME, BaseColumns._ID + "=" + rowId, null) > 0;

}

Eu era o mesmo quando, em um fragmento, eu queria preencher um ListView (em um único TextView) com o endereço mac de dispositivos BLE verificados ao longo do tempo.

O que eu fiz foi isso:

public class Fragment01 extends android.support.v4.app.Fragment implements ...
{
    private ListView                listView;
    private ArrayAdapter<String>    arrayAdapter_string;

...

@Override
public void onActivityCreated(Bundle savedInstanceState)
{
    ...
    this.listView= (ListView) super.getActivity().findViewById(R.id.fragment01_listView);
    ...
    this.arrayAdapter_string= new ArrayAdapter<String>(super.getActivity(), R.layout.dispositivo_ble_item, R.id.fragment01_item_textView_titulo);
    this.listView.setAdapter(this.arrayAdapter_string);
}


@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord)
{
    ...
    super.getActivity().runOnUiThread(new RefreshListView(device));
}


private class RefreshListView implements Runnable
{
    private BluetoothDevice bluetoothDevice;

    public RefreshListView(BluetoothDevice bluetoothDevice)
    {
        this.bluetoothDevice= bluetoothDevice;
    }

    @Override
    public void run()
    {
        Fragment01.this.arrayAdapter_string.add(new String(bluetoothDevice.toString()));
        Fragment01.this.arrayAdapter_string.notifyDataSetChanged();
    }
}

Em seguida, o ListView começou a preencher dinamicamente com o endereço mac dos dispositivos encontrados.


Eu não era capaz de obter notifyDataSetChanged () para trabalhar em atualizar meu SimpleAdapter, então tentei primeiro remover todas as vistas que estavam anexadas ao layout pai usando removeAllViews (), adicionando o ListView, e isso funcionou, permitindo-me atualizar o UI:

LinearLayout results = (LinearLayout)findViewById(R.id.results);
ListView lv = new ListView(this);
ArrayList<HashMap<String,String>> list = new ArrayList<HashMap<String,String>>();
SimpleAdapter adapter = new SimpleAdapter( this, list, R.layout.directory_row, 
                new String[] { "name", "dept" }, new int[] { R.id.name, R.id.dept } );

for (...) { 
    HashMap<String, String> map = new HashMap<String, String>();
    map.put("name", name);
    map.put("dept", dept);
    list.add(map);
}

lv.setAdapter(adapter);
results.removeAllViews();     
results.addView(lv);

Eu tenho alguns problemas com a atualização dinâmica do meu listview.

Chame notifyDataSetChanged () no seu adaptador.

Alguns detalhes adicionais sobre como / quando chamar notifyDataSetChanged () podem ser visualizados neste vídeo do Google I / O.

notifyDataSetChanged () não funcionou corretamente no meu caso [chamei o notifyDataSetChanged de outra classe]. Apenas no caso eu editei o ListView na atividade em execução (Thread). Esse vídeo graças a Christopher deu a dica final.

Na minha segunda aula eu usei

Runnable run = new Runnable(){
     public void run(){
         contactsActivity.update();
     }
};
contactsActivity.runOnUiThread(run);

para acessar o update () da minha atividade. Esta atualização inclui

myAdapter.notifyDataSetChanged();

para informar ao Adaptador para atualizar a visualização. Funcionou bem, tanto quanto posso dizer.


Eu tinha uma ArrayList que queria exibir em um listview. ArrayList continha elementos do mysql. Eu substituí o método onRefresh e, nesse método, usei tablelayout.removeAllViews (); e, em seguida, repetiu o processo para obter dados novamente do banco de dados. Mas antes disso, certifique-se de limpar sua ArrayList ou qualquer outra estrutura de dados, ou então os novos dados serão anexados ao antigo.


Para mim, após alterar informações no banco de dados sql, nada poderia atualizar a exibição de lista (para ser uma exibição de lista expansível específica), portanto, se notifyDataSetChanged () não ajudar, você pode tentar limpar sua lista primeiro e adicioná-la novamente após a chamada notifyDataSetChanged (). Por exemplo

private List<List<SomeNewArray>> arrayList;
List<SomeNewArray> array1= getArrayList(...);
List<SomeNewArray> array2= getArrayList(...);
arrayList.clear();
arrayList.add(array1);
arrayList.add(array2);
notifyDataSetChanged();

Espero que faça sentido para você.


Se eu falei sobre o meu cenário aqui, as respostas acima não funcionaram porque eu tinha uma atividade que mostra a lista de valores de db junto com um botão delete e quando um botão delete é pressionado, eu queria deletar esse item da lista.

O legal foi que eu não usei a visualização de reciclador, mas uma visualização de lista simples e aquela visualização de lista inicializada na classe do adaptador. Portanto, chamar o notifyDataSetChanged() não fará nada dentro da classe do adaptador e até mesmo na classe de atividade na qual o objeto do adaptador foi inicializado porque o método delete estava na classe do adaptador.

Assim, a solução foi remover o objeto do adaptador no método getView classe do adaptador (para excluir apenas esse objeto específico, mas se você quiser excluir tudo, chame clear() ).

Para você ter uma ideia, qual era o meu código,

public class WordAdapter extends ArrayAdapter<Word> {
  Context context;

  public WordAdapter(Activity context, ArrayList<Word> words) {}
    //.......

    @NonNull
    @Override
    public View getView(final int position, View convertView, ViewGroup group) {
      //.......
     ImageButton deleteBt = listItemView.findViewById(R.id.word_delete_bt);
     deleteBt.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
             if (vocabDb.deleteWord(currentWord.id)) {
                //.....
             } else{
                //.....
             }
             remove(getItem(position)); // <---- here is the trick ---<
             //clear() // if you want to clear everything
         }
    });
 //....

Nota: aqui os métodos remove() e getItem() são getItem() da classe Adapter.

  • remove() - para remover o item específico que é clicado
  • getItem(position) - é obter o item (aqui, meu objeto do Word que adicionei à lista) da posição clicada.

É assim que eu configuro o adaptador para o listview na classe de atividade,

    ArrayList<Word> wordList = new ArrayList();
    WordAdapter adapter = new WordAdapter(this, wordList);

    ListView list_view = (ListView) findViewById(R.id.activity_view_words);
    list_view.setAdapter(adapter);

Se você deseja atualizar a lista de UI de um serviço, torne o adaptador estático em sua atividade principal e faça o seguinte:

@Override
public void onDestroy() {
    if (MainActivity.isInFront == true) {
        if (MainActivity.adapter != null) {
            MainActivity.adapter.notifyDataSetChanged();
        }

        MainActivity.listView.setAdapter(MainActivity.adapter);
    }
}    

Se você está indo por linhas de guia android e você está usando o ContentProviders para obter dados do Database de Database e você está exibindo no ListView usando o CursorLoader e CursorAdapters , então todas as alterações aos dados relacionados serão automaticamente refletidas no ListView.

Seu getContext().getContentResolver().notifyChange(uri, null); no cursor no ContentProvider será suficiente para refletir as alterações. Não há necessidade de um trabalho extra.

Mas quando você não estiver usando tudo isso, precisará informar o adaptador quando o conjunto de dados estiver sendo alterado. Também é necessário re-preencher / recarregar seu conjunto de dados (digamos, list) e, em seguida, você precisa chamar notifyDataSetChanged() no adaptador.

notifyDataSetChanged() não funcionará se não houver alterações no datset. Aqui está o comentário acima do método em docs-

/**
 * Notifies the attached observers that the underlying data has been changed
 * and any View reflecting the data set should refresh itself.
 */

Se você quiser manter sua posição de rolagem quando atualizar, e você pode fazer isso:

if (mEventListView.getAdapter() == null) {
    EventLogAdapter eventLogAdapter = new EventLogAdapter(mContext, events);
    mEventListView.setAdapter(eventLogAdapter);
} else {
    ((EventLogAdapter)mEventListView.getAdapter()).refill(events);
}

public void refill(List<EventLog> events) {
    mEvents.clear();
    mEvents.addAll(events);
    notifyDataSetChanged();
}

Para obter informações detalhadas, consulte Android ListView: mantenha sua posição de rolagem ao atualizar .


Você precisa usar um único objeto dessa lista para exibir os dados que está inflando no ListView . Se a referência for alterada, então notifyDataSetChanged() não funciona. Sempre que você estiver excluindo elementos da exibição de lista, exclua-os da lista que está usando, seja ArrayList <> ou Something else, em seguida, call notifyDataSetChanged() no objeto Your adapter. classe.

Então veja aqui como eu consegui isso no meu adaptador veja abaixo

public class CountryCodeListAdapter extends BaseAdapter implements OnItemClickListener{

private Context context;
private ArrayList<CountryDataObject> dObj;
private ViewHolder holder;
private Typeface itemFont;
private int selectedPosition=-1;
private ArrayList<CountryDataObject> completeList;

public CountryCodeListAdapter(Context context, ArrayList<CountryDataObject> dObj) {
    this.context = context;
    this.dObj=dObj;
    completeList=new  ArrayList<CountryDataObject>();
    completeList.addAll(dObj);
    itemFont=Typeface.createFromAsset(context.getAssets(), "CaviarDreams.ttf");
}

@Override
public int getCount() {
    return dObj.size();
}

@Override
public Object getItem(int position) {
    return dObj.get(position);
}

@Override
public long getItemId(int position) {
    return position;
}
@Override
public View getView(int position, View view, ViewGroup parent) {
    if(view==null){
        holder = new ViewHolder();
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        view = inflater.inflate(R.layout.states_inflator_layout, null);
        holder.textView = ((TextView)view.findViewById(R.id.stateNameInflator));
        holder.checkImg=(ImageView)view.findViewById(R.id.checkBoxState);
        view.setTag(holder);
    }else{
        holder = (ViewHolder) view.getTag();
    }
    holder.textView.setText(dObj.get(position).getCountryName());
    holder.textView.setTypeface(itemFont);

    if(position==selectedPosition)
     {
         holder.checkImg.setImageResource(R.drawable.check);
     }
     else
     {
         holder.checkImg.setImageResource(R.drawable.uncheck);
     }
    return view;
}
private class ViewHolder{
    private TextView textView;
    private ImageView checkImg;
}

public void getFilter(String name) {
    dObj.clear();
    if(!name.equals("")){
    for (CountryDataObject item : completeList) {
        if(item.getCountryName().toLowerCase().startsWith(name.toLowerCase(),0)){
            dObj.add(item);
        }
    }
    }
    else {
        dObj.addAll(completeList);
    }
    selectedPosition=-1;
    notifyDataSetChanged();
    notifyDataSetInvalidated(); 
}

@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
        long id) {
    Registration reg=(Registration)context;
    selectedPosition=position;
    reg.setSelectedCountryCode("+"+dObj.get(position).getCountryCode());
    notifyDataSetChanged();
}
}

na outra opção é onWindowFocusChanged método, mas com certeza é sensível e precisa de alguma codificação extra para quem está interessado

 override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)

       // some controls needed
        programList = usersDBHelper.readProgram(model.title!!)
        notesAdapter = DailyAdapter(this, programList)
        notesAdapter.notifyDataSetChanged()
        listview_act_daily.adapter = notesAdapter
    }




refresh