Fokus EditText wewnątrz ListView

121

Do tej pory spędziłem nad tym około 6 godzin i uderzałem tylko w blokady drogowe. Ogólne założenie jest takie, że istnieje wiersz ListView(niezależnie od tego, czy został wygenerowany przez adapter, czy dodany jako widok nagłówka), który zawiera EditTextwidget i plik Button. Chcę tylko móc użyć kulki / strzałek, aby przejść selektorem do poszczególnych elementów, tak jak zwykle, ale kiedy dojdę do określonego wiersza - nawet jeśli muszę wyraźnie zidentyfikować wiersz - ma on opcję fokusu dziecko, chcę, aby dziecko skupiło się, zamiast wskazywać pozycję za pomocą selektora.

Wypróbowałem wiele możliwości i do tej pory nie miałem szczęścia.

układ:

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

Widok nagłówka:

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

Zakładając, że w adapterze znajdują się inne pozycje, użycie klawiszy strzałek spowoduje przesunięcie wyboru w górę / w dół listy zgodnie z oczekiwaniami; ale kiedy przechodzimy do wiersza nagłówka, jest on również wyświetlany za pomocą selektora i nie ma możliwości skupienia się na EditTextużywaniu kulki. Uwaga: dotknięcie przycisku EditText spowoduje skupienie się w tym momencie, jednak zależy to od ekranu dotykowego, co nie powinno być wymagane.

ListViewnajwyraźniej ma dwa tryby w tym względzie:
1 setItemsCanFocus(true).: selektor nigdy nie jest wyświetlany, ale EditTextmoże uzyskać ostrość, gdy używasz strzałek. Algorytm wyszukiwania fokusowego jest trudny do przewidzenia i nie ma wizualnej informacji zwrotnej (w jakimkolwiek wierszu: z wybranymi elementami podrzędnymi lub nie), z których oba mogą dać użytkownikowi nieoczekiwane wrażenia.
2.setItemsCanFocus(false) .: Selektor jest zawsze rysowany w trybie bezdotykowym i EditTextnigdy nie może uzyskać ostrości - nawet jeśli go dotkniesz.

Co gorsza, dzwonię editTextView.requestFocus() zwraca prawdę, ale w rzeczywistości nie skupia się na EditText.

To, co wyobrażam, jest w zasadzie hybrydą 1 i 2, gdzie zamiast ustawienia listy, czy wszystkie elementy są aktywne, czy nie, chcę ustawić ostrość dla jednego elementu na liście, tak aby selektor płynnie przechodził od wyboru cały wiersz dla elementów, na które nie można skupić się, i przechodzenie przez drzewo aktywności dla elementów, które zawierają elementy podrzędne, na które można ustawić fokus.

Jacyś chętni?

Joe
źródło

Odpowiedzi:

101

Przepraszam, odpowiedziałem na moje własne pytanie. Może nie jest to najbardziej poprawne lub najbardziej eleganckie rozwiązanie, ale działa dla mnie i zapewnia całkiem solidne wrażenia użytkownika. Zajrzałem do kodu ListView, aby zobaczyć, dlaczego te dwa zachowania są tak różne, i natknąłem się na to z ListView.java:

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

Tak więc, dzwoniąc setItemsCanFocus(false), ustawia się również zdolność skupiania się na potomku, tak aby żadne dziecko nie mogło się skupić. To wyjaśnia, dlaczego nie mogłem po prostu przełączyć się mItemsCanFocusw OnItemSelectedListener ListView - ponieważ ListView blokował wtedy fokus dla wszystkich dzieci.

Co mam teraz:

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

Używam, beforeDescendantsponieważ selektor będzie rysowany tylko wtedy, gdy sam ListView (nie dziecko) ma fokus, więc domyślne zachowanie musi być takie, że ListView najpierw przejmuje fokus i rysuje selektory.

