Фокусируемый EditText внутри ListView

Я потратил на это около 6 часов и не наткнулся ни на что, кроме препятствий. Общая предпосылка состоит в том, что в ListView (генерируется ли она адаптером или добавляется в виде заголовка) есть строка, содержащая виджет EditText и Button. Все, что я хочу сделать, это иметь возможность использовать джогбол / стрелки для перемещения селектора к отдельным элементам, как обычно, но когда я перехожу к определенной строке - даже если мне нужно явно идентифицировать строку - у которой есть фокусируемый child, я хочу, чтобы этот ребенок взял фокус вместо того, чтобы указывать позицию с помощью селектора.

Я пробовал много возможностей, и пока мне не повезло.

макет:

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

Заголовок:

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

Предполагая, что в адаптере есть другие элементы, использование клавиш со стрелками будет перемещать выбор вверх / вниз в списке, как и ожидалось; но при переходе к строке заголовка она также отображается с помощью селектора, и нет возможности сфокусироваться на EditText с помощью джогбола. Примечание: нажатие на EditText сфокусирует его на этой точке, однако это зависит от сенсорного экрана, что не является обязательным.

В этом отношении ListView очевидно имеет два режима:
1. setItemsCanFocus(true): селектор никогда не отображается, но EditText может получить фокус при использовании стрелок. Алгоритм поиска по фокусу трудно предсказать, и нет визуальной обратной связи (в любых строках: есть ли дочерние элементы с фокусом или нет) о том, какой элемент выбран, что может дать пользователю неожиданный опыт.
2. setItemsCanFocus(false): селектор всегда нарисовано в режиме без касания, и EditText никогда не сможет сфокусироваться, даже если вы нажмете на него.

Что еще хуже, вызов editTextView.requestFocus() возвращает true, но фактически не дает фокус EditText.

То, что я представляю, в основном представляет собой гибрид 1 и 2, где вместо настройки списка, если фокусируются все элементы или нет, я хочу установить фокусируемость для одного элемент в списке, так что селектор плавно переходит от выбора всей строки для нефокусируемых элементов и обхода дерева фокуса для элементов, которые содержат фокусируемые дочерние элементы.

Есть берущие?


person Joe    schedule 21.04.2010    source источник


Ответы (13)


Извините, ответил на свой вопрос. Возможно, это не самое правильное и элегантное решение, но оно работает для меня и дает довольно надежный пользовательский интерфейс. Я просмотрел код ListView, чтобы понять, почему эти два поведения настолько разные, и наткнулся на это из ListView.java:

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

Таким образом, при вызове setItemsCanFocus(false) он также устанавливает фокусируемость потомка, чтобы ни один дочерний элемент не мог получить фокус. Это объясняет, почему я не мог просто переключить mItemsCanFocus в OnItemSelectedListener ListView - потому что ListView тогда блокировал фокус для всех дочерних элементов.

Что у меня сейчас:

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

Я использую beforeDescendants, потому что селектор будет отображаться только тогда, когда сам ListView (не дочерний элемент) будет иметь фокус, поэтому поведение по умолчанию должно заключаться в том, что ListView сначала берет фокус и рисует селекторы.

Затем в OnItemSelectedListener, поскольку я знаю, в каком виде заголовка я хочу переопределить селектор (потребуется больше работы, чтобы динамически определить, содержит ли какая-либо данная позиция фокусируемое представление), я могу изменить фокусируемость потомка и установить фокус на EditText. И когда я выхожу из этого заголовка, снова меняю его.

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

Обратите внимание на закомментированные setItemsCanFocus вызовы. С этими вызовами я получил правильное поведение, но setItemsCanFocus(false) заставил фокус перескочить с EditText на другой виджет вне ListView, обратно в ListView и отобразил селектор на следующем выбранном элементе, и этот прыжок фокуса отвлекал. Удаление изменения ItemsCanFocus и просто переключение фокусируемости потомков дало мне желаемое поведение. Все элементы рисуют селектор как обычно, но при переходе к строке с EditText вместо этого он фокусируется на текстовом поле. Затем, продолжая выход из этого EditText, он снова начал рисовать селектор.

