Android: elementy ListView z wieloma klikalnymi przyciskami

182

Mam ListViewgdzie każdy element na liście zawiera TextView i dwa różne przyciski. Coś takiego:

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

Za pomocą tego kodu mogę utworzyć OnItemClickListenerdla całego elementu:

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

        }

    }
});

Nie chcę jednak, aby można było klikać cały element, ale tylko dwa przyciski każdego elementu listy.

Więc moje pytanie brzmi: jak zaimplementować onClickListener dla tych dwóch przycisków z następującymi parametrami:

  • int button (który przycisk elementu został kliknięty)
  • int position (który jest elementem na liście, na którym nastąpiło kliknięcie przycisku)

Aktualizacja: Znalazłem rozwiązanie opisane w mojej odpowiedzi poniżej. Teraz mogę kliknąć / dotknąć przycisku za pomocą ekranu dotykowego. Nie mogę jednak ręcznie wybrać go za pomocą trackballa. Zawsze zaznacza cały element listy i stamtąd przechodzi bezpośrednio do następnego elementu listy, ignorując przyciski, mimo że ustawiłem .setFocusable(true)i setClickable(true)dla przycisków w getView().

Dodałem również ten kod do mojego niestandardowego adaptera listy:

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

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

To powoduje, że żaden element listy nie jest już do wyboru. Ale nie pomogło to w wybraniu zagnieżdżonych przycisków.

Czy ktoś ma pomysł?

znq
źródło
Czy nadal są potrzebne?
Stuart Axon,
Jeśli spojrzysz na kod BaseAdapter, zobaczysz, że areAllItemsEnabled () i isEnabled () są po prostu zakodowane na prawdę, co czyni je prostymi symbolami zastępczymi bez logiki.
Artem Russakovskii
Co zrobić, jeśli chcę użyć SimpleCursorAdapter? Czy muszę utworzyć niestandardowy adapter, który rozszerza moduł SimpleCursor?
oratis

Odpowiedzi:

150

Rozwiązanie tego jest w rzeczywistości łatwiejsze niż myślałem. Możesz po prostu dodać do getView()metody niestandardowego adaptera setOnClickListener () dla używanych przycisków.

Wszelkie dane związane z przyciskiem należy dodać myButton.setTag()w getView()i można uzyskać do niego dostęp za pośrednictwem onClickListenerview.getTag()

Szczegółowe poradnik zamieściłem na blogu .

znq
źródło
2
Naprawiony. Dzięki za zgłoszenie :-)
znq
jak dokładnie otrzymałeś curItem.url z zapytania, czy byłbyś bardziej szczegółowy?
oratis
1
Dzięki znq, To było dla mnie bardzo przydatne ... Zaoszczędziłem mnóstwo czasu.
Nandagopal T
Obejrzyj tę rozmowę o 11:39, jest doskonały przykład: youtu.be/wDBM6wVEO70?t=11m39s Następnie zrób to, co mówi @znq ... setTag (), gdy convertView == null i wykonaj getTag () w onClick () metoda przycisku onClickListener (). Dziękuję Ci!
Shehaaz,
12
wspaniale byłoby mieć odpowiedzi w samym SO i nie wskazywać innej strony. Link do bloga nie działa!
gsb
63

To jest odpowiedź na dodatek @ znq ...

Istnieje wiele przypadków, w których chcesz poznać pozycję wiersza dla klikniętego elementu ORAZ chcesz wiedzieć, który widok w wierszu został dotknięty. Będzie to o wiele ważniejsze w interfejsach tabletów.

Możesz to zrobić za pomocą następującego niestandardowego adaptera:

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
źródło
6
lub możesz również użyć czegoś takiego: ListView mListView = (ListView) v.getParent (). getParent ();
max4ever
1
Jak mogę uzyskać pozycję, jeśli używam adaptera w osobnej klasie. Po prostu użyłem pozycji wewnątrz OnClickListener, coś takiego jak ten_c.get (pozycja). Sugeruje to, aby pozycja była końcowa w metodzie getView adaptera. Jeśli dokonam tej zmiany, pozycja, którą otrzymuję, jest taka sama dla wszystkich pozycji w lisview. Czy możesz mi zasugerować, jak to rozwiązać.
Manikandan
Dzieje się tak, ponieważ kiedy używasz parametru position metody getView, daje on pozycję pozycji listy, która jest ostatnio tworzona, ale nie tej, którą kliknąłeś
Archie.bpgc
@Manikandan: Jeśli używasz metody getView () adaptera, oznacza to, że masz już przekazaną pozycję w parametrach metody. W takim przypadku możesz użyć czegoś takiego jak setTag () do przechowywania informacji o pozycji.
greg7gkb
1
Korzystam z Twojego kodu w mojej aplikacji, ale zdarzenie kliknięcia jest wywoływane z opóźnieniem lub podczas próby przewijania widoku listy. Wszelkie pomysły, dlaczego tak się dzieje?
Abdullah Umer,
22

Dla przyszłych czytelników:

Aby ręcznie wybrać przyciski za pomocą trackballa, użyj:

myListView.setItemsCanFocus(true);

I aby wyłączyć fokus na całej pozycji listy:

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

Działa to dla mnie dobrze, mogę klikać przyciski z ekranem dotykowym, a także umożliwia skupienie kliknięcia za pomocą klawiatury

Fabricio PH
źródło
2
Tylko ten pomógł mi! Dziękuję bardzo!
Onur
A co z ExpandableListView, mam wiele widoków w elemencie, chcę tylko widoki w dziecku klikalne, dzięki.
twlkyao
12

Nie mam dużego doświadczenia niż powyżsi użytkownicy, ale napotkałem ten sam problem i rozwiązałem go poniżej

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

btnRemoveClick Zdarzenie Click

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

}
Bhavin Chauhan
źródło
ciekawe rozwiązanie
sherpya
9

Prawdopodobnie wiesz, jak to zrobić, ale możesz zadzwonić

ListView.setItemsCanFocus(true)

a teraz twoje przyciski będą się skupiać

Rafał
źródło
5

Nie jestem pewien, czy to najlepszy sposób, ale działa dobrze i cały kod pozostaje w 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
źródło
3

Wiem, że jest późno, ale to może pomóc, to przykład, w jaki sposób piszę niestandardową klasę adaptera dla różnych akcji kliknięcia

 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
źródło
-4

Czy rozwiązanie platformy dla tej implementacji nie korzysta z menu kontekstowego wyświetlanego po długim naciśnięciu?

Czy autor pytania zna menu kontekstowe? Układanie przycisków w widoku listy ma wpływ na wydajność, zaśmieca interfejs użytkownika i narusza zalecany projekt interfejsu dla platformy.

Z drugiej strony; menu kontekstowe - z natury nie posiadające pasywnej reprezentacji - nie są oczywiste dla użytkownika końcowego. Zastanów się nad udokumentowaniem zachowania?

Ten przewodnik powinien dać dobry początek.

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

Gusdor
źródło