Problema de clic personalizado en ListView en elementos en Android

110

Entonces tengo un objeto ListView personalizado. Los elementos de la lista tienen dos vistas de texto apiladas una encima de la otra, además de una barra de progreso horizontal que quiero que permanezca oculta hasta que realmente haga algo. En el extremo derecho hay una casilla de verificación que solo quiero mostrar cuando el usuario necesita descargar actualizaciones a su (s) base de datos (s). Cuando desactivo la casilla de verificación estableciendo la visibilidad en Visibility.GONE, puedo hacer clic en los elementos de la lista. Cuando la casilla de verificación está visible, no puedo hacer clic en nada en la lista excepto en las casillas de verificación. Hice algunas búsquedas pero no encontré nada relevante para mi situación actual. Encontré esta preguntapero estoy usando un ArrayAdapter anulado ya que estoy usando ArrayLists para contener la lista de bases de datos internamente. ¿Solo necesito obtener la vista LinearLayout y agregar un onClickListener como lo hizo Tom? No estoy seguro.

Aquí está el XML de diseño de fila de la vista de lista:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:padding="6dip">
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="0dip"
        android:layout_weight="1"
        android:layout_height="fill_parent">
        <TextView
            android:id="@+id/UpdateNameText"
            android:layout_width="wrap_content"
            android:layout_height="0dip"
            android:layout_weight="1"
            android:textSize="18sp"
            android:gravity="center_vertical"
            />
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
            android:id="@+id/UpdateStatusText"
            android:singleLine="true"
            android:ellipsize="marquee"
            />
        <ProgressBar android:id="@+id/UpdateProgress" 
                     android:layout_width="fill_parent" 
                     android:layout_height="wrap_content"
                     android:indeterminateOnly="false" 
                     android:progressDrawable="@android:drawable/progress_horizontal" 
                     android:indeterminateDrawable="@android:drawable/progress_indeterminate_horizontal" 
                     android:minHeight="10dip" 
                     android:maxHeight="10dip"                    
                     />
    </LinearLayout>
    <CheckBox android:text="" 
              android:id="@+id/UpdateCheckBox" 
              android:layout_width="wrap_content" 
              android:layout_height="wrap_content" 
              />
</LinearLayout>

Y aquí está la clase que extiende ListActivity. Obviamente, todavía está en desarrollo, así que perdone las cosas que faltan o que se pueden dejar por ahí:

public class UpdateActivity extends ListActivity {

    AccountManager lookupDb;
    boolean allSelected;
    UpdateListAdapter list;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        lookupDb = new AccountManager(this);
        lookupDb.loadUpdates();

        setContentView(R.layout.update);
        allSelected = false;

        list = new UpdateListAdapter(this, R.layout.update_row, lookupDb.getUpdateItems());
        setListAdapter(list);

        Button btnEnterRegCode = (Button)findViewById(R.id.btnUpdateRegister);
        btnEnterRegCode.setVisibility(View.GONE);

        Button btnSelectAll = (Button)findViewById(R.id.btnSelectAll);
        btnSelectAll.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View v) {
                allSelected = !allSelected;

                for(int i=0; i < lookupDb.getUpdateItems().size(); i++) {
                    lookupDb.getUpdateItem(i).setSelected(!lookupDb.getUpdateItem(i).isSelected());
                }

                list.notifyDataSetChanged();
                // loop through each UpdateItem and set the selected attribute to the inverse 

            } // end onClick
        }); // end setOnClickListener

        Button btnUpdate = (Button)findViewById(R.id.btnUpdate);
        btnUpdate.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View v) {
            } // end onClick
        }); // end setOnClickListener

        lookupDb.close();
    } // end onCreate


    @Override
    protected void onDestroy() {
        super.onDestroy();

        for (UpdateItem item : lookupDb.getUpdateItems()) {
            item.getDatabase().close();        
        }
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);

        UpdateItem item = lookupDb.getUpdateItem(position);

        if (item != null) {
            item.setSelected(!item.isSelected());
            list.notifyDataSetChanged();
        }
    }

    private class UpdateListAdapter extends ArrayAdapter<UpdateItem> {
        private List<UpdateItem> items;

        public UpdateListAdapter(Context context, int textViewResourceId, List<UpdateItem> items) {
            super(context, textViewResourceId, items);
            this.items = items;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View row = null;

            if (convertView == null) {
                LayoutInflater li = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                row = li.inflate(R.layout.update_row, null);
            } else {
                row = convertView;
            }

            UpdateItem item = items.get(position);

            if (item != null) {
                TextView upper = (TextView)row.findViewById(R.id.UpdateNameText);
                TextView lower = (TextView)row.findViewById(R.id.UpdateStatusText);
                CheckBox cb = (CheckBox)row.findViewById(R.id.UpdateCheckBox);

                upper.setText(item.getName());
                lower.setText(item.getStatusText());

                if (item.getStatusCode() == UpdateItem.UP_TO_DATE) {
                    cb.setVisibility(View.GONE);
                } else {
                    cb.setVisibility(View.VISIBLE);
                    cb.setChecked(item.isSelected());
                }

                ProgressBar pb = (ProgressBar)row.findViewById(R.id.UpdateProgress);
                pb.setVisibility(View.GONE);
            }
            return row;
        }

    } // end inner class UpdateListAdapter
}

