Android: элементы ListView с несколькими нажимаемыми кнопками

182

У меня есть, ListViewгде каждый элемент в списке содержит TextView и две разные кнопки. Что-то вроде этого:

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

С помощью этого кода я могу создать OnItemClickListenerдля всего элемента:

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

        }

    }
});

Однако я не хочу, чтобы весь элемент был кликабельным, а только две кнопки каждого элемента списка.

Итак, мой вопрос, как мне реализовать onClickListener для этих двух кнопок со следующими параметрами:

  • int button (какая кнопка элемента была нажата)
  • int position (который является элементом в списке, по которому произошло нажатие кнопки)

Обновление: я нашел решение, как описано в моем ответе ниже. Теперь я могу нажать / нажать кнопку через сенсорный экран. Однако я не могу вручную выбрать его с помощью трекбола. Он всегда выбирает весь элемент списка и оттуда сразу переходит к следующему элементу списка, игнорируя кнопки, хотя я установил .setFocusable(true)и setClickable(true)для кнопок в getView().

Я также добавил этот код в свой адаптер списка:

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

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

Это приводит к тому, что ни один элемент списка больше не может быть выбран вообще. Но это не помогло сделать вложенные кнопки выбираемыми.

Есть идеи?

znq
источник
Они все еще нужны?
Стюарт Аксон
Если вы посмотрите на код BaseAdapter, вы увидите, что areAllItemsEnabled () и isEnabled () просто жестко закодированы в true, что делает их простыми заполнителями без какой-либо логики.
Артем Руссаковский
Что делать, если я хочу использовать SimpleCursorAdapter? Нужно ли делать собственный адаптер, расширяющий simplecursoradapter?
oratis

Ответы:

150

Решение этой проблемы на самом деле проще, чем я думал. Вы можете просто добавить в getView()методе своего пользовательского адаптера setOnClickListener () для кнопок, которые вы используете.

Любые данные, связанные с кнопкой, должны быть добавлены myButton.setTag()в getView()и могут быть доступны в onClickListener черезview.getTag()

Я разместил подробное решение в своем блоге в качестве учебного пособия.

znq
источник
2
Исправлена. Спасибо за сообщение :-)
znq
Насколько точно вы получили curItem.url из запроса, не могли бы вы быть более конкретным?
oratis
1
Спасибо znq, это было очень полезно для меня ... Экономия времени.
Nandagopal T
Посмотрите этот доклад в 11:39. Отличный пример: youtu.be/wDBM6wVEO70?t=11m39s. Затем делайте то, что говорит @znq ... setTag (), когда convertView == null, и выполняйте getTag () в onClick (). метод кнопки onClickListener (). Спасибо!
Shehaaz
12
было бы здорово иметь ответы в самой SO и не указывать на какую-то другую страницу. Ссылка на блог мертва!
GSB
63

Это своего рода ответ придатка @ znq ...

Есть много случаев, когда вы хотите узнать положение строки для элемента, по которому щелкнули, И вы хотите знать, какой вид в строке был нажат. Это будет намного важнее в планшетных интерфейсах.

Вы можете сделать это с помощью следующего пользовательского адаптера:

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
источник
6
или вы также можете использовать что-то вроде этого ListView mListView = (ListView) v.getParent (). getParent ();
max4ever
1
Как я могу получить должность, если я использую адаптер в отдельном классе. Я просто использовал позицию внутри OnClickListener, что-то вроде like_c.get (position). Предлагается сделать позицию финальной в методе getView адаптера. Если я сделаю это изменение, то позиция, которую я получу, будет одинаковой для всех элементов в lisview. Не могли бы вы подсказать мне, как решить эту проблему?
Маникандан
Это потому, что когда вы используете параметр position метода getView, он дает вам позицию элемента списка, который был создан последним, но не тот, который вы щелкнули
Archie.bpgc
@Manikandan: Если вы используете метод getView () адаптера, то вам уже передана позиция в параметрах метода. В этом случае вы можете использовать что-то вроде setTag () для хранения информации о местоположении.
greg7gkb
1
Я использую ваш код в своем приложении, но событие click вызывается после задержки или при попытке прокрутки списка. Есть идеи, почему это происходит?
Абдулла Умер
22

Для будущих читателей:

Чтобы вручную выбрать кнопки с помощью трекбола:

myListView.setItemsCanFocus(true);

И чтобы отключить фокус на весь список элементов:

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

У меня это работает нормально, я могу нажимать на кнопки с сенсорным экраном, а также позволяет фокусировать клик с помощью клавиатуры

Fabricio PH
источник
2
Только этот помог мне! Большое тебе спасибо!
Онур
Что касается ExpandableListView, у меня есть несколько представлений в элементе, я просто хочу, чтобы представления в дочернем элементе были активными, спасибо.
Twlkyao
12

У меня не так много опыта, как у вышеупомянутых пользователей, но я столкнулся с той же проблемой и решил ее с помощью нижеприведенного решения.

<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 Событие клика

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

}
Бхавин Чаухан
источник
интересное решение
шерпя
9

Возможно, вы нашли, как это сделать, но вы можете позвонить

ListView.setItemsCanFocus(true)

и теперь ваши кнопки поймают фокус

Рафал
источник
5

Я не уверен, что будет лучшим способом, но работает нормально, и весь код остается в вашем 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
источник
3

Я знаю, что уже поздно, но это может помочь, это пример того, как я пишу собственный класс адаптера для различных действий щелчка

 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;
    }
}
Манохар Редди
источник
-4

Разве платформенное решение для этой реализации не использует контекстное меню, которое отображается при долгом нажатии?

Знает ли автор вопроса о контекстных меню? Расположение кнопок в представлении списка влияет на производительность, загромождает ваш интерфейс и нарушает рекомендуемый дизайн интерфейса для платформы.

С другой стороны; контекстные меню - по природе отсутствия пассивного представления - не очевидны для конечного пользователя. Рассматриваете документирование поведения?

Это руководство должно дать вам хорошее начало.

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

Gusdor
источник