Fokussierbarer EditText in ListView

121

Ich habe bisher ungefähr 6 Stunden damit verbracht und nichts als Straßensperren getroffen. Die allgemeine Voraussetzung ist, dass eine Zeile ListView(unabhängig davon, ob sie vom Adapter generiert oder als Header-Ansicht hinzugefügt wird) eine Zeile enthält, die ein EditTextWidget und ein enthält Button. Alles, was ich tun möchte, ist, den Jogball / die Pfeile verwenden zu können, um den Selektor wie gewohnt zu einzelnen Elementen zu navigieren, aber wenn ich zu einer bestimmten Zeile komme - auch wenn ich die Zeile explizit identifizieren muss -, hat dies eine Fokussierbarkeit Kind, ich möchte, dass dieses Kind den Fokus erhält, anstatt die Position mit dem Selektor anzuzeigen.

Ich habe viele Möglichkeiten ausprobiert und bisher kein Glück gehabt.

Layout:

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

Kopfansicht:

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

Angenommen, der Adapter enthält weitere Elemente. Mit den Pfeiltasten wird die Auswahl in der Liste erwartungsgemäß nach oben / unten verschoben. Wenn Sie jedoch zur Kopfzeile gelangen, wird diese auch mit dem Selektor angezeigt, und Sie können sich nicht auf die EditTextVerwendung des Jogballs konzentrieren. Hinweis: Wenn Sie auf das tippen, EditText wird es an diesem Punkt fokussiert. Dies hängt jedoch von einem Touchscreen ab, der nicht erforderlich sein sollte.

ListViewAnscheinend gibt es diesbezüglich zwei Modi:
1 . setItemsCanFocus(true): Der Selektor wird nie angezeigt, aber der EditTextkann bei Verwendung der Pfeile scharfgestellt werden. Der Fokus-Suchalgorithmus ist schwer vorherzusagen und es gibt kein visuelles Feedback (zu Zeilen: mit fokussierbaren untergeordneten Elementen oder nicht), welches Element ausgewählt ist. Beides kann dem Benutzer eine unerwartete Erfahrung bieten.
2 . setItemsCanFocus(false): Der Selektor wird immer im Non-Touch-Modus gezeichnet und EditTextkann niemals scharfgestellt werden - selbst wenn Sie darauf tippen.

Um die Sache noch schlimmer zu machen, gibt der Aufruf editTextView.requestFocus()true zurück, gibt jedoch nicht den EditText-Fokus.

Was ich mir vorstelle, ist im Grunde eine Mischung aus 1 und 2, bei der ich anstelle der Listeneinstellung, ob alle Elemente fokussierbar sind oder nicht, die Fokussierbarkeit für ein einzelnes Element in der Liste festlegen möchte , damit der Selektor nahtlos von der Auswahl der Elemente abweicht ganze Zeile für nicht fokussierbare Elemente und Durchlaufen des Fokusbaums für Elemente, die fokussierbare untergeordnete Elemente enthalten.

Irgendwelche Abnehmer?

Joe
quelle

Antworten:

101

Entschuldigung, beantwortete meine eigene Frage. Es ist vielleicht nicht die korrekteste oder eleganteste Lösung, aber es funktioniert für mich und bietet eine ziemlich solide Benutzererfahrung. Ich habe im Code für ListView nachgesehen, warum die beiden Verhaltensweisen so unterschiedlich sind, und bin auf ListView.java gestoßen:

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

Wenn Sie anrufen setItemsCanFocus(false), wird auch die Fokussierbarkeit der Nachkommen so eingestellt, dass kein Kind den Fokus erhalten kann. Dies erklärt, warum ich mItemsCanFocusden OnItemSelectedListener der ListView nicht einfach umschalten konnte, da die ListView dann den Fokus für alle untergeordneten Elemente blockierte.

Was ich jetzt habe:

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

Ich verwende, beforeDescendantsweil der Selektor nur gezeichnet wird, wenn die ListView selbst (kein untergeordnetes Element) den Fokus hat. Das Standardverhalten muss also sein, dass die ListView zuerst den Fokus nimmt und Selektoren zeichnet.