person Joe    schedule 21.04.2010
comment
очень круто, еще не тестировал. Вы тестировали на 1.5, 1.6 и 3.0? - person Rafael Sanches; 03.05.2011
comment
Рафаэль Санчес: Я не касался проекта с версии 2.1, но на тот момент было подтверждено, что он работает в 1.5, 1.6 и 2.1. Я не гарантирую, что он по-прежнему работает в версии 2.2 или более поздней. - person Joe; 09.05.2011
comment
нужен только android: ПотомкиFocusability = afterDescendants - все равно +1 - person kellogs; 26.05.2011
comment
@kellogs: да, descendantFocusability="afterDescendants" позволит вашему EditText получить фокус внутри ListView, но тогда вы получите no селектор элементов списка при навигации с помощью dpad. Моя задача заключалась в том, чтобы селектор элементов списка находился во всех строках, кроме строки с EditText. Я рад, что это помогло. FWIW, мы закончили переоценку этой реализации и решили, что фокусируемый внутри ListView - это просто не идиоматический дизайн пользовательского интерфейса Android, поэтому мы отказались от этой идеи в пользу более дружественного для Android подхода. - person Joe; 27.05.2011
comment
Спасибо, чувак, похоже, ты можешь ответить только мне: где инициализировать myEditText? - person Vikas; 27.07.2011
comment
В любом случае я справлюсь, надеюсь, мой пример может помочь другим .. - person Vikas; 27.07.2011
comment
@Vikas: myEditText был просто произвольным примером. Обычно вы инициализируете его в onCreate() действия или всякий раз, когда макет раздувается (или при его создании, если поле создается программно). - person Joe; 04.08.2011
comment
Прохладный. Мне нужно было добавить несколько хуков к событиям щелчка TextEdit, чтобы завершить правильную обработку, но в остальном это работало хорошо. - person Pointer Null; 19.09.2011
comment
+1 к комментарию @kellogs. Мне также нужно было добавить windowSoftInoutMode ниже, чтобы правильно сфокусировать поля EditText. - person Ants; 28.11.2011
comment
Это было проверено на Ice Cream Sandwich? Я не могу заставить его работать. Спасибо. - person Rajat Anantharam; 11.06.2013
comment
@ Джо, что ты в итоге использовал? - person Hades; 03.06.2015
comment
@Hades, ничего ... в итоге мы переделали приложение, чтобы все было в большей степени Android. - person Joe; 05.06.2015
comment
@Joe: метод onItemSelected определен в действии справа, поэтому myEditText должен быть увеличен для каждого элемента в списке. Итак, как мы можем получить myEditText с помощью findViewById или любого другого метода внутри метода onItemSelected? - person Jayesh; 04.01.2016

Это мне помогло.
В вашем манифесте:

<activity android:name= ".yourActivity" android:windowSoftInputMode="adjustPan"/>
person logan    schedule 04.02.2011
comment
Не уверен, что вижу актуальность. Настройка windowSoftInputMode просто изменяет способ, которым IME настраивает остальное содержимое окна, когда оно открыто. Это не позволяет вам выборочно изменять тип фокуса ListView. Не могли бы вы объяснить немного подробнее, как это соотносится с первоначальным вариантом использования? - person Joe; 07.02.2011
comment
@Joe Когда IME открыт, курсор - и, вероятно, фокус тоже - просто прыгает по моему экрану, делая невозможным ввод текста. Ваш OnItemSelectedListener этого не меняет. Однако простое решение Иогана работает как шарм, спасибо! - person Gubbel; 17.05.2011
comment
@Gubbel: В самом деле, это вообще не изменит этого, потому что исходный вопрос был о чем-то совершенно другом :) Рад, что исправление логана работает для того, что вы искали, но это просто даже отдаленно не связано с вопросом. - person Joe; 27.05.2011
comment
Используя этот плюс, свойство android:descendantFocusability Джо получил мои EditText внутри ListView правильного разрешения клавиатуры, проголосовал за оба. android:descendantFocusability сам по себе не помог, и я не был в восторге от @Overriding onItemSelected всех 14 EditText, с которыми мне приходилось иметь дело. :) Спасибо! - person Thomson Comer; 31.07.2011
comment
У меня это сработало, но обратите внимание, если вы используете TabHost или TabActivity, вам нужно установить android: windowSoftInputMode = "adjustPan" для определения TabActivity в манифесте. - person kiduxa; 09.06.2013
comment
Потрясающий! Я был на грани своего остроумия, редактируя тексты, борясь за фокус, но это помогло. - person piccy; 30.11.2017
comment
кто-нибудь им пользуется, проблема с прокруткой? cz у меня проблема, - person Budi Mulyo; 27.11.2019

