EditText enfocable dentro de ListView

121

He pasado alrededor de 6 horas en esto hasta ahora y no he encontrado más que obstáculos. La premisa general es que hay alguna fila en un ListView(ya sea generado por el adaptador o agregado como una vista de encabezado) que contiene un EditTextwidget y un Button. Todo lo que quiero hacer es poder usar el jogball / flechas, para navegar el selector a elementos individuales como de costumbre, pero cuando llego a una fila en particular, incluso si tengo que identificar explícitamente la fila, eso tiene un enfoque niño, quiero que ese niño se enfoque en lugar de indicar la posición con el selector.

He probado muchas posibilidades y hasta ahora no he tenido suerte.

diseño:

<ListView
    android:id="@android:id/list" 
    android:layout_height="fill_parent" 
    android:layout_width="fill_parent"
    />

Vista de encabezado:

EditText view = new EditText(this);
listView.addHeaderView(view, null, true);

Suponiendo que hay otros elementos en el adaptador, el uso de las teclas de flecha moverá la selección hacia arriba o hacia abajo en la lista, como se esperaba; pero al llegar a la fila del encabezado, también se muestra con el selector, y no hay forma de enfocarse en el EditTextuso del jogball. Nota: tocando en elEditText se enfocará en ese punto, sin embargo, eso depende de una pantalla táctil, que no debería ser un requisito.

ListViewaparentemente tiene dos modos a este respecto:
1 setItemsCanFocus(true).: el selector nunca se muestra, pero el EditTextpuede enfocarse al usar las flechas. El algoritmo de búsqueda de enfoque es difícil de predecir y no hay comentarios visuales (en ninguna fila: tener hijos enfocables o no) sobre qué elemento se selecciona, los cuales pueden brindar al usuario una experiencia inesperada.
2 setItemsCanFocus(false).: el selector siempre se dibuja en modo no táctil y EditTextnunca puede enfocarse, incluso si lo toca.

Para empeorar las cosas, la llamada editTextView.requestFocus()devuelve verdadero, pero de hecho no da el foco EditText.

Lo que estoy imaginando es básicamente un híbrido de 1 y 2, donde en lugar de la configuración de la lista si todos los elementos son enfocables o no, quiero establecer la capacidad de enfoque para un solo elemento en la lista, de modo que el selector pase sin problemas de seleccionar el fila completa para elementos no enfocables y atravesar el árbol de enfoque para elementos que contienen elementos secundarios enfocables.

¿Ningún arrendatario?

Joe
fuente

Respuestas:

101

Lo siento, respondí mi propia pregunta. Puede que no sea la solución más correcta o más elegante, pero funciona para mí y brinda una experiencia de usuario bastante sólida. Busqué en el código de ListView para ver por qué los dos comportamientos son tan diferentes y encontré esto en ListView.java:

    public void setItemsCanFocus(boolean itemsCanFocus) {
        mItemsCanFocus = itemsCanFocus;
        if (!itemsCanFocus) {
            setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
        }
    }

Entonces, al llamar setItemsCanFocus(false), también establece la capacidad de enfoque descendiente de modo que ningún niño pueda concentrarse. Esto explica por qué no podía simplemente alternar mItemsCanFocusel OnItemSelectedListener de ListView, porque ListView estaba bloqueando el enfoque para todos los niños.

Lo que tengo ahora:

<ListView
    android:id="@android:id/list" 
    android:layout_height="match_parent" 
    android:layout_width="match_parent"
    android:descendantFocusability="beforeDescendants"
    />

Lo uso beforeDescendantsporque el selector solo se dibujará cuando ListView en sí (no un niño) tiene el foco, por lo que el comportamiento predeterminado debe ser que ListView se enfoque primero y dibuje selectores.

Luego, en OnItemSelectedListener, ya que sé qué vista de encabezado quiero anular el selector (tomaría más trabajo determinar dinámicamente si una posición determinada contiene una vista enfocable), puedo cambiar la capacidad de enfoque descendente y establecer el foco en EditText. Y cuando salgo de ese encabezado, lo vuelvo a cambiar.

public void onItemSelected(AdapterView<?> listView, View view, int position, long id)
{
    if (position == 1)
    {
        // listView.setItemsCanFocus(true);

        // Use afterDescendants, because I don't want the ListView to steal focus
        listView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        myEditText.requestFocus();
    }
    else
    {
        if (!listView.isFocused())
        {
            // listView.setItemsCanFocus(false);

            // Use beforeDescendants so that the EditText doesn't re-take focus
            listView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
            listView.requestFocus();
        }
    }
}