Da ich dann im OnItemSelectedListener weiß, welche Header-Ansicht ich den Selektor überschreiben möchte (würde mehr Arbeit erfordern, um dynamisch zu bestimmen, ob eine bestimmte Position eine fokussierbare Ansicht enthält), kann ich die Fokussierbarkeit von Nachkommen ändern und den Fokus auf den EditText setzen. Und wenn ich aus diesem Header heraus navigiere, ändere ich ihn wieder.

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);
}

Beachten Sie die auskommentierten setItemsCanFocusAnrufe. Bei diesen Aufrufen habe ich das richtige Verhalten erhalten, aber setItemsCanFocus(false)den Fokus vom EditText zu einem anderen Widget außerhalb der ListView zurück zur ListView gesprungen und den Selektor für das nächste ausgewählte Element angezeigt, und dieser Sprungfokus war ablenkend. Durch das Entfernen der ItemsCanFocus-Änderung und das Umschalten der Fokussierbarkeit von Nachkommen wurde das gewünschte Verhalten erzielt. Alle Elemente zeichnen den Selektor wie gewohnt, aber wenn Sie mit dem EditText zur Zeile gelangen, konzentriert er sich stattdessen auf das Textfeld. Wenn Sie diesen EditText verlassen, wird der Selektor erneut gezeichnet.

Joe
quelle
sehr cool, noch nicht getestet. Haben Sie auf 1.5, 1.6 und 3.0 getestet?
Rafael Sanches
Rafael Sanches: Ich habe das Projekt seit 2.1 nicht mehr angerührt, aber zu diesem Zeitpunkt wurde bestätigt, dass es in 1.5, 1.6 und 2.1 funktioniert. Ich kann nicht garantieren, dass es in 2.2 oder höher noch funktioniert.
Joe
13
nur benötigt android: descantFocusability = "afterDescendants" - sowieso +1
kellogs
5
@kellogs: Ja, descendantFocusability="afterDescendants"Ihr EditText kann sich auf die ListView konzentrieren, aber dann erhalten Sie beim Navigieren mit einem dpad keine Auswahl für Listenelemente. Meine Aufgabe war es, die Listenelementauswahl in allen Zeilen außer der mit dem EditText zu haben. Ich bin froh, dass es geholfen hat. FWIW, wir haben diese Implementierung letztendlich neu bewertet und entschieden, dass ein fokussierbares Design in einer ListView kein idiomatisches Android-UI-Design ist. Deshalb haben wir die Idee zugunsten eines Android-freundlicheren Ansatzes verworfen.
Joe
5
Wurde dies auf Ice Cream Sandwich getestet? Ich kann es nicht zum Laufen bringen. Vielen Dank.
Rajat Anantharam
99

Das hat mir geholfen.
In Ihrem Manifest:

<activity android:name= ".yourActivity" android:windowSoftInputMode="adjustPan"/>
Logan
quelle
3
Ich bin mir nicht sicher, ob ich die Relevanz sehe. Durch das Festlegen von windowSoftInputMode wird nur die Art und Weise geändert, in der der IME den Rest des Fensterinhalts beim Öffnen anpasst. Sie können den Fokustyp der ListView nicht selektiv ändern. Können Sie etwas näher erläutern, wie dies mit dem anfänglichen Anwendungsfall zusammenhängt?
Joe
1
@Joe Wenn der IME geöffnet wird, springt der Cursor - und wahrscheinlich auch der Fokus - einfach auf meinem Bildschirm herum und macht die Eingabe von Text unmöglich. Das OnItemSelectedListenerändert du nicht. Die einfache Lösung von Iogan funktioniert jedoch wie ein Zauber, danke!
Gubbel
2
@Gubbel: In der Tat würde es das überhaupt nicht ändern, denn die ursprüngliche Frage betraf etwas völlig anderes :) Ich bin froh, dass Logans Fix für das funktioniert, wonach Sie gesucht haben, aber es hängt einfach nicht einmal im entferntesten mit der Frage zusammen.
Joe
8
Dieses Plus Joes Mit android:descendantFocusabilityEigenschaft hätte meinen EditTexts innerhalb von einem auf ListViewdie Tastatur zu lösen richtig, upvoted beide. android:descendantFocusabilityan sich hat es nicht geschafft und ich war nicht im entferntesten begeistert von @Overriding onItemSelectedallen 14 EditTextSekunden, mit denen ich zu tun habe. :) Vielen Dank!
Thomson Comer
Dies hat bei mir funktioniert, aber wenn Sie TabHost oder TabActivity verwenden, müssen Sie android: windowSoftInputMode = "adjustPan" für die TabActivity-Definition im Manifest festlegen.
Kiduxa
18