editar: sigo teniendo este problema. Estoy haciendo trampa y agregando controladores onClick a las vistas de texto, pero parece extremadamente estúpido que mi función onListItemClick () no se llame en absoluto cuando no estoy haciendo clic en mi casilla de verificación.

MattC
fuente

Respuestas:

243

El problema es que Android no le permite seleccionar elementos de la lista que tienen elementos que se pueden enfocar. Modifiqué la casilla de verificación en el elemento de la lista para tener un atributo como este:

android:focusable="false"

Ahora los elementos de mi lista que contienen casillas de verificación (también funciona para botones) son "seleccionables" en el sentido tradicional (se iluminan, puede hacer clic en cualquier parte del elemento de la lista y se activará el controlador "onListItemClick", etc.).

EDITAR: Como una actualización, un comentarista mencionó "Solo una nota, después de cambiar la visibilidad del botón tuve que deshabilitar programáticamente el enfoque nuevamente".

MattC
fuente
24
Parece que esta solución no funciona cuando se usa ImageButton en el elemento de la lista. :(
Julian A.
1
Ok, pero ¿qué pasa si quiero que mi casilla de verificación y mi clic en la vista de lista sean mutuamente excluyentes? Si selecciono el elemento ListView, no necesariamente quiero marcar el checkBox. ¿Es esto posible con esta solución?
Mike6679
@Mike Sí, esta solución aún requiere que se haga clic explícitamente en la casilla de verificación para poder interactuar con ella. Simplemente vuelve a habilitar la funcionalidad en ListView que misteriosamente se pasa por alto cuando tiene elementos enfocables en el diseño de su elemento de lista.
MattC
2
Gran respuesta gracias, funciona bien con ImageButtons también. Solo una nota, después de cambiar la visibilidad del botón tuve que deshabilitar programáticamente el enfoque nuevamente.
Ljdawson
1
También puede que necesite establecer Android: = se puede hacer clic "falsa"
Mina Samy
43

En caso de que tenga ImageButton dentro del elemento de la lista, debe establecer el descendantFocusabilityvalor en 'blocksDescendants' en el elemento del elemento de la lista raíz.

android:descendantFocusability="blocksDescendants"

Y la focusableInTouchModebandera a truela ImageButtonvista.

android:focusableInTouchMode="true"
micnoy
fuente
1
Esto funciona perfectamente para listitem que tiene botones de imagen. Pero esto podría funcionar excepto ImageButton
raksja
12

Tuve un problema similar y descubrí que CheckBox es bastante delicado en un ListView. Lo que sucede es que impone su voluntad en todo el ListItem y, en cierto modo, anula el onListItemClick. Es posible que desee implementar un controlador de clic para eso y establecer la propiedad de texto para CheckBox también, en lugar de usar TextViews.

Yo diría que también mire este objeto de Vista, puede funcionar mejor que el CheckBox

Vista de texto comprobado

Andrew Burgess
fuente
6
Descubrí que si establece la configuración "enfocable" en falso para los elementos enfocables en la lista, puede usar su función onListItemClick nuevamente.
MattC
2
Sin embargo, la casilla de verificación se vuelve naranja como el elemento de la lista. ¿Alguien conoce una solución?
erdomester
8

use esta línea en la vista raíz del elemento de la lista

android: descendantFocusability = "blocksDescendants"

Suresh Kumar
fuente