Focusable EditText inside ListView
Asked Answered
B

13

124

I've spent about 6 hours on this so far, and been hitting nothing but roadblocks. The general premise is that there is some row in a ListView (whether it's generated by the adapter, or added as a header view) that contains an EditText widget and a Button. All I want to do is be able to use the jogball/arrows, to navigate the selector to individual items like normal, but when I get to a particular row -- even if I have to explicitly identify the row -- that has a focusable child, I want that child to take focus instead of indicating the position with the selector.

I've tried many possibilities, and have so far had no luck.

layout:

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

Header view:

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

Assuming there are other items in the adapter, using the arrow keys will move the selection up/down in the list, as expected; but when getting to the header row, it is also displayed with the selector, and no way to focus into the EditText using the jogball. Note: tapping on the EditText will focus it at that point, however that relies on a touchscreen, which should not be a requirement.

ListView apparently has two modes in this regard:
1. setItemsCanFocus(true): selector is never displayed, but the EditText can get focus when using the arrows. Focus search algorithm is hard to predict, and no visual feedback (on any rows: having focusable children or not) on which item is selected, both of which can give the user an unexpected experience.
2. setItemsCanFocus(false): selector is always drawn in non-touch-mode, and EditText can never get focus -- even if you tap on it.

To make matters worse, calling editTextView.requestFocus() returns true, but in fact does not give the EditText focus.

What I'm envisioning is basically a hybrid of 1 & 2, where rather than the list setting if all items are focusable or not, I want to set focusability for a single item in the list, so that the selector seamlessly transitions from selecting the entire row for non-focusable items, and traversing the focus tree for items that contain focusable children.

Any takers?

Boylan answered 21/4, 2010 at 2:37 Comment(0)
B
101

Sorry, answered my own question. It may not be the most correct or most elegant solution, but it works for me, and gives a pretty solid user experience. I looked into the code for ListView to see why the two behaviors are so different, and came across this from ListView.java:

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

So, when calling setItemsCanFocus(false), it's also setting descendant focusability such that no child can get focus. This explains why I couldn't just toggle mItemsCanFocus in the ListView's OnItemSelectedListener -- because the ListView was then blocking focus to all children.

What I have now:

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

I use beforeDescendants because the selector will only be drawn when the ListView itself (not a child) has focus, so the default behavior needs to be that the ListView takes focus first and draws selectors.

Then in the OnItemSelectedListener, since I know which header view I want to override the selector (would take more work to dynamically determine if any given position contains a focusable view), I can change descendant focusability, and set focus on the EditText. And when I navigate out of that header, change it back it again.

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

Note the commented-out setItemsCanFocus calls. With those calls, I got the correct behavior, but setItemsCanFocus(false) caused focus to jump from the EditText, to another widget outside of the ListView, back to the ListView and displayed the selector on the next selected item, and that jumping focus was distracting. Removing the ItemsCanFocus change, and just toggling descendant focusability got me the desired behavior. All items draw the selector as normal, but when getting to the row with the EditText, it focused on the text field instead. Then when continuing out of that EditText, it started drawing the selector again.