Meine Aufgabe war es zu implementieren, ListViewwas sich beim Klicken erweitert. Der zusätzliche Bereich zeigt an, EditTextwo Sie Text eingeben können. Die App sollte ab Version 2.2 funktionsfähig sein (bis zu 4.2.2 zum Zeitpunkt des Schreibens)

Ich habe zahlreiche Lösungen aus diesem Beitrag und anderen, die ich finden konnte, ausprobiert. testete sie auf 2.2 bis 4.2.2 Geräten. Keine der Lösungen war auf allen Geräten ab Version 2.2 zufriedenstellend, wobei jede Lösung unterschiedliche Probleme aufwies.

Ich wollte meine endgültige Lösung teilen:

  1. Stellen Sie die Listenansicht auf ein android:descendantFocusability="afterDescendants"
  2. Stellen Sie die Listenansicht auf ein setItemsCanFocus(true);
  3. Stellen Sie Ihre Aktivität auf android:windowSoftInputMode="adjustResize" Viele Leute schlagen vor adjustPan, adjustResizegeben aber viel besseres ux imho, testen Sie dies einfach in Ihrem Fall. MitadjustPan Sie beispielsweise unterste Listenelemente, die verdeckt sind. Die Dokumente schlagen vor, dass ("Dies ist im Allgemeinen weniger wünschenswert als die Größenänderung"). Ebenfalls in 4.0.4, nachdem der Benutzer mit der Eingabe auf der Softtastatur begonnen hat, wird der Bildschirm nach oben verschoben.
  4. Unter 4.2.2 adjustResizegibt es einige Probleme mit dem EditText-Fokus. Die Lösung besteht darin, die rjrjr-Lösung aus diesem Thread anzuwenden. Es sieht beängstigend aus, ist es aber nicht. Und es funktioniert. Probier es einfach.

Zusätzliche 5. Aufgrund der EditTextAktualisierung des Adapters (aufgrund der Größenänderung der Ansicht), wenn der Fokus auf frühere HoneyComb-Versionen gelegt wird, trat ein Problem mit umgekehrten Ansichten auf: Abrufen der Ansicht für ListView-Element / umgekehrte Reihenfolge in 2.2; funktioniert am 4.0.3

Wenn Sie einige Animationen adjustPanausführen, möchten Sie möglicherweise das Verhalten für Versionen vor der Wabe ändern, damit die Größenänderung nicht ausgelöst wird und der Adapter die Ansichten nicht aktualisiert. Sie müssen nur so etwas hinzufügen

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

All dies ergibt einen akzeptablen UX auf 2.2 - 4.2.2 Geräten. Ich hoffe, es wird den Menschen Zeit sparen, da ich mindestens einige Stunden gebraucht habe, um zu diesem Schluss zu kommen.

AndroidGecko
quelle
1
Nur der erste und zweite Schritt genug in meinem Fall
Kalpesh Lakhani
Funktioniert perfekt mit meinem xElement.setOnClickListener (..) in ArrayAdapter und einem ListView.setOnItemClickListener (...) - endlich !! Vielen Dank.
Javatar
10

Das hat mir das Leben gerettet --->

  1. setze diese Zeile

    ListView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);

  2. Geben Sie dann in Ihrem Manifest im Aktivitäts-Tag Folgendes ein ->

    <activity android:windowSoftInputMode="adjustPan">

Deine übliche Absicht

Ravi Ranjan
quelle
7

Wir versuchen dies auf einer kurzen Liste, die kein View-Recycling durchführt. So weit, ist es gut.

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
quelle
Leicht modifiziert, funktioniert diese Lösung für mich nach 2 Tagen @ # ( : if (sticky! = Null) {sticky.RequestFocus (); sticky.RequestFocusFromTouch (); sticky = null;}
Chris van de Steeg
4

Dieser Beitrag stimmte genau mit meinen Keywords überein. Ich habe einen ListView-Header mit einem EditText für die Suche und einer Suchschaltfläche.

Um den EditText nach dem Verlieren des anfänglichen Fokus zu fokussieren, habe ich nur Folgendes gefunden:

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

Viele Stunden verloren und es ist keine echte Lösung. Hoffe es hilft jemandem hart.