Моей задачей было реализовать ListView, который расширяется при нажатии. Дополнительное пространство показывает EditText, где вы можете ввести текст. Приложение должно работать на версиях 2.2+ (до 4.2.2 на момент написания)

Я пробовал множество решений из этого поста и других, которые смог найти; тестировал их на устройствах от 2.2 до 4.2.2. Ни одно из решений не было удовлетворительным на всех устройствах 2.2+, каждое решение было связано с разными проблемами.

Я хотел поделиться своим окончательным решением:

  1. установить listview на android:descendantFocusability="afterDescendants"
  2. установить listview на setItemsCanFocus(true);
  3. установите для своей активности android:windowSoftInputMode="adjustResize" Многие люди предлагают adjustPan, но adjustResize дает гораздо лучший ux imho, просто проверьте это в своем случае. Например, с adjustPan вы получите скрытые элементы нижнего списка. Документы предполагают, что («Обычно это менее желательно, чем изменение размера»). Также в 4.0.4 после того, как пользователь начинает печатать на виртуальной клавиатуре, экран перемещается вверх.
  4. в 4.2.2 с adjustResize есть некоторые проблемы с фокусом EditText. Решение состоит в том, чтобы применить решение rjrjr из этой ветки. Это выглядит пугающе, но это не так. И это работает. Просто попробуйте.

Дополнительно 5. Из-за обновления адаптера (из-за изменения размера представления), когда EditText фокусируется на версиях до HoneyComb, я обнаружил проблему с перевернутыми представлениями: получение View для элемента ListView / обратный порядок на 2.2; работает на 4.0.3

Если вы делаете некоторые анимации, вы можете изменить поведение на adjustPan для версий до сот, чтобы изменение размера не запускалось, а адаптер не обновлял представления. Вам просто нужно добавить что-то вроде этого

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

Все это дает приемлемый ux на устройствах 2.2 - 4.2.2. Надеюсь, это сэкономит людям время, так как мне потребовалось как минимум несколько часов, чтобы прийти к такому выводу.

person AndroidGecko    schedule 04.04.2013
comment
в моем случае достаточно первого и второго шага - person Kalpesh Lakhani; 01.05.2015
comment
Отлично работает с моим xElement.setOnClickListener (..) в ArrayAdapter и ListView.setOnItemClickListener (...) - наконец !! Большое спасибо. - person Javatar; 27.08.2015

Это спасло мне жизнь --->

  1. установить эту линию

    ListView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);

  2. Затем в вашем манифесте в теге активности введите это ->

    <activity android:windowSoftInputMode="adjustPan">

Ваше обычное намерение

person ravi ranjan    schedule 18.10.2015

Мы пробуем это на коротком списке, который не использует переработку представлений. Все идет нормально.

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>

Ява:

/**
 * 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();
        }
      }
    });
  }
}
person rjrjr    schedule 25.03.2013
comment
Немного измененное, это решение работает у меня через 2 дня @ # (: if (sticky! = Null) {sticky.RequestFocus (); sticky.RequestFocusFromTouch (); sticky = null;} - person Chris van de Steeg; 19.06.2013

этот пост точно соответствует моим ключевым словам. У меня есть заголовок ListView с поиском EditText и кнопкой поиска.

Чтобы сфокусироваться на EditText после потери начального фокуса, я нашел только один ХАК:

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

Потеряли много часов, и это не настоящее решение. Надеюсь, это поможет кому-то крутому.

person Rafael Sanches    schedule 21.10.2010

Если список является динамическим и содержит настраиваемые виджеты, то правильным вариантом является использование RecyclerView вместо ListView IMO.

Обходные пути, которые устанавливают adjustPan, FOCUS_AFTER_DESCENDANTS или запоминают вручную положение фокуса, на самом деле являются обходными путями. У них есть угловые случаи (прокрутка + проблемы с мягкой клавиатурой, изменение положения курсора в EditText). Они не меняют того факта, что ListView создает / уничтожает представления в массовом порядке в течение notifyDataSetChanged.

С RecyclerView вы уведомляете об отдельных вставках, обновлениях и удалениях. Сфокусированное представление не воссоздается, поэтому нет проблем с потерей фокуса элементами управления формы. В качестве дополнительного бонуса RecyclerView анимирует вставку и удаление элементов списка.

Вот пример из официальных документов о том, как начать работу с RecyclerView: Руководство разработчика - Создать Список с RecyclerView

person Pēteris Caune    schedule 03.02.2016

Иногда, когда вы используете android:windowSoftInputMode="stateAlwaysHidden"in manifest activity или xml, в этот раз он теряет фокус клавиатуры. Поэтому сначала проверьте это свойство в своем xml и манифесте, если оно есть, просто удалите его. После добавления этих параметров в файл манифеста в побочном действии android:windowSoftInputMode="adjustPan" и добавление этого свойства в список в xml android:descendantFocusability="beforeDescendants"

person Community    schedule 25.11.2015

Еще одно простое решение - определить onClickListener в методе getView (..) вашего 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;

Таким образом, ваша строка станет интерактивной, как и ваше внутреннее представление :)

person Weber Antoine    schedule 04.10.2011
comment
Вопрос касается фокусировки, а не кликабельности. - person Joe; 11.10.2011

Самая важная часть - заставить фокус работать для ячейки списка. Особенно для списка на Google TV это важно:

Метод setItemsCanFocus представления списка делает свое дело:

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

Моя ячейка списка xml начинается следующим образом:

<?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 также важны для навигации с помощью D-Pad.

Для получения дополнительной информации ознакомьтесь с другими отличными ответами.

person Michael Biermann    schedule 04.12.2012

Я только что нашел другое решение. Я считаю, что это скорее взлом, чем решение, но он работает на Android 2.3.7 и Android 4.3 (я даже тестировал этот старый добрый D-pad)

запустите свой веб-просмотр, как обычно, и добавьте это: (спасибо Майклу Бирману)

listView.setItemsCanFocus(true);

Во время вызова getView:

editText.setOnFocusChangeListener(
    new OnFocusChangeListener(View view,boolean hasFocus){
        view.post(new Runnable() {
            @Override
            public void run() {
                view.requestFocus();
                view.requestFocusFromTouch();
            }
     });
person alaeri    schedule 22.11.2013
comment
что здесь в view.requestFocus? - person Narendra Singh; 10.08.2015
comment
это старый ответ, но он относится к представлению в области видимости, данной как аргумент для OnFocusChangeListener, я считаю - person alaeri; 12.08.2015
comment
Это вызывает цикл смены фокуса. - person Morteza Rastgoo; 14.02.2016

Просто попробуй это

android:windowSoftInputMode="adjustNothing"

в

Мероприятия

раздел вашего манифеста. Да, он ничего не регулирует, а это значит, что editText останется там, где он есть, когда открывается IME. Но это всего лишь небольшое неудобство, которое все же полностью решает проблему потери фокуса.

person Tor_Gash    schedule 20.02.2016

В моем случае в представлении списка есть 14 входных текстов редактирования. Проблема, с которой я столкнулся, когда клавиатура открывается, редактируется текст, фокус теряется, прокручивается макет, и как только сфокусированный вид не виден для пользовательской клавиатуры вниз. Это плохо сказалось на пользовательском опыте. Я не могу использовать windowSoftInputMethod = adjustPan. Итак, после стольких поисков я нашел ссылку, которая расширяет настраиваемый макет и устанавливает данные для просмотра в качестве адаптера с помощью LinearLayout и scrollView и хорошо работают в моем случае.

person Sumit Kumar    schedule 12.10.2020