public void onNothingSelected(AdapterView<?> listView)
{
    // This happens when you start scrolling, so we need to prevent it from staying
    // in the afterDescendants mode if the EditText was focused 
    listView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
}

Tenga en cuenta las setItemsCanFocusllamadas comentadas . Con esas llamadas, obtuve el comportamiento correcto, pero setItemsCanFocus(false)causé que el enfoque saltara de EditText a otro widget fuera de ListView, volviera a ListView y mostrara el selector en el siguiente elemento seleccionado, y ese enfoque de salto distraía. Al eliminar el cambio ItemsCanFocus y simplemente alternar la capacidad de enfoque descendiente, obtuve el comportamiento deseado. Todos los elementos dibujan el selector de forma normal, pero al llegar a la fila con EditText, se centró en el campo de texto. Luego, al continuar fuera de ese EditText, comenzó a dibujar el selector nuevamente.

Joe
fuente
muy bueno, aún no probado. ¿Ha probado en 1.5, 1.6 y 3.0?
Rafael Sanches
Rafael Sanches: No he tocado el proyecto desde 2.1, pero en ese momento, se confirmó que funcionaba en 1.5, 1.6 y 2.1. No garantizo que todavía funcione en 2.2 o posterior.
Joe
13
solo se necesita android: descendantFocusability = "afterDescendants" - de todos modos +1
kellogs
5
@kellogs: sí, descendantFocusability="afterDescendants"permitirá que su EditText se enfoque dentro de ListView, pero luego no obtendrá ningún selector de elementos de lista mientras navega con un dpad. Mi tarea era tener el selector de elementos de la lista en todas las filas excepto en la que tiene EditText. Aunque me alegro de que haya ayudado. FWIW, terminamos reevaluando esta implementación y decidimos que un enfoque dentro de un ListView simplemente no es un diseño idiomático de la interfaz de usuario de Android, por lo que descartamos la idea a favor de un enfoque más compatible con Android.
Joe
5
¿Se ha probado esto en Ice Cream Sandwich? No puedo hacer que funcione. Gracias.
Rajat Anantharam
99

Eso me ayudó.
En tu manifiesto:

<activity android:name= ".yourActivity" android:windowSoftInputMode="adjustPan"/>
logan
fuente
3
No estoy seguro de ver la relevancia. Establecer windowSoftInputMode simplemente cambia la forma en que el IME ajusta el resto del contenido de la ventana cuando está abierta. No le permite cambiar de forma selectiva el tipo de enfoque de ListView. ¿Puede explicar un poco más cómo se relaciona esto con el caso de uso inicial?
Joe
1
@Joe Cuando se abre el IME, el cursor, y probablemente también el foco, simplemente salta en mi pantalla, haciendo imposible ingresar texto. Tu OnItemSelectedListenerno cambia eso. Sin embargo, la sencilla solución de Iogan funciona de maravilla, ¡gracias!
Gubbel
2
@Gubbel: De hecho, no cambiaría eso en absoluto, porque la pregunta original era sobre algo completamente diferente :) Me alegra que la solución de Logan funcione para lo que estabas buscando, pero simplemente no está ni remotamente relacionada con la pregunta.
Joe
8
El uso de esta android:descendantFocusabilitypropiedad más de Joe obtuve mi EditTexts dentro de una ListViewresolución del teclado correctamente, votó a ambos. android:descendantFocusabilitypor sí solo no funcionó y no estaba ni remotamente entusiasmado con @Overriding onItemSelectedlos 14 EditTexts con los que tengo que lidiar. :) ¡Gracias!
Thomson Comer
Esto funcionó para mí, pero tenga en cuenta que si está usando TabHost o TabActivity, debe configurar android: windowSoftInputMode = ”adjustPan” para la definición de TabActivity en el manifiesto.
Kiduxa
18

Mi tarea era implementar lo ListViewque se expande cuando se hace clic. El espacio adicional muestraEditText dónde puede ingresar texto. La aplicación debe ser funcional en 2.2+ (hasta 4.2.2 al momento de escribir esto)

Probé numerosas soluciones de esta publicación y otras que pude encontrar; los probé en dispositivos 2.2 hasta 4.2.2. Ninguna de las soluciones fue satisfactoria en todos los dispositivos 2.2+, cada solución presentaba problemas diferentes.

