Listview android , marks items wrong

Asked

Viewed 384 times

0

I have a list where I mark the items as follows :view.setBackgroundResource(R.drawable.active_row), Every time I click it needs to set the color. When I click again, it arrow the dark line. So far so good, however, when I scroll down the list it "marks" the items that are below the list.

Follow the code snippet:

    final ArrayList<HashMap<String, String>> songsListData = new ArrayList<HashMap<String, String>>();
    SongsManager plm = new SongsManager();
    // get all songs from sdcard
    this.songsList = plm.getPlayList();
    // looping through playlist
    for (int i = 0; i < songsList.size(); i++) {
        // creating new HashMap
        HashMap<String, String> song = songsList.get(i);

        // adding HashList to ArrayList
        songsListData.add(song);
    }
    // Adding menuItems to ListView
     final ListAdapter adapter = new SimpleAdapter(this, songsListData,
            R.layout.playlist_item, new String[] { "songTitle" }, new int[] {
                    R.id.songTitle2 });


    setListAdapter(adapter);


    ListView lv = getListView();
    // listening to single listitem click
    lv.setOnItemClickListener(new OnItemClickListener() {
        int flag1 = 0;
        int flag2 = 0;

        int auxPosicao1 = -1;//primeira vez
        int auxPosicao2 = -1;//primeira vez

        @SuppressWarnings("unused")
        @Override
        public void onItemClick(AdapterView<?> parent, View view,
                int position, long id) {

            View lastRow = null;        
            if (lastRow != null) {
                lastRow.setBackgroundColor(0x00000000);
                }
            if (flag1==0 &&((auxPosicao1 == position || (auxPosicao1 == -1 && auxPosicao2==-1)))){
                flag1 = 1;
                auxPosicao1 = position;
                aux = position;
                view.setBackgroundResource(R.drawable.active_row);

            }else if(auxPosicao1==position)
            {           
                if (flag2 == 1){

                    aux = aux2;
                    auxPosicao1 = auxPosicao2;  

                    aux2 = -1;
                    flag2 =0;
                    auxPosicao2 =-1;

                }else{
                    aux = -1;
                    flag1=0;
                    auxPosicao1 =-1;

                }
                view.setBackgroundResource(R.drawable.active_row2);

            }else if(flag2 == 0 &&((auxPosicao2 == position || auxPosicao2 == -1))){
                aux2 = position;
                view.setBackgroundResource(R.drawable.active_row);
                flag2 = 1;
                auxPosicao2 = position;

            }else if(position == auxPosicao2){
                aux2 = -1;
                flag2 =0;
                auxPosicao2 =-1;
                view.setBackgroundResource(R.drawable.active_row2);
            }else{
                Toast.makeText(getBaseContext(), "Coming soon ...", Toast.LENGTH_SHORT).show();
            }
            //lastRow = view;
        }
    }); 
  • Diogo, I think that kind of logic needs to go through Adapter (give to Adapter the responsibility to "paint" the lines when clicked). I say this because the "recycling algorithm" of `Listview is the cause of this problem, of elements that you didn’t click are color when they shouldn’t. I’ll try to put together a code to help you, but it’s gonna get a little big.

  • blz put Adapter to help you...

1 answer

0


Edit

As the intention was to make a line selection algorithm, the previous solution did not fully meet, making the new solution simpler.

In the new solution, the Adapter has a SparseArray (could be a HashMap, but the SparseArray is more recommending on the Android platform).

For each position, the SparseArray or save 1 for the position if it is marked or 0 otherwise.

As a request, the second click unchecks the item, such as a checkbox.

public class TestBaseAdapter extends SimpleAdapter {

    // Armazena as linhas que possuem cliques, para marcação
    SparseArray<Integer> mCliques = new SparseArray<Integer>();

    // Usado para adicionar um valor a uma View, no metodo setTag
    int mResId;

    public TestBaseAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to) {
        super(context, data, resource, from, to);
        mResId = resource;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = super.getView(position, convertView, parent);

        updateBackground(view, position);

        // Guardo na View, sua posicao
        // Facilita ao fazer a limpeza manual do background caso seja necessario
        view.setTag(mResId, position);

        return view;
    }

    /***
     * Atualiza o background da View conforme a regra de cliques
     * @param view
     * @param position
     */
    void updateBackground(View view, int position) {
        // Se houve nao tem clique, entao remove o background
        if(mCliques.get(position, 0) == 0) {
            view.setBackgroundResource(0);
        } else { // Adiciona o background em caso positivo
            view.setBackgroundResource(R.color.primary);
        }
    }

    /***
     * Atualiza a ultima posicao clicada e o numero de cliques.
     * E atualiza o Background da view conforme o resultado
     * @param adapterView
     * @param view
     * @param position
     */
    public void updateClick(AdapterView adapterView, View view, int position) {

        // Se a posicao foi clicada e ja estava marcada, remove a marcação
        if(mCliques.get(position, -1) != -1) {
            mCliques.remove(position);
        } else { // Adiciona a marcação
            mCliques.put(position, 1);
        }

        // Podemos usar o "notifyDataSetChanged", com isso todas as Views
        // que estao visiveis serao reconstruidas
        // notifyDataSetChanged();

        // Ou podemos atualizar manualmente as visiveis, as demais
        // serao construídas pelo Adapter
        updateVisibleViews(adapterView);
    }

    void updateVisibleViews(AdapterView adapterView) {
        for(int i = 0, childCount = adapterView.getChildCount(); i < childCount; ++i) {
            View view = adapterView.getChildAt(i);
            int position = (Integer) view.getTag(mResId);

            updateBackground(view, position);
        }
    }

    /***
     * Retorna a lista dos indices dos itens selecionados
     * @return
     */
    public ArrayList<Integer> getSelectedItems() {
        ArrayList<Integer> selecao = new ArrayList<Integer>(mCliques.size());

        for(int i = 0, size = mCliques.size(); i < size; ++i) {
            selecao.add(mCliques.keyAt(i));
        }

        return selecao;
    }
}

To accomplish this task, it is necessary to create a subclass of the SimpleAdapter, because we need to incorporate the "coloring" rule into the Adapter, making it simpler and giving responsibility to the right element.

I made an example of how it can be done, my solution is just a basis, just adapting to your specific case.

Since I don’t have the same data as you, I had to improvise:

This is the construction of Adapter and the data of ListView in my Activity:

final ArrayList<HashMap<String, String>> songsListData = new ArrayList<HashMap<String, String>>();

// Construindo dados fake, apenas para ter um ListView cheio
for (int i = 0; i < 100; i++) {
    // creating new HashMap
    HashMap<String, String> song = new HashMap<String, String>();

    song.put("NUMERO", "" + i);

    songsListData.add(song);
}

ListView lv = (ListView) view.findViewById(R.id.fpa_list);

// Crio o meu Adapter customizado, que herda de SimpleAdapter
mAdapter = new TestBaseAdapter(getActivity(), songsListData, R.layout.playlist_item, new String[]{"NUMERO"}, new int[] {android.R.id.text1});

lv.setAdapter(mAdapter);

// Seto um OnItemClickListener apenas para delegar ao Adapter a atualizacao
// do ultimo item clicado e do numero de cliques
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        mAdapter.updateClick(parent, view, position);
    }
});

Now comes the SimpleAdapter I did to apply the click rule:

public class TestBaseAdapter extends SimpleAdapter {

    // Armazena a quantidade de clicks em uma determinada linha e sua posicao
    int mUltimaPosicaoClicada = -1, mNumeroCliques = 0;

    // Usado para adicionar um valor a uma View, no metodo setTag
    int mResId;

    public TestBaseAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to) {
        super(context, data, resource, from, to);
        mResId = resource;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = super.getView(position, convertView, parent);

        updateBackground(view, position);

        // Guardo na View, sua posicao
        // Facilita ao fazer a limpeza manual do background caso seja necessario
        view.setTag(mResId, position);

        return view;
    }

    /***
     * Atualiza o background da View conforme a regra de cliques
     * @param view
     * @param position
     */
    void updateBackground(View view, int position) {
        if(position == mUltimaPosicaoClicada) {
            switch (mNumeroCliques) {
                case 0:
                    view.setBackgroundResource(0);
                case 1:
                    view.setBackgroundResource(R.color.primary);
                    break;
                case 2:
                default:
                    view.setBackgroundResource(R.color.black_87p);
                    break;
            }
        } else {
            view.setBackgroundResource(0);
        }
    }

    /***
     * Atualiza a ultima posicao clicada e o numero de cliques.
     * E atualiza o Background da view conforme o resultado
     * @param adapterView
     * @param view
     * @param position
     */
    public void updateClick(AdapterView adapterView, View view, int position) {
        // Logica para guardar o numero de cliques e o ultimo item clicado
        if(mUltimaPosicaoClicada == position) {
            mNumeroCliques++;
        } else {
            mUltimaPosicaoClicada = position;
            mNumeroCliques = 1;
        }

        // Podemos usar o "notifyDataSetChanged", com isso todas as Views
        // que estao visiveis serao reconstruidas
        // notifyDataSetChanged();

        // Ou podemos atualizar manualmente as visiveis, as demais
        // serao construídas pelo Adapter
        updateVisibleViews(adapterView);
    }

    void updateVisibleViews(AdapterView adapterView) {
        for(int i = 0, childCount = adapterView.getChildCount(); i < childCount; ++i) {
            View view = adapterView.getChildAt(i);
            int position = (Integer) view.getTag(mResId);

            updateBackground(view, position);
        }
    }
}

I would like to make a comment:

In the method updateClick of TestAdapter, I left the update of the items can be done in two ways:

  1. Use the notifyDataSetChanged to force the update of all View's visible by the ListView. This will force the method getView be called again for all visible items. It may not be a good alternative if the construction of the items is complex, causing an "overhead".
  2. Manually update the View's that are visible. This is much more performative, but it involves adding extra logic. In this case I added the position of the item as a tag in the View.setTag within the method getView, to make it easy to update later in the method updateVisibleViews in the TestAdapter.
  • It worked, but this way it only allows to score one at a time, I need to select more than one option, I’m working to get it done, but if you can help me I would be grateful anyway, I will accept the answer, because it has helped a lot...

  • @Diogoodelli I had thought about it. My first attempt was going to be that way, but then I saw that his logic was not like that. I can help you yes, the idea is to exchange the two flags for one SparseArray, but what would be the logic to cancel?

  • I have a list, each item in the list is an index, which I will use later.When I click on the line, it is the first word, if I click on this line again, it needs to be unchecked. If I click another row with the first row marked, it needs to select the other row as well, which will be a second Indice. That is, if the line is checked and it is clicked, it clears, as if it were a checkbox... I don’t know if I was clear.

  • if you help this application link posted to the playstore with this bug, https://play.google.com/store/apps/details?id=com.ssMultiTrack.android ...

  • @Diogoodelli, updated, see if it helps like I did. You can change the SparseArray<Integer> for SparseArray<Boolean> to get even better. Only make the necessary adaptations.

  • Man, thank you very much, it worked perfectly, this is my course completion work, I’m picking up some for the listviews, I’m going to put your name on the dedications...kkk vlw

  • @Diogoodelli, very cool :) My final project was also an Android app. I worked hard, I learned a lot. This is a good motivation to learn this platform, which is very rich and I learn more every day. Good luck in the studies and try to follow the tutorials of Android, they have a very rich material.

  • really, a lot to do to learn, I intend to specialize in android, and again thank you very much for the help...

Show 3 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.