Następnie w OnItemSelectedListener, ponieważ wiem, który widok nagłówka chcę przesłonić selektor (wymagałoby więcej pracy, aby dynamicznie określić, czy dana pozycja zawiera widok, który można ustawić na fokus), mogę zmienić możliwość ustawiania ostrości podrzędnej i ustawić fokus na EditText. A kiedy wyjdę z tego nagłówka, zmień go ponownie.

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

Zwróć uwagę na skomentowane setItemsCanFocuspołączenia. Dzięki tym wywołaniom uzyskałem poprawne zachowanie, ale setItemsCanFocus(false)spowodowałem , że fokus przeskoczył z EditText do innego widżetu poza ListView, z powrotem do ListView i wyświetlił selektor na następnym wybranym elemencie, a to przeskakiwanie było rozpraszające. Usunięcie zmiany ItemsCanFocus i samo przełączenie ostrości potomnej dało mi pożądane zachowanie. Wszystkie elementy rysują selektor normalnie, ale po przejściu do wiersza z tekstem edycji, zamiast tego skupiał się na polu tekstowym. Następnie, kiedy wychodził z tego EditText, zaczął ponownie rysować selektor.

Joe
źródło
bardzo fajne, jeszcze nie testowane. Czy testowałeś na 1.5, 1.6 i 3.0?
Rafael Sanches
Rafael Sanches: Nie dotykałem projektu od 2.1, ale wtedy potwierdzono, że działa w 1.5, 1.6 i 2.1. Nie gwarantuję, że nadal będzie działać w wersji 2.2 lub nowszej.
Joe
13
potrzebny tylko android: descendantFocusability = "afterDescendants" - w każdym razie +1
kellogs
5
@kellogs: Tak, descendantFocusability="afterDescendants"pozwoli na EditText wziąć ostrość wewnątrz ListView, ale potem masz żadnego wyboru elementu listy podczas nawigowania z padu kierunkowego. Moim zadaniem było umieszczenie selektora elementów listy we wszystkich wierszach oprócz tego z EditText. Cieszę się, że to pomogło. FWIW, w końcu ponownie oceniliśmy tę implementację i zdecydowaliśmy, że element, na którym można skupić się w ListView, nie jest po prostu idiomatycznym projektem interfejsu użytkownika systemu Android, więc porzuciliśmy ten pomysł na rzecz podejścia bardziej przyjaznego dla systemu Android.
Joe
5
Czy zostało to przetestowane na Ice Cream Sandwich? Nie mogę sprawić, żeby to działało. Dzięki.
Rajat Anantharam
99

To mi pomogło.
W twoim manifeście:

<activity android:name= ".yourActivity" android:windowSoftInputMode="adjustPan"/>
logan
źródło
3
Nie jestem pewien, czy widzę znaczenie. Ustawienie windowSoftInputMode po prostu zmienia sposób, w jaki edytor IME dostosowuje pozostałą zawartość okna, gdy jest ono otwarte. Nie pozwala na selektywną zmianę typu fokusu ListView. Czy możesz wyjaśnić trochę więcej, jak to się ma do początkowego przypadku użycia?
Joe,
1
@Joe Po otwarciu edytora IME kursor - i prawdopodobnie również fokus - po prostu przeskakuje po ekranie, uniemożliwiając wprowadzenie tekstu. Twój OnItemSelectedListenernie zmienia tego. Jednak proste rozwiązanie Iogana działa jak urok, dzięki!
Gubbel
2
@Gubbel: Rzeczywiście, to w ogóle by tego nie zmieniło, ponieważ pierwotne pytanie dotyczyło czegoś zupełnie innego :) Cieszę się, że poprawka logana działa na to, czego szukałeś, ale po prostu nie jest nawet związana z tym pytaniem.
Joe
8
Korzystanie z tej Plus Joe android:descendantFocusabilitywłasność got my EditTexts wewnątrz ListViewrozwiązywania klawiaturę poprawnie, upvoted obu. android:descendantFocusabilitysamo w sobie nie wystarczyło i nie byłem entuzjastycznie nastawiony @Overriding onItemSelecteddo wszystkich 14 EditTextsekund, z którymi mam do czynienia. :) Dzięki!
Thomson Comer
U mnie to zadziałało, ale zauważ, że jeśli używasz TabHost lub TabActivity, musisz ustawić android: windowSoftInputMode = ”adjustPan” dla definicji TabActivity w manifeście.
kiduxa
18

