Android: elementos ListView com vários botões clicáveis

182

Eu tenho um ListViewonde cada elemento da lista contém um TextView e dois botões diferentes. Algo assim:

ListView
--------------------
[Text]
[Button 1][Button 2]
--------------------
[Text]
[Button 1][Button 2]
--------------------
... (and so on) ...

Com este código, posso criar um OnItemClickListenerpara o item inteiro:

listView.setOnItemClickListener(new OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> list, View view, int position, long id) {
        Log.i(TAG, "onListItemClick: " + position);

        }

    }
});

No entanto, não quero que o item inteiro seja clicável, mas apenas os dois botões de cada elemento da lista.

Portanto, minha pergunta é: como implementar um onClickListener para esses dois botões com os seguintes parâmetros:

  • int button (em qual botão do elemento foi clicado)
  • int position (que é o elemento na lista em que o clique do botão aconteceu)

Atualização: Encontrei uma solução conforme descrito na minha resposta abaixo. Agora posso clicar / tocar no botão na tela de toque. No entanto, não consigo selecioná-lo manualmente com o trackball. Ele sempre seleciona o item inteiro da lista e, a partir daí, vai diretamente para o próximo item da lista, ignorando os botões, mesmo que eu tenha definido .setFocusable(true)e setClickable(true)pressionado os botões getView().

Também adicionei esse código ao meu adaptador de lista personalizado:

@Override
public boolean  areAllItemsEnabled() {
    return false;           
}

@Override
public boolean isEnabled(int position) {
        return false;
}

Isso faz com que nenhum item da lista seja mais selecionável. Mas isso não ajudou em tornar os botões aninhados selecionáveis.

Alguém tem uma ideia?

znq
fonte
Ainda são necessários?
Stuart Axon
Se você olhar para o código BaseAdapter, verá que areAllItemsEnabled () e isEnabled () são codificados apenas como true, tornando-os espaços reservados simples, sem lógica.
Artem Russakovskii
E se eu quiser usar um SimpleCursorAdapter? Preciso fazer um adaptador personalizado estender o simplecursoradapter?
oratis

Respostas:

150

A solução para isso é realmente mais fácil do que eu pensava. Você pode simplesmente adicionar no getView()método do seu adaptador personalizado um setOnClickListener () para os botões que você está usando.

Todos os dados associados ao botão devem ser adicionados myButton.setTag()no getView()e podem ser acessados ​​no onClickListener viaview.getTag()

Publiquei uma solução detalhada no meu blog como tutorial.

znq
fonte
2
Fixo. Obrigado por relatórios :-)
znq
quão exatamente você obteve o curItem.url da consulta, seria mais específico?
oratis 7/12
1
Obrigado znq, Isso foi muito útil para mim ... Economizou um tempo.
Nandagopal T
Assista a esta palestra às 11:39. Há um excelente exemplo: youtu.be/wDBM6wVEO70?t=11m39s Em seguida, faça o que o @znq diz ... setTag () quando o convertView == null e getTag () no onClick () método do onClickListener () do botão. Obrigado!
precisa saber é o seguinte
12
seria ótimo ter respostas no próprio SO e não apontar para outra página. O link do blog está morto!
gsb
63

Esta é uma espécie de resposta do apêndice @ znq ...

Existem muitos casos em que você deseja saber a posição da linha de um item clicado E deseja saber em qual exibição da linha foi tocada. Isso será muito mais importante nas interfaces de usuário do tablet.

Você pode fazer isso com o seguinte adaptador personalizado:

private static class CustomCursorAdapter extends CursorAdapter {

    protected ListView mListView;

    protected static class RowViewHolder {
        public TextView mTitle;
        public TextView mText;
    }

    public CustomCursorAdapter(Activity activity) {
        super();
        mListView = activity.getListView();
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        // do what you need to do
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        View view = View.inflate(context, R.layout.row_layout, null);

        RowViewHolder holder = new RowViewHolder();
        holder.mTitle = (TextView) view.findViewById(R.id.Title);
        holder.mText = (TextView) view.findViewById(R.id.Text);

        holder.mTitle.setOnClickListener(mOnTitleClickListener);
        holder.mText.setOnClickListener(mOnTextClickListener);

        view.setTag(holder);

        return view;
    }

    private OnClickListener mOnTitleClickListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            final int position = mListView.getPositionForView((View) v.getParent());
            Log.v(TAG, "Title clicked, row %d", position);
        }
    };

    private OnClickListener mOnTextClickListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            final int position = mListView.getPositionForView((View) v.getParent());
            Log.v(TAG, "Text clicked, row %d", position);
        }
    };
}
greg7gkb
fonte
6
ou você também pode usar algo como este ListView mListView = (ListView) v.getParent (). getParent ();
max4ever
1
Como posso obter a posição se eu usar o adaptador em uma classe separada. Simplesmente usei a posição dentro do OnClickListener, algo como this_c.get (position). Ele sugere fazer a posição final no método getView do adaptador. Se eu fizer isso mudar, a posição que recebo é a mesma para todos os itens da lista. Você poderia me sugerir como resolver isso.
Manikandan
Isso porque quando você usa o parâmetro de posição do método getView dá-lhe a posição do item da lista que é criada mais recentemente, mas não o que você clicou
Archie.bpgc
@ Manikandan: Se você estiver usando o método getView () do adaptador, você já terá a posição passada nos parâmetros do método. Nesse caso, você pode usar algo como setTag () para armazenar as informações de posição.
22612 greg7gkb
1
Estou usando seu código no meu aplicativo, mas o evento click é chamado após um atraso ou quando tento rolar a lista. Alguma idéia de por que isso está acontecendo?
Abdullah Umer
22