Boylan answered 21/4, 2010 at 3:17 Comment(13)
very cool, didn't tested yet. Have you tested on 1.5, 1.6 and 3.0?Tardy
Rafael Sanches: I haven't touched the project since 2.1, but at that time, it was confirmed working in 1.5, 1.6, and 2.1. I make no guarantee that it still works in 2.2 or later.Boylan
only needed android:descendantFocusability="afterDescendants" - anyway +1Postern
@kellogs: yeah, descendantFocusability="afterDescendants" will allow your EditText to take focus inside the ListView, but then you get no list item selector while navigating with a dpad. My task was to have the list item selector on all rows except the one with the EditText. Glad it helped though. FWIW, we ended up reevaluating this implementation and decided that a focusable inside a ListView is just not idiomatic-Android UI design, so we scrapped the idea in favor of a more-Android-friendly approach.Boylan
Thanks man, looks like you can only answer me, Where do you initialize myEditText ?Reveille
Anyway I manage it, Hope my example can help others..Reveille
@Vikas: The "myEditText" was just an arbitrary example. You would normally initialize it in onCreate() of the activity, or whenever the layout is inflated (or when you instantiate it, if creating the field programmatically).Boylan
Cool. I needed to add some hooks to TextEdit click events to finish correct handling, but otherwise this was working good.Spleeny
+1 to @Postern comment. I also needed to add the windowSoftInoutMode below to get the EditText fields focusing correctly.Modie
Has this been tested on Ice Cream Sandwich? I cannot get it working. Thanks.Macmillan
@Boylan what did you end up using?Meli
@Hades, nothing.. we ended up redesigning the app to do things in a more Android way.Boylan
@Joe: onItemSelected method is defined in activity right, so myEditText should be inflated for every item in listview, So how can we get myEditText using findViewById or any other method inside the onItemSelected method ?Ori
S
103

This helped me.
In your manifest :

<activity android:name= ".yourActivity" android:windowSoftInputMode="adjustPan"/>
Slogan answered 4/2, 2011 at 18:39 Comment(6)
I'm not sure I see the relevance. Setting windowSoftInputMode just changes the way the IME adjusts the rest of the window contents when it is open. It doesn't allow you to selectively change the focus type of the ListView. Can you explain a bit more, how this relates to the initial use case?Boylan
@Boylan When the IME is opened, the cursor - and probably the focus, too - just jumps around in my screen, making it impossible to input text. Your OnItemSelectedListener doesn't change that. However Iogan's simple solution works like a charm, thanks!Endless
@Gubbel: Indeed, it wouldn't change that at all, because the original question was about something entirely different :) Glad logan's fix works for what you were looking for, but it simply is not even remotely related to the question.Boylan
Using this plus Joe's android:descendantFocusability property got my EditTexts inside of a ListView resolving the keyboard properly, upvoted both. android:descendantFocusability by itself didn't do the trick and I wasn't remotely enthusiastic about @Overriding onItemSelected for all 14 EditTexts I have to deal with. :) Thanks!Diver
This worked for me, but notice if you are using TabHost or TabActivity, you have to set android:windowSoftInputMode=”adjustPan” for the TabActivity definition in the manifest.Texas
anyone use it, got problem with scroll ? cz i got problem,,Indiscretion
B
101

Sorry, answered my own question. It may not be the most correct or most elegant solution, but it works for me, and gives a pretty solid user experience. I looked into the code for ListView to see why the two behaviors are so different, and came across this from ListView.java:

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

So, when calling setItemsCanFocus(false), it's also setting descendant focusability such that no child can get focus. This explains why I couldn't just toggle mItemsCanFocus in the ListView's OnItemSelectedListener -- because the ListView was then blocking focus to all children.

What I have now:

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

I use beforeDescendants because the selector will only be drawn when the ListView itself (not a child) has focus, so the default behavior needs to be that the ListView takes focus first and draws selectors.

Then in the OnItemSelectedListener, since I know which header view I want to override the selector (would take more work to dynamically determine if any given position contains a focusable view), I can change descendant focusability, and set focus on the EditText. And when I navigate out of that header, change it back it again.

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

Note the commented-out setItemsCanFocus calls. With those calls, I got the correct behavior, but setItemsCanFocus(false) caused focus to jump from the EditText, to another widget outside of the ListView, back to the ListView and displayed the selector on the next selected item, and that jumping focus was distracting. Removing the ItemsCanFocus change, and just toggling descendant focusability got me the desired behavior. All items draw the selector as normal, but when getting to the row with the EditText, it focused on the text field instead. Then when continuing out of that EditText, it started drawing the selector again.