Moim zadaniem było wdrożenie, ListViewktóre rozszerza się po kliknięciu. Dodatkowe miejsce pokazuje, EditTextgdzie można wprowadzić tekst. Aplikacja powinna działać w wersji 2.2+ (do 4.2.2 w momencie pisania tego)

Wypróbowałem wiele rozwiązań z tego postu i innych, które mogłem znaleźć; przetestowałem je na urządzeniach od 2.2 do 4.2.2. Żadne z rozwiązań nie było satysfakcjonujące na wszystkich urządzeniach 2.2+, każde rozwiązanie prezentowało inne problemy.

Chciałem podzielić się moim ostatecznym rozwiązaniem:

  1. ustaw widok listy na android:descendantFocusability="afterDescendants"
  2. ustaw widok listy na setItemsCanFocus(true);
  3. ustaw swoją aktywność na android:windowSoftInputMode="adjustResize" Wiele osób sugeruje, adjustPanale adjustResizedaje znacznie lepsze ux imho, po prostu przetestuj to w swoim przypadku. ZadjustPan uzyskasz na przykład zasłonięte dolne pozycje z listy. Dokumenty sugerują, że („Generalnie jest to mniej pożądane niż zmiana rozmiaru”). Również w 4.0.4, gdy użytkownik zacznie pisać na klawiaturze ekranowej, ekran przesunie się do góry.
  4. w 4.2.2 z adjustResizeproblemami z fokusem EditText. Rozwiązaniem jest zastosowanie rozwiązania rjrjr z tego wątku. Wygląda strasznie, ale tak nie jest. I to działa. Po prostu spróbuj.

Dodatkowe 5. Ze względu na odświeżanie adaptera (z powodu zmiany rozmiaru widoku), gdy EditTextzyski skupiają się na wersjach wcześniejszych niż HoneyComb, znalazłem problem z odwróconymi widokami: pobieranie widoku elementu ListView / odwrócenie kolejności w wersji 2.2; działa na 4.0.3

Jeśli wykonujesz jakieś animacje, możesz chcieć zmienić zachowanie na adjustPanwersje wcześniejsze niż plaster miodu, aby funkcja zmiany rozmiaru nie uruchamiała się, a adapter nie odświeżał widoków. Musisz tylko dodać coś takiego

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

Wszystko to daje akceptowalny UX na urządzeniach 2.2 - 4.2.2. Mam nadzieję, że zaoszczędzi to ludziom trochę czasu, ponieważ dojście do tego wniosku zajęło mi co najmniej kilka godzin.

AndroidGecko
źródło
1
w moim przypadku wystarczy pierwszy i drugi krok
Kalpesh Lakhani
Doskonale współpracuje z moim xElement.setOnClickListener (..) w ArrayAdapter i ListView.setOnItemClickListener (...) - wreszcie !! Wielkie dzięki.
Javatar
10

To uratowało mi życie --->

  1. ustaw tę linię

    ListView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);

  2. Następnie w swoim manifeście w tagu aktywności wpisz to ->

    <activity android:windowSoftInputMode="adjustPan">

Twój zwykły zamiar

ravi ranjan
źródło
7

Próbujemy tego na krótkiej liście, która nie przewiduje recyklingu. Na razie w porządku.

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>

Jawa:

/**
 * 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
źródło
Nieznacznie zmodyfikowane rozwiązanie działa u mnie po 2 dniach @ # ( : if (sticky! = Null) {sticky.RequestFocus (); sticky.RequestFocusFromTouch (); sticky = null;}
Chris van de Steeg
4

ten post pasował dokładnie do moich słów kluczowych. Mam nagłówek ListView z wyszukiwaniem EditText i przyciskiem wyszukiwania.

Aby skupić się na EditText po utracie początkowego fokusu, jedyne HACK, które znalazłem, to:

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

Straciłem wiele godzin i to nie jest prawdziwa poprawka. Mam nadzieję, że pomoże to komuś twardemu.

Rafael Sanches
źródło
2

Jeśli lista jest dynamiczna i zawiera widżety, na które można ustawić fokus, właściwą opcją jest użycie RecyclerView zamiast ListView IMO.

Do obejścia że zestaw adjustPan, FOCUS_AFTER_DESCENDANTSlub ręcznie pamiętają skoncentrowaną pozycję, rzeczywiście są tylko rozwiązania. Mają narożne przypadki (przewijanie + problemy z klawiaturą miękką, zmiana pozycji kursora w EditText). Nie zmienia to faktu, że ListView tworzy / niszczy widoki masowo czasie notifyDataSetChanged.

Dzięki RecyclerView powiadamiasz o poszczególnych wstawieniach, aktualizacjach i usunięciach. Widok skupiony nie jest odtwarzany, więc nie ma problemów z utratą aktywności kontrolek formularza. Jako dodatkowy bonus, RecyclerView animuje wstawianie i usuwanie elementów listy.

Oto przykład z oficjalnej dokumentacji, jak zacząć RecyclerView: Przewodnik dla programistów - Utwórz listę za pomocą RecyclerView

Pēteris Caune
źródło
1

Czasami, gdy używasz android:windowSoftInputMode="stateAlwaysHidden"w manifest activity lub xml, tym razem stracisz fokus klawiatury. Więc najpierw sprawdź tę właściwość w swoim xml i manifeście, jeśli tam jest, po prostu ją usuń. Po dodaniu tych opcji, aby zamanifestować plik w działaniu pobocznym android:windowSoftInputMode="adjustPan"i dodać tę właściwość do widoku listy w formacie xmlandroid:descendantFocusability="beforeDescendants"


źródło
0

Innym prostym rozwiązaniem jest zdefiniowanie onClickListener w metodzie getView (..) Twojego 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;

W ten sposób można kliknąć wiersz, a także widok wewnętrzny :)

Weber Antoine
źródło
1
Pytanie dotyczy koncentracji, a nie klikalności.
Joe
0

Najważniejsze jest, aby fokus działał dla komórki listy. Szczególnie w przypadku listy w Google TV jest to niezbędne:

Metoda setItemsCanFocus widoku listy załatwia sprawę :

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

Moja komórka listy xml zaczyna się następująco:

<?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 są również ważne dla nawigacji D-Pad.

Aby uzyskać więcej informacji, zapoznaj się z innymi świetnymi odpowiedziami.

Michael Biermann
źródło
0

Właśnie znalazłem inne rozwiązanie. Uważam, że to bardziej hack niż rozwiązanie, ale działa na Androidzie 2.3.7 i Androidzie 4.3 (testowałem nawet tego starego dobrego D-pad)

init your webview jak zwykle i dodaj to: (dzięki Michael Bierman)

listView.setItemsCanFocus(true);

Podczas połączenia getView:

editText.setOnFocusChangeListener(
    new OnFocusChangeListener(View view,boolean hasFocus){
        view.post(new Runnable() {
            @Override
            public void run() {
                view.requestFocus();
                view.requestFocusFromTouch();
            }
     });
alaeri
źródło
co tu jest view.requestFocus?
Narendra Singh
to jest stara odpowiedź, ale odnosiła się do widoku w zakresie podanym jako argument dla OnFocusChangeListener, jak sądzę
alaeri
1
Powoduje pętlę w zmianie ostrości.
Morteza Rastgoo
0

Po prostu spróbuj tego

android:windowSoftInputMode="adjustNothing"

w

czynność

sekcja manifestu. Tak, nie dostosowuje niczego, co oznacza, że ​​editText pozostanie tam, gdzie jest, gdy jest otwierany IME. Ale to tylko mała niedogodność, która nadal całkowicie rozwiązuje problem utraty koncentracji.

Tor_Gash
źródło