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?
This saved my life--->
set this line
ListView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
Then in your manifest in activity tag type this-->
<activity android:windowSoftInputMode="adjustPan">
Your usual intent
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 activityandroid:windowSoftInputMode="adjustPan"
and add this property to listview in xmlandroid:descendantFocusability="beforeDescendants"
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:
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 togglemItemsCanFocus
in the ListView's OnItemSelectedListener -- because the ListView was then blocking focus to all children.What I have now:
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.
Note the commented-out
setItemsCanFocus
calls. With those calls, I got the correct behavior, butsetItemsCanFocus(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.My task was to implement
ListView
which expands when clicked. The additional space showsEditText
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 :
android:descendantFocusability="afterDescendants"
setItemsCanFocus(true);
android:windowSoftInputMode="adjustResize"
Many people suggestadjustPan
butadjustResize
gives much better ux imho, just test this in your case. WithadjustPan
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.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.3If 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 thisAll 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.
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 duringnotifyDataSetChanged
.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 RecyclerViewAnother simple solution is to define your onClickListener, in the getView(..) method, of your ListAdapter.
That way your row are clickable, and your inner view too :)