Quería compartir mi solución final:

  1. establecer listview en android:descendantFocusability="afterDescendants"
  2. establecer listview en setItemsCanFocus(true);
  3. establezca su actividad en android:windowSoftInputMode="adjustResize" Mucha gente sugiere, adjustPanpero adjustResizeda mucho mejor ux en mi humilde opinión, solo pruebe esto en su caso. Con adjustPanusted obtendrá los elementos de la lista inferior oscurecidos, por ejemplo. Los documentos sugieren que ("Por lo general, esto es menos deseable que cambiar el tamaño"). También en 4.0.4 después de que el usuario comienza a escribir en el teclado virtual, la pantalla se desplaza hacia la parte superior.
  4. en 4.2.2 con adjustResizehay algunos problemas con el foco EditText. La solución es aplicar la solución rjrjr de este hilo. Parece aterrador pero no lo es. Y funciona. Solo inténtalo.

Adicional 5. Debido a que el adaptador se actualiza (debido al cambio de tamaño de la vista) cuando EditTextse enfoca en las versiones anteriores de HoneyComb, encontré un problema con las vistas invertidas: obteniendo Vista para el elemento ListView / orden inverso en 2.2; funciona en 4.0.3

Si está haciendo algunas animaciones, es posible que desee cambiar el comportamiento adjustPanpara las versiones anteriores al panal para que el cambio de tamaño no se active y el adaptador no actualice las vistas. Solo necesitas agregar algo como esto

if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);

Todo esto da una ux aceptable en dispositivos 2.2 - 4.2.2. Espero que le ahorre a la gente algo de tiempo, ya que me tomó al menos varias horas llegar a esta conclusión.

AndroidGecko
fuente
1
solo el primer y segundo paso es suficiente en mi caso
Kalpesh Lakhani
Funciona perfectamente con mi xElement.setOnClickListener (..) en ArrayAdapter y un ListView.setOnItemClickListener (...) - ¡¡finalmente !! Muchas gracias.
Javatar
10

Esto me salvó la vida --->

  1. establecer esta línea

    ListView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);

  2. Luego, en su manifiesto en la etiqueta de actividad, escriba esto ->

    <activity android:windowSoftInputMode="adjustPan">

Tu intención habitual

ravi ranjan
fuente
7

Estamos probando esto en una lista corta que no hace ningún reciclaje. Hasta aquí todo bien.

XML:

<RitalinLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
  <ListView
      android:id="@+id/cart_list"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:scrollbarStyle="outsideOverlay"
      />
</RitalinLayout>

Java:

/**
 * It helps you keep focused.
 *
 * For use as a parent of {@link android.widget.ListView}s that need to use EditText
 * children for inline editing.
 */
public class RitalinLayout extends FrameLayout {
  View sticky;

  public RitalinLayout(Context context, AttributeSet attrs) {
    super(context, attrs);

    ViewTreeObserver vto = getViewTreeObserver();

    vto.addOnGlobalFocusChangeListener(new ViewTreeObserver.OnGlobalFocusChangeListener() {
      @Override public void onGlobalFocusChanged(View oldFocus, View newFocus) {
        if (newFocus == null) return;

        View baby = getChildAt(0);

        if (newFocus != baby) {
          ViewParent parent = newFocus.getParent();
          while (parent != null && parent != parent.getParent()) {
            if (parent == baby) {
              sticky = newFocus;
              break;
            }
            parent = parent.getParent();
          }
        }
      }
    });

    vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
      @Override public void onGlobalLayout() {
        if (sticky != null) {
          sticky.requestFocus();
        }
      }
    });
  }
}
rjrjr
fuente
Ligeramente modificada, esta solución me funciona después de 2 días de @ # ( : if (sticky! = Null) {sticky.RequestFocus (); sticky.RequestFocusFromTouch (); sticky = null;}
Chris van de Steeg
4

esta publicación coincidía exactamente con mis palabras clave. Tengo un encabezado ListView con un EditText de búsqueda y un botón de búsqueda.

Para enfocar el EditText después de perder el enfoque inicial, el único HACK que encontré es:

    searchText.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View arg0) {
            // LOTS OF HACKS TO MAKE THIS WORK.. UFF...
            searchButton.requestFocusFromTouch();
            searchText.requestFocus();
        }
    });

Perdí muchas horas y no es una solución real. Espero que ayude a alguien duro.

Rafael Sanches
fuente
2

