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 helped me.
In your manifest :
<activity android:name= ".yourActivity" android:windowSoftInputMode="adjustPan"/>
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.
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 :
set listview to android:descendantFocusability="afterDescendants"
set listview to setItemsCanFocus(true);
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.
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.
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
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();
}
}
});
}
}
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.
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
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"
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 :)
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.
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();
}
});
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.
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.
Before Scrolling:
During Scrolling:
What I expect during scrolling:
I have a problem with HorizontalScrollView. When I choose an element from this View, I set focus to that element by calling:
else if (v.getParent() == candidatesScrollView.getChildAt(0))
{
Button candidateButton = (Button) v;
v.requestFocusFromTouch();
v.setSelected(true);
(...)
}
After that, when I scroll the list without choosing other element, I lose focus of previously selected element. I made some research about this topic, but there was no solution that could work for me... How can I scroll my HorizontalScrollList without loosing focus from selected element? Any Help is Appreciated. It has been about 14 days since I asked that question and still didn't find solution. Please help.
Here is part of my XML:
<HorizontalScrollView
android:id="#+id/CandidatesHorizontalScrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="#+id/linearLayout2"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:visibility="gone" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:orientation="horizontal" >
<Button
android:id="#+id/horizontalscrollview1_button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
android:textSize="25sp" />
(...)
// 11 more buttons
</LinearLayout>
</HorizontalScrollView>
UPDATE
MY CURRENT SOLUTION #1 (not working correctly):
After scrolling, and then scrolling again (for example scrolling back), scrolling starts from selected element.
I created custom HorizontalScrollView class inside which I overridden onTouchEvent() method. I don't think this is optimal way of doing that, because in that case I have to do calculations every time I move even one pixel. for example, if I add toast.show() to the below method, it will try to show as many toast as many I moved pixels (If I move by 10 pixels, it will try to show 10 Toast). Anyway, it works for me and the selection and focus are being kept. Please help me modify this code to make finally a good answer for that known issue:
#Override
public boolean onTouchEvent(MotionEvent ev)
{
int i = 0;
Button button = null;
for (; i < 11; i++)
{
button = (Button)((LinearLayout)getChildAt(0)).getChildAt(i);
if(button.isSelected())
break;
}
super.onTouchEvent(ev);
button.setSelected(true);
button.requestFocusFromTouch();
return true;
}
To be sure that the above code will work, you need to have only one selected item in your HorizontalScrollView at a time, i.e when you press diferent button, you need to make the previous one setSelected(false)
MY CURRENT SOLUTION #2 (not working correctly):
Solution #2 that I tried to implement, thinking that first one is not elegant enough, involves usage of gesture detector. In my custom HorizontalListView class I have added the following code:
Constructor:
public MyHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
gestureDetector = new GestureDetector(context, new MyHorizontalScrollViewGestureDetector());
this.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
gestureDetector.onTouchEvent(event);
return true;
}
});
// TODO Auto-generated constructor stub
}
MyHorizontalScrollViewGestureDetector internal class:
public class MyHorizontalScrollViewGestureDetector extends SimpleOnGestureListener
{
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
{
//Here code similar like that one in solution #1
//But the View is not scrolling, even without that code
super.onScroll(e1, e2, distanceX, distanceY);
return true;
}
}
However, the list is not scrolling with that solution. I can add to onScroll method:
ScrollBy((int)positionX, (int)positionY);
which makes the list will scroll, but not in a good way ad it will freeze sometimes.
I am wondering why scrolling is not called by the super. method.
MY CURRENT SOLUTION #3 (working, but it is walk-around):
Because both solution 1 and 2 were not working, I decided to not play with focus anymore.
What I do now, is to change the Button Drawable whenever I click it and every time when I change to different Button. I use same Drawable as is used for focused button (Holo). In that case, I don't have to be worried about scrolling in HorizontalScrollView. This solution is some kind of walk-around, so I am looking forward to any comments, suggestions and edits.
Can the following solution be applicable (I took the idea from here):
You may want to try creating your own custom class that extends HorizontalScrollView and overriding the onScrollChanged() function as such:
public class TestHorizontalScrollView extends HorizontalScrollView {
public TestHorizontalScrollView(Context context) {
super(context);
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
// TODO Auto-generated method stub
Log.i("Scrolling", "X from ["+oldl+"] to ["+l+"]");
Button button = null;
for (; i < 11; i++)
{
button = (Button)((LinearLayout)getChildAt(0)).getChildAt(i);
if(button.isSelected())
break;
}
button.setSelected(true);
button.requestFocusFromTouch();
super.onScrollChanged(l, t, oldl, oldt);
}
}
Now that you have detected the scroll (we're speaking about one of the HorizontalScrollView) you can set again the selected status of the corresponding button. Can you please try this solution out and see if it works. I'm very interested in the resolution of this one, as the question is quite interesting also.
Anyway, I managed to find the following article. They state there that:
Imagine a simple application, ApiDemos for example, that shows a list
of text items. The user can freely navigate through the list using the
trackball and they can also scroll and fling the list using their
finger. The issue in this scenario is the selection. If I select an
item at the top of the list and then fling the list towards the
bottom, what should happen to the selection? Should it remain on the
item and scroll off the screen? In this case, what would happen if I
then decide to move the selection with the trackball? Or worse, if I
press the trackball to act upon the currently selected item, which is
not shown on screen anymore. After careful considerations, we decided
to remove the selection altogether.
In touch mode, there is no focus and no selection. Any selected item
in a list of in a grid becomes unselected as soon as the user enters
touch mode. Similarly, any focused widgets become unfocused when the
user enters touch mode. The image below illustrates what happens when
the user touches a list after selecting an item with the trackball.
Anyway, the statements there didn't make me happy and I went further to find this:
android:focusable="true"
android:focusableInTouchMode="true"
Also set android:clickable="true" (you might as well set android:focusable="true" of the LinearLayout).
Try adding those to the buttons and to the Layout that contains them.
Try everything.
I'l try backing you up as much as I can.
Cheers
I remember having a problem with scroll views that I think was similar to the problem you're having.
The solution I came up with was to override the onRequestFocusInDescendants method in the HorizontalScrollView class. This requires you creating your own class extended from HorizontalScrollView if you aren't already doing so.
In my case, I always returned true from the method. This tells the caller that you have taken care of the focus change so it shouldn't try to do anything further.
#Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
return true;
}
Depending on your requirements, you may find it necessary to return true only under certain conditions (say when a scroll is in progress), otherwise forward the call to the superclass.
This question has no answer except for custom way of doing it, as you might have already done like changing the drawables. Reason is when you slide, the focus goes to the HorizontalScroll, and you can only focus on one item, it makes no sense to focus more than one view. So either implement a drawable (which you seem to have done) or extend a checkbox and override the functionality so when it is focused it is checked and changes the looks.
I have found a solution to my problem. You can see the updated question for details. Basically, instead of using focus I decided to use Drawable selector, which is much more easier.
I have an ImageView in my layout for the individual items of a list.
The ImageView's src is an XML file in the drawable folder that defines which images to use during the various states of pressing an item.
However, I've noticed when you click the list row (and not the ImageView itself) the selector assigned to the ImageView is activated. It doesn't actually hit the ImageView's onClick code, but the image toggles as if it has been clicked.
This is actually a desirable effect in some cases, but in this specific case it is not. Is there a way I can stop this from happening?
set android:duplicateParentState in child to true
I think you should read Cyril Mottier's blog. He has a post about this here.
In a word, you have to extends your Child views to Override the method setPressed(boolean) like this:
#Override
public void setPressed(boolean pressed) {
if (pressed && getParent() instanceof View && ((View) getParent()).isPressed()) {
return;
}
super.setPressed(pressed);
}
I'm using a custom view expanded from a XML layout in a horizontal scroll view as a sort of horizontal image list but I'm not sure how to get them to appear clickable/tappable (ie they highlight when tapped) or how to capture these events. I think I've tried setOnClickHandler without it working. I'm also trying to get a simple TextView to do the same. I've also tried setting android:clickable="true" but that hasn't helped either. Any ideas?
To take care of the visual feedback use an xml Selector, and set it as the View's background.
To handle click events use
mView.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
//your code here
}
});