Boylan answered 21/4, 2010 at 3:17 Comment(13)
very cool, didn't tested yet. Have you tested on 1.5, 1.6 and 3.0?Tardy
Rafael Sanches: I haven't touched the project since 2.1, but at that time, it was confirmed working in 1.5, 1.6, and 2.1. I make no guarantee that it still works in 2.2 or later.Boylan
only needed android:descendantFocusability="afterDescendants" - anyway +1Postern
@kellogs: yeah, descendantFocusability="afterDescendants" will allow your EditText to take focus inside the ListView, but then you get no list item selector while navigating with a dpad. My task was to have the list item selector on all rows except the one with the EditText. Glad it helped though. FWIW, we ended up reevaluating this implementation and decided that a focusable inside a ListView is just not idiomatic-Android UI design, so we scrapped the idea in favor of a more-Android-friendly approach.Boylan
Thanks man, looks like you can only answer me, Where do you initialize myEditText ?Reveille
Anyway I manage it, Hope my example can help others..Reveille
@Vikas: The "myEditText" was just an arbitrary example. You would normally initialize it in onCreate() of the activity, or whenever the layout is inflated (or when you instantiate it, if creating the field programmatically).Boylan
Cool. I needed to add some hooks to TextEdit click events to finish correct handling, but otherwise this was working good.Spleeny
+1 to @Postern comment. I also needed to add the windowSoftInoutMode below to get the EditText fields focusing correctly.Modie
Has this been tested on Ice Cream Sandwich? I cannot get it working. Thanks.Macmillan
@Boylan what did you end up using?Meli
@Hades, nothing.. we ended up redesigning the app to do things in a more Android way.Boylan
@Joe: onItemSelected method is defined in activity right, so myEditText should be inflated for every item in listview, So how can we get myEditText using findViewById or any other method inside the onItemSelected method ?Ori
B
18

My task was to implement ListView which expands when clicked. The additional space shows EditText where you can input some text. App should be functional on 2.2+ (up to 4.2.2 at time of writing this)

I tried numerous solutions from this post and others I could find; tested them on 2.2 up to 4.2.2 devices. None of solutions was satisfactionary on all devices 2.2+, each solution presented with different problems.

I wanted to share my final solution :

  1. set listview to android:descendantFocusability="afterDescendants"
  2. set listview to setItemsCanFocus(true);
  3. set your activity to android:windowSoftInputMode="adjustResize" Many people suggest adjustPan but adjustResize gives much better ux imho, just test this in your case. With adjustPan you will get bottom listitems obscured for instance. Docs suggest that ("This is generally less desirable than resizing"). Also on 4.0.4 after user starts typing on soft keyboard the screen pans to the top.
  4. on 4.2.2 with adjustResize there are some problems with EditText focus. The solution is to apply rjrjr solution from this thread. It looks scarry but it is not. And it works. Just try it.

Additional 5. Due to adapter being refreshed (because of view resize) when EditText gains focus on pre HoneyComb versions I found an issue with reversed views: getting View for ListView item / reverse order on 2.2; works on 4.0.3

If you are doing some animations you might want to change behaviour to adjustPan for pre-honeycomb versions so that resize doesnt fire and adapter doesn't refresh the views. You just need to add something like this

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

All this gives acceptable ux on 2.2 - 4.2.2 devices. Hope it will save people some time as it took me at least several hours to come to this conclusion.

Bootlick answered 4/4, 2013 at 18:42 Comment(2)
just first and second step enough in my caseTraci
Works perfectly with my xElement.setOnClickListener(..) in ArrayAdapter and a ListView.setOnItemClickListener(...) - finally!! Big thx.Gravitt
D
10

This saved my life--->

  1. set this line

    ListView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);

  2. Then in your manifest in activity tag type this-->

    <activity android:windowSoftInputMode="adjustPan">

Your usual intent

Dilettantism answered 18/10, 2015 at 16:45 Comment(0)
F
7

