Фокусируемое EditText всередині ListView


121

Я витратив на це близько 6 годин, і не зачепив нічого, крім дорожніх блоків. Загальна передумова полягає в тому, що в рядку ListView(який він генерується адаптером або доданий у вигляді перегляду заголовка) є якийсь рядок, який містить EditTextвіджет та a 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використанні джогбола. Примітка: натискання на EditText волю зосередить увагу саме на цьому місці, однак це покладається на сенсорний екран, що не повинно бути вимогою.

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

Що ще гірше, виклик editTextView.requestFocus()повертає правдою, але насправді не дає уваги EditText.

Що я маю на увазі, це в основному гібрид 1 і 2, де замість налаштування списку, якщо всі елементи фокусуються чи ні, я хочу встановити фокусируемость для одного елемента в списку, щоб селектор плавно переходив від вибору цілий рядок для нефокусованих елементів та обхід дерева фокусу для елементів, які містять дітей, що фокусуються.

Хто-небудь, хто приймає?

Відповіді:


101

Вибачте, відповів на власне запитання. Це може бути не найбільш правильним чи найелегантнішим рішенням, але це працює для мене і дає досить солідний досвід користувача. Я переглянув код для ListView, щоб побачити, чому обидві форми поведінки настільки різні, і натрапив на це з ListView.java:

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

Отже, під час дзвінка setItemsCanFocus(false)це також встановлює фокусируемость нащадків таким чином, що жодна дитина не може отримати фокус. Це пояснює, чому я не міг просто переключити mItemsCanFocusOnItemSelectedListener 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 і відобразити селектор на наступному вибраному елементі, і цей фокус стрибки відволікав. Видалення змін ChangeCanFocus і просто зміна фокусуваності нащадків отримали мені бажану поведінку. Усі елементи малюють селектор як звичайний, але, переходячи до рядка за допомогою EditText, він натомість зосереджується на текстовому полі. Потім, продовжуючи працювати з цим EditText, він знову почав малювати селектор.


дуже круто, ще не тестували. Ви протестували на 1.5, 1.6 та 3.0?
Рафаель Санчес

Рафаель Санчес: Я проект не торкався з 2.1, але в той час він був підтверджений, що працює в 1.5, 1.6 і 2.1. Я не даю жодних гарантій, що він все ще працює в версії 2.2 або пізнішої версії.
Джо

13
потрібен лише андроїд: descendantFocusability = "afterDescendants" - все одно +1
kellogs

5
@kellogs: так, descendantFocusability="afterDescendants"дозволить редактору EditText зосередитись усередині ListView, але тоді ви не отримаєте селектора елементів списку під час навігації за допомогою dpad. Моє завдання полягало в тому, щоб мати перемикач елементів списку на всіх рядках, окрім одного з EditText. Радий, що це допомогло. FWIW, ми в кінцевому підсумку переоцінили цю реалізацію і вирішили, що фокусируемость у ListView - це просто не ідіоматичний дизайн інтерфейсу Android.
Джо

5
Чи перевірено це на бутерброді з морозивом? Я не можу працювати. Дякую.
Раджат Анантарам

99

Це мені допомогло.
У вашому маніфесті:

<activity android:name= ".yourActivity" android:windowSoftInputMode="adjustPan"/>

3
Я не впевнений, що бачу відповідність. Встановлення windowSoftInputMode просто змінює спосіб IME регулює решту вмісту вікна, коли воно відкрите. Це не дозволяє вибірково змінювати тип фокусу ListView. Чи можете ви пояснити трохи більше, як це стосується випадку початкового використання?
Джо

1
@Joe Коли IME відкрито, курсор - і, мабуть, також фокус - просто стрибає на моєму екрані, унеможливлюючи введення тексту. Ви OnItemSelectedListenerцього не змінюєте. Однак просте рішення Йогана працює як шарм, дякую!
Губбель

2
@Gubbel: Дійсно, це взагалі не змінило б, тому що початкове питання стосувалося чогось зовсім іншого :) Виправлення раді Логана працює на те, що ви шукали, але це просто навіть далеко не пов'язане з питанням.
Джо

8
Використовуючи це плюс android:descendantFocusabilityвластивість Джо, я отримав EditTextвідповідне ListViewрішення про клавіатуру, а також обидва. android:descendantFocusabilityсама по собі не зробила фокусу, і я не був віддалений ентузіазмом @Overriding onItemSelectedпротягом усіх 14 EditTextс, з якими мені доводиться мати справу. :) Дякую!
Thomson Comer

Це працювало для мене, але зауважте, якщо ви використовуєте TabHost або TabActivity, вам слід встановити android: windowSoftInputMode = "prilagodPan" для визначення TabActivity в маніфесті.
kiduxa

18

Моє завдання полягало в тому, щоб реалізувати, 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дає набагато краще ux imho, просто протестуйте це у вашому випадку. З adjustPanвами ви отримаєте нижній список, наприклад, затемнений. Документи припускають, що ("Це, як правило, менш бажано, ніж змінювати розмір"). Також на 4.0.4 після того, як користувач починає набирати на м’якій клавіатурі, екран простежує вгорі.
  4. 4.2.2 з adjustResizeдеякими проблемами з фокусом EditText. Рішення полягає в застосуванні розчину rjrjr з цієї нитки. Це виглядає страшно, але це не так. І це працює. Просто спробуйте.

Додатково 5. Через оновлення адаптера (через розмір перегляду), коли він EditTextзосереджується на попередніх версіях HoneyComb, я виявив проблему з перевернутими видами: отримання View for 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. Сподіваюся, це заощадить людей деякий час, оскільки мені знадобилося принаймні кілька годин, щоб прийти до цього висновку.


1
в моєму випадку достатньо першого та другого кроку
Калпеш Лахані

Відмінно працює з моїм xElement.setOnClickListener (..) в ArrayAdapter та ListView.setOnItemClickListener (...) - нарешті !! Великий THX.
Джаватар

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>

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

Трохи модифікований, це рішення працює для мене через 2 дні @ # ( : if (липкий! = Null) {sticky.RequestFocus (); sticky.RequestFocusFromTouch (); sticky = null;}
Кріс ван де Стіг

4

ця публікація відповідала саме моїм ключовим словам. У мене є заголовок 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();
        }
    });

Втрачено багато годин, і це не справжнє виправлення. Сподіваюся, це допомагає комусь важко.


2

Якщо список динамічний і містить фокусуються віджети, то правильним варіантом є використання RecyclerView замість IMO ListView.

Обхідні шляхи, які встановлюють 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();
            }
     });

що тут видно у view.requestFocus?
Нарендра Сінгх

це стара відповідь, але вона посилається на погляд у межах, наведений як аргумент OnFocusChangeListener Я вважаю
alaeri

1
Це викликає цикл у зміні фокусу.
Morteza Rastgoo

0

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

android:windowSoftInputMode="adjustNothing"

в

діяльності

розділ вашого маніфесту. Так, він коригує nothings, що означає, що editText залишиться там, де він знаходиться, коли IME відкриється. Але це лише невелика незручність, яка все ще повністю вирішує проблему втрати уваги.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.