Si la lista es dinámica y contiene widgets enfocables, entonces la opción correcta es usar RecyclerView en lugar de ListView IMO.

Las soluciones provisionales que establecen adjustPan, FOCUS_AFTER_DESCENDANTSo recuerdan manualmente la posición enfocada, son de hecho solo soluciones provisionales. Tienen casos de esquina (desplazamiento + problemas de teclado suave, cambio de posición de intercalación en EditText). No cambian el hecho de que ListView crea / destruye vistas en masa durante notifyDataSetChanged.

Con RecyclerView, notifica sobre inserciones, actualizaciones y eliminaciones individuales. La vista enfocada no se está recreando, por lo que no hay problemas con los controles de formulario que pierden el enfoque. Como beneficio adicional, RecyclerView anima las inserciones y eliminaciones de elementos de la lista.

Aquí hay un ejemplo de documentos oficiales sobre cómo comenzar con RecyclerView: Guía para desarrolladores : cree una lista con RecyclerView

Pēteris Caune
fuente
1

algunas veces, cuando lo usa android:windowSoftInputMode="stateAlwaysHidden"en una actividad de manifiesto o xml, esa vez perderá el enfoque del teclado. Entonces, primero verifique esa propiedad en su xml y manifiesto, si está allí, simplemente elimínela. Después de agregar estas opciones al archivo de manifiesto en la actividad lateral android:windowSoftInputMode="adjustPan"y agregar esta propiedad a la vista de lista en xmlandroid:descendantFocusability="beforeDescendants"


fuente
0

Otra solución simple es definir su onClickListener, en el método getView (..), de su ListAdapter.

public View getView(final int position, View convertView, ViewGroup parent){
    //initialise your view
    ...
    View row = context.getLayoutInflater().inflate(R.layout.list_item, null);
    ...

    //define your listener on inner items

    //define your global listener
    row.setOnClickListener(new OnClickListener(){
        public void onClick(View v) {
            doSomethingWithViewAndPosition(v,position);
        }
    });

    return row;

De esa manera, se puede hacer clic en su fila y también en su vista interior :)

Weber Antoine
fuente
1
La pregunta se relaciona con el enfoque, no con la posibilidad de hacer clic.
Joe
0

La parte más importante es hacer que el enfoque funcione para la celda de la lista. Especialmente para la lista en Google TV, esto es esencial:

El método setItemsCanFocus de la vista de lista hace el truco:

...
mPuzzleList = (ListView) mGameprogressView.findViewById(R.id.gameprogress_puzzlelist);
mPuzzleList.setItemsCanFocus(true);
mPuzzleList.setAdapter(new PuzzleListAdapter(ctx,PuzzleGenerator.getPuzzles(ctx, getResources(), version_lite)));
...

Mi lista de celdas xml comienza de la siguiente manera:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:id="@+id/puzzleDetailFrame"
             android:focusable="true"
             android:nextFocusLeft="@+id/gameprogress_lessDetails"
             android:nextFocusRight="@+id/gameprogress_reset"
...

nextFocusLeft / Right también son importantes para la navegación del D-Pad.

Para obtener más detalles, consulte las otras excelentes respuestas.

Michael Biermann
fuente
0

Acabo de encontrar otra solución. Creo que es más un truco que una solución, pero funciona en Android 2.3.7 y Android 4.3 (incluso he probado ese viejo D-pad)

inicie su vista web como de costumbre y agregue esto: (gracias Michael Bierman)

listView.setItemsCanFocus(true);

Durante la llamada getView:

editText.setOnFocusChangeListener(
    new OnFocusChangeListener(View view,boolean hasFocus){
        view.post(new Runnable() {
            @Override
            public void run() {
                view.requestFocus();
                view.requestFocusFromTouch();
            }
     });
alaeri
fuente
¿Qué es la vista aquí en view.requestFocus?
Narendra Singh
esta es una respuesta antigua, pero se refirió a la vista en el alcance dado como un argumento para OnFocusChangeListener, creo
alaeri
1
Provoca un bucle al cambiar el enfoque.
Morteza Rastgoo
0

Solo prueba esto

android:windowSoftInputMode="adjustNothing"

en el

actividad

sección de su manifiesto. Sí, no ajusta nada, lo que significa que editText permanecerá donde está cuando se abra IME. Pero eso es solo un pequeño inconveniente que aún resuelve por completo el problema de perder el enfoque.

Tor_Gash
fuente