We're trying this on a short list that does not do any view recycling. So far so good.

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();
        }
      }
    });
  }
}
Forty answered 25/3, 2013 at 17:29 Comment(1)
Slightly modified, this solution works for me after 2 days of @#( : if (sticky != null) { sticky.RequestFocus(); sticky.RequestFocusFromTouch(); sticky = null; }Skerrick
T
4

this post was matching exactly my keywords. I have a ListView header with a search EditText and a search Button.

In order to give focus to the EditText after loosing the initial focus the only HACK that i found is:

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

Lost lots of hours and it's not a real fix. Hope it helps someone tough.

Tardy answered 21/10, 2010 at 9:13 Comment(0)
G
2

If the list is dynamic and contains focusable widgets, then the right option is to use RecyclerView instead of ListView IMO.

The workarounds that set adjustPan, FOCUS_AFTER_DESCENDANTS, or manually remember focused position, are indeed just workarounds. They have corner cases (scrolling + soft keyboard issues, caret changing position in EditText). They don't change the fact that ListView creates/destroys views en masse during notifyDataSetChanged.

With RecyclerView, you notify about individual inserts, updates, and deletes. The focused view is not being recreated so no issues with form controls losing focus. As an added bonus, RecyclerView animates the list item insertions and removals.

Here's an example from official docs on how to get started with RecyclerView: Developer guide - Create a List with RecyclerView

Graupel answered 3/2, 2016 at 14:9 Comment(0)
S
1

The most important part is to get the focus working for the list cell. Especially for list on Google TV this is essential:

setItemsCanFocus method of the list view does the trick:

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

My list cell xml starts like follows:

<?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 are also important for D-Pad navigation.

For more details check out the great other answers.

Swett answered 4/12, 2012 at 20:11 Comment(0)
H
1

some times when you use android:windowSoftInputMode="stateAlwaysHidden"in manifest activity or xml, that time it will lose keyboard focus. So first check for that property in your xml and manifest,if it is there just remove it. After add these option to manifest file in side activity android:windowSoftInputMode="adjustPan"and add this property to listview in xml android:descendantFocusability="beforeDescendants"

Harl answered 25/11, 2015 at 5:57 Comment(0)
C
0

Another simple solution is to define your onClickListener, in the getView(..) method, of your 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;

That way your row are clickable, and your inner view too :)

Coverdale answered 4/10, 2011 at 9:15 Comment(1)
The question relates to focus, not clickability.Boylan
C
0

I just found another solution. I believe it's more a hack than a solution but it works on android 2.3.7 and android 4.3 (I've even tested that good old D-pad)

init your webview as usual and add this: (thanks Michael Bierman)

listView.setItemsCanFocus(true);

During the getView call:

editText.setOnFocusChangeListener(
    new OnFocusChangeListener(View view,boolean hasFocus){
        view.post(new Runnable() {
            @Override
            public void run() {
                view.requestFocus();
                view.requestFocusFromTouch();
            }
     });
Colony answered 22/11, 2013 at 17:8 Comment(3)
what is view here in view.requestFocus?Veloz
this is an old answer but it referred to the view in the scope given as an argument to the OnFocusChangeListener I believeColony
It causes a loop in changing focus.Training
A
0

Just try this

android:windowSoftInputMode="adjustNothing"

in the

activity

section of your manifest. Yes, it adjusts nothings, which means the editText will stay where it is when IME is opening. But that's just an little inconvenience that still completely solves the problem of losing focus.

Analemma answered 20/2, 2016 at 14:46 Comment(0)
E
0

In my case, there is 14 input edit text in the list view. The problem I was facing, when the keyboard open, edit text focus lost, scroll the layout, and as soon as focused view not visible to the user keyboard down. It was not good for the user experience. I can't use windowSoftInputMethod="adjustPan". So after so much searching, I found a link that inflates custom layout and sets data on view as an adapter by using LinearLayout and scrollView and work well for my case.

Epideictic answered 12/10, 2020 at 10:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.