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

121

Я потратил на это около 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использовании jogball. Примечание: нажатие на EditText значок сфокусирует его в этой точке, однако это зависит от сенсорного экрана, что не является обязательным требованием.

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

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

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

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

Джо
источник

Ответы:

101

Извините, ответил на свой вопрос. Возможно, это не самое правильное и элегантное решение, но оно работает для меня и дает довольно надежный пользовательский интерфейс. Я просмотрел код 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, он снова начал рисовать селектор.

Джо
источник
очень круто, еще не тестировал. Вы тестировали на 1.5, 1.6 и 3.0?
Рафаэль Санчес
Рафаэль Санчес: Я не касался проекта с версии 2.1, но на тот момент было подтверждено, что он работает в 1.5, 1.6 и 2.1. Я не гарантирую, что он по-прежнему работает в версии 2.2 или более поздней.
Джо,
13
нужен только android: ПотомкиFocusability = "afterDescendants" - в любом случае +1
kellogs
5
@kellogs: да, descendantFocusability="afterDescendants"ваш EditText сможет сосредоточиться внутри ListView, но тогда вы получите не селектор элемента списка во время навигации с DPad. Моя задача заключалась в том, чтобы селектор элементов списка находился во всех строках, кроме строки с EditText. Я рад, что это помогло. FWIW, мы закончили переоценку этой реализации и решили, что фокусируемый внутри ListView - это просто не идиоматический дизайн пользовательского интерфейса Android, поэтому мы отказались от этой идеи в пользу более удобного для Android подхода.
Джо
5
Это было проверено на Ice Cream Sandwich? Я не могу заставить его работать. Спасибо.
Раджат Анантарам
99

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

<activity android:name= ".yourActivity" android:windowSoftInputMode="adjustPan"/>
логан
источник
3
Не уверен, что вижу актуальность. Настройка windowSoftInputMode просто изменяет способ, которым IME настраивает остальное содержимое окна, когда оно открыто. Это не позволяет вам выборочно изменять тип фокуса ListView. Не могли бы вы объяснить немного подробнее, как это соотносится с первоначальным вариантом использования?
Джо
1
@Joe Когда IME открыт, курсор - и, вероятно, фокус тоже - просто прыгает по моему экрану, делая невозможным ввод текста. Вы OnItemSelectedListenerэтого не меняете. Однако простое решение Иогана работает как шарм, спасибо!
Gubbel
2
@Gubbel: В самом деле, это вообще не изменит этого, потому что исходный вопрос был о чем-то совершенно другом :) Рад, что исправление логана работает для того, что вы искали, но оно просто даже отдаленно не связано с вопросом.
Джо
8
Используя это android:descendantFocusabilityсвойство плюс, Джо получил мои EditTexts внутри ListViewправильного разрешения клавиатуры, проголосовал за оба. android:descendantFocusabilityсамо по себе не помогло, и я не испытывал никакого энтузиазма в @Overriding onItemSelectedтечение всех 14 EditTextсекунд, с которыми мне приходилось иметь дело. :) Спасибо!
Thomson Comer
Это сработало для меня, но обратите внимание, если вы используете TabHost или TabActivity, вам нужно установить android: windowSoftInputMode = ”adjustPan” для определения TabActivity в манифесте.
kiduxa
18

Моей задачей было реализовать то, 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, я обнаружил проблему с перевернутыми представлениями: получение представления для элемента 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. Надеюсь, это сэкономит людям время, так как мне потребовалось как минимум несколько часов, чтобы прийти к такому выводу.

AndroidGecko
источник
1
в моем случае достаточно первого и второго шага
Калпеш Лахани
Прекрасно работает с моим xElement.setOnClickListener (..) в ArrayAdapter и ListView.setOnItemClickListener (...) - наконец !! Большое спасибо.
Джаватар
10

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

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

    ListView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);

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

    <activity android:windowSoftInputMode="adjustPan">

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

рави ранджан
источник
7

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

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();
        }
      }
    });
  }
}
rjrjr
источник
Слегка измененное, это решение работает у меня через 2 дня @ # ( : if (sticky! = Null) {sticky.RequestFocus (); sticky.RequestFocusFromTouch (); sticky = null;}
Крис ван де Стиг
4

этот пост точно соответствует моим ключевым словам. У меня есть заголовок 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();
        }
    });

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

Рафаэль Санчес
источник
2

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

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

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

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

Петерис Цауне
источник
1

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


источник
0

Другое простое решение - определить ваш 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;

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

Вебер Антуан
источник
1
Вопрос касается фокусировки, а не кликабельности.
Джо
0

Самая важная часть - заставить фокус работать для ячейки списка. Особенно для списка на 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.

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

Майкл Бирманн
источник
0

Я только что нашел другое решение. Я считаю, что это скорее взлом, чем решение, но он работает на 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();
            }
     });
alaeri
источник
что здесь в view.requestFocus?
Нарендра Сингх
это старый ответ, но он относился к представлению в области видимости, данной как аргумент для OnFocusChangeListener, я считаю
alaeri
1
Это вызывает цикл смены фокуса.
Morteza Rastgoo,
0

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

android:windowSoftInputMode="adjustNothing"

в

деятельность

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

Tor_Gash
источник