Para futuros leitores:

Para selecionar manualmente os botões com o trackball, use:

myListView.setItemsCanFocus(true);

E para desativar o foco em todos os itens da lista:

myListView.setFocusable(false);
myListView.setFocusableInTouchMode(false);
myListView.setClickable(false);

Funciona bem para mim, posso clicar nos botões com tela sensível ao toque e também permite focar um clique usando o teclado

Fabricio PH
fonte
2
Apenas este me ajudou! Muito obrigado!
Onur
Que tal um ExpandableListView, eu tenho várias visualizações no item, só quero que as visualizações no filho sejam clicáveis, obrigado.
twlkyao
12

Não tenho muita experiência do que os usuários acima, mas enfrentei o mesmo problema e resolvi-o com a solução abaixo

<Button
        android:id="@+id/btnRemove"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/btnEdit"
        android:layout_weight="1"
        android:background="@drawable/btn"
        android:text="@string/remove" 
        android:onClick="btnRemoveClick"
        />

Evento btnRemoveClick Click

public void btnRemoveClick(View v)
{
    final int position = listviewItem.getPositionForView((View) v.getParent()); 
    listItem.remove(position);
    ItemAdapter.notifyDataSetChanged();

}
Bhavin Chauhan
fonte
solução interessante
sherpya
9

Provavelmente você descobriu como fazê-lo, mas pode ligar

ListView.setItemsCanFocus(true)

e agora seus botões vão chamar a atenção

Rafal
fonte
5

Não tenho a certeza de ser a melhor maneira, mas funciona bem e todo o código permanece no seu ArrayAdapter.

package br.com.fontolan.pessoas.arrayadapter;

import java.util.List;

import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import br.com.fontolan.pessoas.R;
import br.com.fontolan.pessoas.model.Telefone;

public class TelefoneArrayAdapter extends ArrayAdapter<Telefone> {

private TelefoneArrayAdapter telefoneArrayAdapter = null;
private Context context;
private EditText tipoEditText = null;
private EditText telefoneEditText = null;
private ImageView deleteImageView = null;

public TelefoneArrayAdapter(Context context, List<Telefone> values) {
    super(context, R.layout.telefone_form, values);
    this.telefoneArrayAdapter = this;
    this.context = context;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View view = inflater.inflate(R.layout.telefone_form, parent, false);

    tipoEditText = (EditText) view.findViewById(R.id.telefone_form_tipo);
    telefoneEditText = (EditText) view.findViewById(R.id.telefone_form_telefone);
    deleteImageView = (ImageView) view.findViewById(R.id.telefone_form_delete_image);

    final int i = position;
    final Telefone telefone = this.getItem(position);
    tipoEditText.setText(telefone.getTipo());
    telefoneEditText.setText(telefone.getTelefone());

    TextWatcher tipoTextWatcher = new TextWatcher() {
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        @Override
        public void afterTextChanged(Editable s) {
            telefoneArrayAdapter.getItem(i).setTipo(s.toString());
            telefoneArrayAdapter.getItem(i).setIsDirty(true);
        }
    };

    TextWatcher telefoneTextWatcher = new TextWatcher() {
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        @Override
        public void afterTextChanged(Editable s) {
            telefoneArrayAdapter.getItem(i).setTelefone(s.toString());
            telefoneArrayAdapter.getItem(i).setIsDirty(true);
        }
    };

    tipoEditText.addTextChangedListener(tipoTextWatcher);
    telefoneEditText.addTextChangedListener(telefoneTextWatcher);

    deleteImageView.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            telefoneArrayAdapter.remove(telefone);
        }
    });

    return view;
}

}
mFontolan
fonte
3

Sei que é tarde, mas isso pode ajudar, este é um exemplo de como eu escrevo uma classe de adaptador personalizada para diferentes ações de clique

 public class CustomAdapter extends BaseAdapter {

    TextView title;
  Button button1,button2;

    public long getItemId(int position) {
        return position;
    }

    public int getCount() {
        return mAlBasicItemsnav.size();  // size of your list array
    }

    public Object getItem(int position) {
        return position;
    }

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

        if (convertView == null) {
            convertView = getLayoutInflater().inflate(R.layout.listnavsub_layout, null, false); // use sublayout which you want to inflate in your each list item
        }

        title = (TextView) convertView.findViewById(R.id.textViewnav); // see you have to find id by using convertView.findViewById 
        title.setText(mAlBasicItemsnav.get(position));
      button1=(Button) convertView.findViewById(R.id.button1);
      button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            //your click action 

           // if you have different click action at different positions then
            if(position==0)
              {
                       //click action of 1st list item on button click
        }
           if(position==1)
              {
                       //click action of 2st list item on button click
        }
    });

 // similarly for button 2

   button2=(Button) convertView.findViewById(R.id.button2);
      button2.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            //your click action 

    });



        return convertView;
    }
}
Manohar Reddy
fonte
-4

A solução da plataforma para esta implementação não usa um menu de contexto exibido em um longo toque?

O autor da pergunta está ciente dos menus de contexto? O empilhamento de botões em uma exibição de lista tem implicações no desempenho, desorganiza a interface do usuário e viola o design de interface do usuário recomendado para a plataforma.

Por outro lado; menus de contexto - por natureza, sem representação passiva - não são óbvios para o usuário final. Considere documentar o comportamento?

Este guia deve lhe dar um bom começo.

http://www.mikeplate.com/2010/01/21/show-a-context-menu-for-long-clicks-in-an-android-listview/

Gusdor
fonte