可聚焦的EditText在ListView中

124

我已经花了大约6个小时的时间,但一直遇到问题。总体思路是,在 ListView 中有一行(无论是适配器生成的还是作为标题视图添加的),其中包含一个EditText小部件和一个Button。我想做的就是能够像平常一样使用jogball /方向键来导航选择器到各个项目,但当我到达一个特定的行--即使我必须明确标识该行--具有可聚焦子项时,我希望该子项将获得焦点而不是使用选择器指示位置。

我尝试了许多可能性,但到目前为止都没有成功。

布局:

<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()将返回true,但实际上并不会给EditText聚焦。

我所设想的基本上是1和2的混合体,即不是列表设置是否所有项目都可聚焦,而是要为列表中的单个项目设置可聚焦性,以便选择器在非可聚焦项中从选择整行过渡到遍历具有可聚焦子项的项目的聚焦树。

有人愿意接受这个挑战吗?

13个回答

103

这对我很有帮助。
在你的清单文件中:

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

3
我不确定我看到相关性。设置windowSoftInputMode只是改变IME在打开时调整其他窗口内容的方式。它不允许你有选择性地改变ListView的焦点类型。你能再解释一下,这与最初的使用案例有什么关系吗? - Joe
1
@Joe 当输入法打开时,光标 - 以及可能的焦点 - 在我的屏幕上跳来跳去,导致无法输入文本。你的 OnItemSelectedListener 并没有改变这一点。然而,Iogan 的简单解决方案非常有效,谢谢! - Gubbel
2
@Gubbel:确实,这不会改变任何东西,因为原问题完全是关于另一件事情的 :) 很高兴logan的修复对你所寻找的有用,但它根本与问题毫不相关。 - Joe
8
使用这个加上Joe的android:descendantFocusability属性,使我在ListView中的EditText可以正确地解决键盘问题,并且我已经点赞了两个。android:descendantFocusability本身无法解决问题,而我对于必须处理的14个EditText都不热衷于重写onItemSelected。 :) 谢谢! - Thomson Comer
这对我有用,但请注意,如果您正在使用TabHost或TabActivity,则必须在清单中为TabActivity定义设置android:windowSoftInputMode =“adjustPan”。 - kiduxa
有人使用它,滚动时遇到问题吗?因为我遇到了问题。 - Budi Mulyo

101

抱歉,我自己找到了答案。这可能不是最正确或最优雅的解决方案,但它对我很有效,并且提供了相当稳定的用户体验。我查看了ListView的代码,以了解为什么这两种行为如此不同,并在ListView.java中找到了以下内容:

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

因此,当调用setItemsCanFocus(false)时,它还会设置后代焦点能力,以使任何子节点都无法获得焦点。这就解释了为什么我不能在ListView的OnItemSelectedListener中切换mItemsCanFocus -- 因为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继续时,它开始重新绘制选择器。

13
android:descendantFocusability="afterDescendants" - 无论如何 +1 - kellogs
5
@kellogs:是的,descendantFocusability="afterDescendants"会允许您在ListView内部使EditText获得焦点,但是在使用dpad导航时,您将无法获得任何列表项选择器。我的任务是在除EditText外的所有行上都有列表项选择器。很高兴它有所帮助。顺便说一下,我们最终重新评估了这种实现方法,并决定将一个可聚焦的视图放在ListView中并不符合Android UI设计模式,因此我们放弃了这个想法,采用了更加Android友好的方法。 - Joe
5
这个在冰激凌三明治版本上测试过吗?我无法让它工作。谢谢。 - Rajat Anantharam
@Joe 你最后用的是什么? - Hades
@Hades,没什么了...我们最终重新设计了应用程序,以更符合Android的方式进行操作。 - Joe
显示剩余8条评论

