it-swarm.com.ru

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

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

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

расположение:

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

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

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

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

Любой берущий?

120
Joe

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

101
Joe

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

<activity Android:name= ".yourActivity" Android:windowSoftInputMode="adjustPan"/>
95
logan

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

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

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

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

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

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

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

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

17
AndroidGecko

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

  1. установить эту строку

    ListView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);

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

    <activity Android:windowSoftInputMode="adjustPan">

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

9
ravi ranjan

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

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

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

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

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

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

4
Rafael Sanches

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

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

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

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

2
Pēteris Caune

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

1
user4715375

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

начните свой веб-просмотр как обычно и добавьте это: (спасибо Michael Bierman)

listView.setItemsCanFocus(true);

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

editText.setOnFocusChangeListener(
    new OnFocusChangeListener(View view,boolean hasFocus){
        view.post(new Runnable() {
            @Override
            public void run() {
                view.requestFocus();
                view.requestFocusFromTouch();
            }
     });
0
alaeri

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

Android:windowSoftInputMode="adjustNothing"

в

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

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

0
Tor_Gash

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

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

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

0
Weber Antoine