Rafael Sanches
quelle
2

Wenn die Liste dynamisch ist und fokussierbare Widgets enthält, ist die richtige Option zu verwenden RecyclerView anstelle von ListView IMO zu verwenden.

Die Abhilfen , dass Satz adjustPan, FOCUS_AFTER_DESCENDANTSoder manuell fokussierten Position erinnern, sind in der Tat nur Workarounds. Sie haben Eckfälle (Bildlauf + Probleme mit der Softtastatur, Caret-Positionsänderung in EditText). Sie ändern nicht die Tatsache , dass Listview / zerstört Ansichten schafft en masse währendnotifyDataSetChanged .

Mit RecyclerView benachrichtigen Sie über einzelne Einfügungen, Aktualisierungen und Löschungen. Die fokussierte Ansicht wird nicht neu erstellt, sodass keine Probleme mit Formularsteuerelementen den Fokus verlieren. Als zusätzlichen Bonus animiert RecyclerView das Einfügen und Entfernen von Listenelementen.

Hier ist ein Beispiel aus offiziellen Dokumenten, wie Sie beginnen können RecyclerView: Entwicklerhandbuch - Erstellen Sie eine Liste mit RecyclerView

Pēteris Caune
quelle
1

android:windowSoftInputMode="stateAlwaysHidden"Manchmal, wenn Sie in Manifest-Aktivitäten oder XML verwenden, verliert es diesmal den Tastaturfokus. Suchen Sie also zuerst in Ihrer XML-Datei nach dieser Eigenschaft und manifestieren Sie sie. Wenn sie vorhanden ist, entfernen Sie sie einfach. Nachdem Sie diese Option hinzugefügt haben, um die Datei in android:windowSoftInputMode="adjustPan"der Nebenaktivität zu manifestieren, fügen Sie diese Eigenschaft der Listenansicht in XML hinzuandroid:descendantFocusability="beforeDescendants"


quelle
0

Eine andere einfache Lösung besteht darin, Ihren onClickListener in der Methode getView (..) Ihres ListAdapters zu definieren.

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;

Auf diese Weise können Sie auf Ihre Zeile klicken und auch auf Ihre innere Ansicht :)

Weber Antoine
quelle
1
Die Frage bezieht sich auf den Fokus, nicht auf die Klickbarkeit.
Joe
0

Der wichtigste Teil ist, den Fokus für die Listenzelle zum Laufen zu bringen. Insbesondere für Listen auf Google TV ist dies wichtig:

Die setItemsCanFocus- Methode der Listenansicht erledigt den Trick:

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

Meine Listenzelle xml beginnt wie folgt:

<?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 sind auch wichtig für die D-Pad-Navigation.

Weitere Details finden Sie in den anderen tollen Antworten.

Michael Biermann
quelle
0

Ich habe gerade eine andere Lösung gefunden. Ich glaube, es ist eher ein Hack als eine Lösung, aber es funktioniert auf Android 2.3.7 und Android 4.3 (ich habe sogar dieses gute alte D-Pad getestet)

Initiieren Sie Ihre Webansicht wie gewohnt und fügen Sie Folgendes hinzu: (danke Michael Bierman)

listView.setItemsCanFocus(true);

Während des getView-Aufrufs:

editText.setOnFocusChangeListener(
    new OnFocusChangeListener(View view,boolean hasFocus){
        view.post(new Runnable() {
            @Override
            public void run() {
                view.requestFocus();
                view.requestFocusFromTouch();
            }
     });
alaeri
quelle
Was ist Ansicht hier in view.requestFocus?
Narendra Singh
Dies ist eine alte Antwort, aber sie bezog sich auf die Ansicht in dem Bereich, der als Argument für den OnFocusChangeListener gegeben wurde, glaube ich
alaeri
1
Es verursacht eine Schleife beim Ändern des Fokus.
Morteza Rastgoo
0

Probieren Sie es einfach aus

android:windowSoftInputMode="adjustNothing"

in dem

Aktivität

Abschnitt Ihres Manifests. Ja, es passt nichts an, was bedeutet, dass der editText dort bleibt, wo er ist, wenn IME geöffnet wird. Aber das ist nur eine kleine Unannehmlichkeit, die das Problem des Fokusverlusts immer noch vollständig löst.

Tor_Gash
quelle