18
我的任务是实现 ListView,当点击时它会扩展,额外的空间会显示 EditText,你可以在其中输入一些文本。应用程序应该在2.2+上使用(在撰写本文时最高支持4.2.2)。
我尝试了许多来自此帖子和其他帖子中的解决方案;测试它们在2.2到4.2.2设备上,但没有一个解决方案能同时满足所有2.2+设备的要求,每个解决方案都有不同的问题。
我想分享我的最终解决方案:
  1. 将listview设置为 android:descendantFocusability="afterDescendants"
  2. 将listview设置为 setItemsCanFocus(true);
  3. 将您的activity设置为 android:windowSoftInputMode="adjustResize" 许多人建议使用 adjustPan,但是在我的观点中,adjustResize 的用户体验要好得多,只需在您的情况下进行测试即可。使用 adjustPan 时,例如底部列表项将被遮挡。文档建议使用 ("This is generally less desirable than resizing")。另外,在4.0.4上,用户开始在软键盘上键入后,屏幕会移动到顶部。
  4. 在4.2.2上,由于EditText获取焦点而导致的view调整刷新了适配器。解决方法是应用来自此线程的rjrjr解决方案。它看起来很可怕,但实际上并不是。而且它有效。只需尝试一下即可。
附加项5: 由于适配器被刷新(因为视图大小调整),在HoneyComb之前的版本中,当 EditText 获得焦点时,我发现了一个与视图相反的问题: getting View for ListView item / reverse order on 2.2; works on 4.0.3 如果您正在执行一些动画,您可能希望将行为更改为针对Honeycomb之前的版本的 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设备上都有可接受的用户体验。 希望这能为人们节省一些时间,因为我至少花了几个小时才得出这个结论。


1
只需要第一步和第二步就足够了。 - Kalpesh Lakhani
在ArrayAdapter和ListView.setOnItemClickListener(...)中,我的xElement.setOnClickListener(..)完美运行 - 终于!!非常感谢。 - Javatar

10

这救了我的命--->

  1. 加入以下代码:

    ListView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);

  2. 然后在你的manifest文件中,在activity标签中添加如下代码:

    <activity android:windowSoftInputMode="adjustPan">

你平常的intent


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

稍作修改后,这个解决方案在我经过两天的*@#(*之后终于奏效了: if (sticky != null) { sticky.RequestFocus(); sticky.RequestFocusFromTouch(); sticky = null; } - Chris van de Steeg

4

这篇文章与我的关键字完全匹配。我有一个带有搜索EditText和搜索按钮的ListView标题。

为了在失去初始焦点后将焦点放在EditText上,我找到的唯一“黑科技”是:

    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而不是ListView。

解决方案,如设置adjustPanFOCUS_AFTER_DESCENDANTS或手动记住焦点位置,实际上只是权宜之计。它们有一些边角情况(滚动+软键盘问题,EditText中插入符号位置的变化)。它们不能改变ListView在notifyDataSetChanged期间进行大量视图创建/销毁的事实。

使用RecyclerView,您可以通知单个插入、更新和删除。聚焦视图不会被重新创建,因此表单控件不会失去焦点。作为额外的奖励,RecyclerView还可以动画化列表项的插入和删除。

以下是从官方文档中获取有关如何开始使用RecyclerView的示例: 开发人员指南-使用RecyclerView创建列表


1

最重要的部分是让列表单元格的焦点正常工作。 特别是对于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 导航也非常重要。

更多细节请查看其他出色的答案。


1

有时候在manifest activity或xml中使用android:windowSoftInputMode="stateAlwaysHidden"会导致失去键盘焦点。因此,首先检查xml和manifest中是否存在该属性,如果有,请将其删除。然后在activity中的manifest文件中添加这些选项android:windowSoftInputMode="adjustPan",并在xml中将此属性添加到listviewandroid:descendantFocusability="beforeDescendants"


0
另一个简单的解决方案是在您的ListAdapter的getView(..)方法中定义您的onClickListener。
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
这个问题涉及到焦点问题,而不是可点击性。 - Joe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接