Spinner无法自动换行文本--这是Android的一个bug吗?

69
如果Spinner项目的文本过长而无法适应单行显示,文本不会自动换行而是被截断。这种情况仅在API level >= 11时发生。以下是Android 4.2.2(左侧)错误行为和Android 2.3.3(右侧)期望效果的屏幕截图。

"

android:singleLine="false" 在这里被忽略了。其他尝试,如 android:linesandroid:minLines 等也是如此。TextView 似乎比窗口宽度要宽得多。

我看到其他人也有同样的问题,但没有人能找到解决方案。那么,这是系统bug吗?我不认为操作系统版本之间的不一致是有意的。

"

请注意:

有一些答案提出了相对简单的解决方案。

  • 编写自定义 Adapter 并覆盖 getView()getDropDownView()。但是这并不是解决方案,因为在这个阶段仍然存在一个原始问题:布局应该如何处理适当的换行?

  • 将下拉视图的 TextView 包装到父 ViewGroup 中。如果使用 android:layout_width="match_parent",由于父元素的宽度奇怪地似乎是无限的,所以这种方法不起作用。

  • 给下拉视图一个固定的宽度。这不适用于 Spinner 可能具有的不同宽度。

  • 当然,没有一个解决方案是手动在文本中任意插入 \n


请使用下面的代码进行复现:

更新:我也将其上传到GitHub作为一个示例项目:Download

/res/values/arrays.xml:

<string-array name="items">
    <item>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</item>
    <item>At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est.</item>
</string-array>

/res/layout/spinner_item.xml:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    style="?android:attr/spinnerDropDownItemStyle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:ellipsize="none"
    android:minHeight="?android:attr/listPreferredItemHeight"
    android:singleLine="false" />

设置 Adapter

spinner.setAdapter(ArrayAdapter.createFromResource(this,
            R.array.items,
            R.layout.spinner_item));

1
你需要创建一个自定义适配器类,继承基础适配器,并使用getDropDownView()方法,然后将该适配器设置到下拉框中即可。 - Piyush
2
查看Android源代码,Spinner的2.3.3版本(链接)将简单的AlertDialog显示为其下拉菜单,而4.1.1版本(链接)则显示其自己的DropdownPopup内部类(ListPopupWindow的子类)。研究这种差异可能是回答此问题的良好起点。 - XåpplI'-I0llwlg'I -
尝试使用layout_weight=1,这可能会起作用。 - Ajay
为什么不向安卓报告并看看他们对此有何说法? - user1545072
我认为这只有在使用全息主题时才是这种情况。 - lokoko
这里是关于b.android.com的错误报告 - Matthias Robbers
15个回答

36

在hollow主题下,默认情况下spinner使用下拉模式。所有针对覆盖默认样式的移动都会转换为对话框模式,该模式可以像api 11中一样成功包装多行文本。您也可以使用new Spinner(context, Spinner.MODE_DIALOG)或在xml中创建具有android:spinnerMode="dialog"属性的spinner。但这并未解决问题,因为它是对话框而不是下拉框。

我找到了另一种解决方法:在ArrayAdapter中覆盖getDropDownView方法,并在视图的post方法中放置setSingleLine(false)。因此,当视图完全创建时,它会将文本包装到适当的行中。

@Override
public View getDropDownView(final int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = new TextView(_context);
    }

    TextView item = (TextView) convertView;
    item.setText("asddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd");
    final TextView finalItem = item;
    item.post(new Runnable() {
        @Override
        public void run() {
            finalItem.setSingleLine(false);
        }
    });
    return item;
}

更新:

下面提供了另一个答案。

手动将ListView包裹在PopupWindow中,在点击TextView时显示它,并在点击列表项时隐藏它。

这只是一个简单的实现,仅用于展示思路:

public class MySpinner extends TextView {
    private PopupWindow _p;
    private ListView _lv;
    public MySpinner(Context context) {
        super(context);
        init();
    }
    public MySpinner(Context context, AttributeSet attributeSet){
        super(context, attributeSet);
        init();
    }

    private void init(){
        setBackgroundResource(R.drawable.spinner_background);
        final List<String> list = new ArrayList<String>();
        list.add("Very long text AAAAAAAAAAAAAAAA");
        list.add("1 Very long text AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
        list.add("2 Very long text A");
        list.add("3 Very long text AAAAAAAAA");

        setMinimumWidth(100);
        setMaxWidth(200);

        _lv = new ListView(getContext());
        _lv.setAdapter(new ArrayAdapter<String>(getContext(), R.layout.simple_list_item_1, list));
        _lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                _p.dismiss();
                setText(list.get(i));
            }
        });

        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {

                _p = new PopupWindow(getContext());
                _p.setContentView(_lv);
                _p.setWidth(getWidth());
                _p.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
                _p.setTouchable(true);
                _p.setFocusable(true);
                _p.setOutsideTouchable(true);
                _p.showAsDropDown(view);
            }
        });
    }
}

好的解决方案!在Android 4.4中仍然没有修复这个错误,所以这个答案非常有用。 - Matthias Robbers
似乎调用setSingleLine方法会强制在TextView显示在屏幕上后重新计算Spinner下拉列表的布局。我拒绝了这个决定,因为当您滚动项目时,滚动条的大小会动态更改,并且下拉项会在模拟器上闪烁。使用PopupWindow和ListView创建自己的Spinner非常简单-请参见我的下一个答案。 - Kolchuga
哇,解决这个问题的方法真糟糕。但是它还是能够正常工作的 :) - Totumus Maximus
使用Spinner.Mode_DIALOG对我很有用!好处是我已经在动态添加一个下拉框。缺点是它强制最小API为11。 - akvallejos
1
解决方法:不需要创建finalItem,只需将item声明为final,并将其设置为setSingleLine(false),就可以按预期工作。另一种方法是不直接在程序中创建TextView,而是从xml创建并将其包装在LinearLayout或RelativeLayout中。这也将解决该问题。 - Bhargav Jhaveri

18

这里只有一种解决方案的组合对我起作用了(在Android 5.1上测试过):

spinner_item.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent"
             android:layout_height="wrap_content">

  <TextView
    android:id="@android:id/text1"
    style="?android:attr/spinnerItemStyle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:singleLine="false"
    android:textAlignment="inherit"/>
</LinearLayout>

代码

  final ArrayAdapter<String> spinnerArrayAdapter=new ArrayAdapter<String>(activity,R.layout.spinner_item,android.R.id.text1,spinnerItemsList)
  {
  @Override
  public View getDropDownView(final int position,final View convertView,final ViewGroup parent)
    {
    final View v=super.getDropDownView(position,convertView,parent);
    v.post(new Runnable()
    {
    @Override
    public void run()
      {
      ((TextView)v.findViewById(android.R.id.text1)).setSingleLine(false);
      }
    });
    return v;
    }
  };
  spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

1
谢谢,我使用了 XML 文件来设置下拉列表项...现在它可以正常工作了。 - madhu527
TextViewLinearLayout包装帮助我实现了多行文本显示!谢谢! - Max Diland

13

我通过切换到对话框式微调器解决了这个问题:

<Spinner
  ...
android:spinnerMode="dialog" />

10
这并不是解决问题,而是避免问题。 - Vlado Pandžić

9
在TextView周围添加LinearLayout可以使文本正确换行。
布局(common_domainreferencemodel_spinner_item.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:padding="4dp">

       <TextView
            android:id="@+id/nameTextView"
            android:singleLine="false"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

</LinearLayout>

Adapter:

public class DomainReferenceModelAdapter extends ArrayAdapter<DomainReferenceModel> {

    private List<DomainReferenceModel> objects;
    private LayoutInflater inflater;
    private int oddRowColor = Color.parseColor("#E7E3D1");
    private int evenRowColor = Color.parseColor("#F8F6E9");

    public DomainReferenceModelAdapter(Context context, int resource, List<DomainReferenceModel> objects) {
        super(context, resource, objects);
        this.objects = objects;
        this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    static class ViewHolder {
        public TextView nameTextView;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        return getViewInternal(position, convertView, parent, false);
    }

    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        return getViewInternal(position, convertView, parent, true);
    }

    private View getViewInternal(int position, View convertView, ViewGroup parent, boolean isDropdownView) {
        View view = convertView;
        if (view == null) {
            view = inflater.inflate(R.layout.common_domainreferencemodel_spinner_item, null);
            ViewHolder viewHolder = new ViewHolder();
            viewHolder.nameTextView = (TextView) view.findViewById(R.id.nameTextView);
            view.setTag(viewHolder);
        }
        if (isDropdownView) {
            view.setBackgroundColor(position % 2 == 0 ? evenRowColor : oddRowColor);
        }
        ViewHolder holder = (ViewHolder) view.getTag();
        DomainReferenceModel model = objects.get(position);
        holder.nameTextView.setText(model.getName());
        return view;
    }

}

8
在使用Holo主题时,实现Spinner的多行下拉项目是不可能的,这是我试过的。解决方法是:
  • Spinner创建一个不继承自Holo的样式。这将启用多行下拉项目。
  • 手动对Spinner进行样式设计,使其看起来像是Holo主题。
这将产生以下效果(显示关闭和打开状态):

enter image description here

具体实现细节如下:
在我看来,即使将下拉项目的TextView singleLine属性设置为false并提供自定义布局,也没有办法从Holo主题继承到Spinner并在Spinner中显示多行。我还尝试了保留Holo样式但更改了
android:spinnerStyle
android:spinnerItemStyle 
android:spinnerDropDownItemStyle 

样式属性(使用这些属性的示例在此处),但我无法使其产生多行结果。

然而,如果我们覆盖Spinner的样式,并且不从Holo继承spinnerStyle

 <style name="AppTheme" parent="android:Theme.Holo.Light">
    <item name="android:spinnerStyle">@style/spinnerStyle</item>
</style>

<--no parent attribute-->
 <style name="spinnerStyle">
    <item name="android:clickable">true</item>
</style>

那么下拉菜单项将支持显示多行。但现在我们失去了Holo主题在Spinner上,关闭状态看起来像一个TextView而不是Spinner,没有箭头或视觉提示它是一个Spinner。如果我们改为将spinnerStyle的父级设置为:parent="android:style/Widget.Spinner

<style name="spinnerStyle" parent="android:style/Widget.Spinner">
    <item name="android:clickable">true</item>
</style>

Spinner关闭状态将显示箭头,但样式类似于灰色的pre-Holo Spinner,看起来与Holo应用不协调。因此,一种可能的解决方案是:

  • 覆盖spinnerStyle主题并不使用父级的Holo。这将使DropDown项中的文本多行显示。
  • 更改Spinner背景以使其看起来继承了Holo主题。

以下是一个示例:

创建一个基本的Activity:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Spinner spinner = (Spinner)findViewById(R.id.styled_spinner);

    spinner.setAdapter(ArrayAdapter.createFromResource(this,
            R.array.items,
            R.layout.spinner_item));        
}

活动布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="50dip"
    tools:context=".MainActivity" >

    <Spinner
        android:id="@+id/styled_spinner"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

样式:

<resources xmlns:android="http://schemas.android.com/apk/res/android">
    <style name="AppTheme" parent="android:Theme.Holo.Light">
        <item name="android:spinnerStyle">@style/spinnerStyle</item>
    </style>
    <style name="spinnerStyle">
        <item name="android:clickable">true</item>
        <item name="android:background">@drawable/spinner_background_holo_light</item>
    </style>
</resources>

在drawable文件夹中,放置spinner_background_holo_light:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_enabled="false"
        android:drawable="@drawable/spinner_disabled_holo_light" />
    <item android:state_pressed="true"
        android:drawable="@drawable/spinner_pressed_holo_light" />
    <item android:state_pressed="false" android:state_focused="true"
        android:drawable="@drawable/spinner_focused_holo_light" />
    <item android:drawable="@drawable/spinner_default_holo_light" />
</selector>

将这些可绘制对象包含在您的drawables-hdpi文件夹中:

enter image description here spinner_default_holo_light.9.png

enter image description here spinner_disabled_holo_light.9.png

enter image description here spinner_focused_holo_light.9.png

enter image description here spinner_pressed_holo_light.9.png

这将产生一个带有Holo主题的关闭状态和多行项目的下拉菜单,如上面的屏幕截图所示。

此示例中的下拉项不是Holo主题,但如果多行显示下拉项真的很重要,则这可能是可以接受的折衷方案。

在此示例中,清单中的android:minSdkVersion设置为14android:targetSdkVersion设置为17

Holo图形和spinner_background_holo_light.xml代码来自HoloEverywhere版权所有(c)2012 Christophe Versieux,Sergey Shatunov。有关许可详细信息,请参见链接到github项目。


这对我非常有帮助,spinner_item.xml 中具体包含什么?我在 spinner_item.xml 的 checkedtextview 中使用 style="@style/spinnerStyle" 吗?我需要问一下,因为当我点击该项时,没有任何列表弹出,只是高亮显示。 - CQM
@CQM 这是一个宽度设置为fill_parent,高度设置为wrap_content的TextView。 - Gunnar Karlsson
谢谢,这个是设置给哪个对象的?style="@style/spinnerStyle" - CQM
在应用主题中包含<item name="android:spinnerStyle">@style/spinnerStyle</item>,可以将其应用为旋转选择器的样式(在上面的示例中为“AppTheme”)。 - Gunnar Karlsson

6

我遇到了同样的问题。我想在下拉列表中看到2行,但是我找到的所有解决方案似乎都不能解决这样一个简单的问题。我调查了Spinner源代码,并发现如果我们使用自定义 .xml文件并带有属性android:singleLine ="false"

<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/multiline_spinner_text_view"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:singleLine="false" />

以下代码在 ListPopupWindow 中执行,使用默认的 ArrayAdapter。

    @Override
       View More obtainView(int position, boolean[] isScrap) {
            View view = super.obtainView(position, isScrap);

           if (view instanceof TextView) {
                ((TextView) view).setHorizontallyScrolling(true);
            }

            return view;        
}

这就是为什么我们在每个列表行中只看到一条字符串线,它实际上是滚动的。

为了解决这种问题,我们的视图不应该是TextView的实例,而是将您的TextView放在FrameLayout或LinearLayout中。

   <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <CheckedTextView
            android:id="@+id/multiline_spinner_text_view"
            android:layout_width="fill_parent"
            android:layout_height="?android:attr/listPreferredItemHeight"
            android:singleLine="false" />

    </LinearLayout>

并且。
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, 
               R.layout.multiline_spinner_dropdown_item,R.id.multiline_spinner_text_view,
                awasomeListValues);

这个解决方案适用于下拉框的两种模式:MODE_DROPDOWN 和 MODE_DROPDOWN。希望它能对你有帮助!

4

我认为安卓系统存在一个bug。你可以尝试这个方法:将文本中的空格去掉,然后再显示,就会正常工作。如果TextView的长度小于字符串长度,则会忽略空格后面的所有字符。

如果需要解决问题,你可以尝试以下方法:

在res/layout文件夹中添加一个名为multiline_spinner_dropdown_item.xml的文件,其中包含以下示例代码:

<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
style="?android:attr/spinnerDropDownItemStyle"
android:singleLine="false"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:ellipsize="marquee" />

当您创建旋转器时,请使用此布局进行创建。

例如:

ArrayAdapter.createFromResource(this, items, R.layout.multiline_spinner_dropdown_item);

基本上,将android.R.layout.simple_spinner_dropdown_item布局复制到项目中,并通过在CheckedTextView中将singleLine属性设置为false来修改该布局。


API 27运行良好。 - Simão Garcia

3

我刚刚发现安卓已经针对这种情况设置了样式

final Spinner pelanggaran = (Spinner) findViewById(R.id.pelanggaran);
ArrayAdapter<CharSequence> pelanggaran_adapter = ArrayAdapter.createFromResource(this,R.array.pelanggaran_array, android.R.layout.simple_expandable_list_item_1);
pelanggaran_adapter.setDropDownViewResource(android.R.layout.simple_expandable_list_item_1);
pelanggaran.setAdapter(pelanggaran_adapter);

希望这解决了你的问题。


2

以下是我所做的使其正常工作的步骤:

ArrayAdapter<KeyValue> adapter = new ArrayAdapter<>(getContext(), R.layout.simple_dropdown_item_multiline, R.id.nameTextView, choices);

这是“simple_dropdown_item_multiline”的内容:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="wrap_content">

<TextView android:id="@+id/nameTextView"
          style="?android:attr/dropDownItemStyle"
          xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:ellipsize="marquee"
          android:paddingBottom="@dimen/large"
          android:paddingTop="@dimen/large"
          android:singleLine="false"
          android:textAppearance="?android:attr/textAppearanceLargePopupMenu"/>


2
阅读此答案:textview casting error:- android.widget.LinearLayout cannot be cast to android.widget.TextView以及此主题,我可以解决这个问题:我们需要一个包装TextView(下拉列表文本)的LinearLayout来避免文本溢出屏幕,但是我们需要解决一些问题。首先,创建布局文件(我将其称为spinner_dd_item.xml):
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:id="@+id/simple_spinner_dropdown"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:paddingBottom="5dp"
            android:paddingLeft="10dp"
            android:paddingRight="10dp"
            android:paddingTop="5dp"
            android:textColor="@color/colorAccent"
            tools:text="Hello" />
    </LinearLayout>

下一步是创建一个ArrayAdapter实例并将其设置到Spinner中:
    ArrayAdapter<CharSequence> arrayAdapter = new ArrayAdapter<CharSequence>(getActivity(), R.layout.spinner_dd_item,
            R.id.simple_spinner_dropdown, hashmapToString(hashMap, keys)) {
        @Override
        public View getDropDownView(int position, View convertView, ViewGroup parent) {
            return getView(position, convertView, parent);
        }
    };
    spinner.setAdapter(arrayAdapter);

不要忘记在ArrayAdapter中添加布局名称和TextView id,因为我们添加了LinearLayout并且需要指定TextView。同时重写getDropDownView以获取在数据集中指定位置显示数据的视图。现在,我们可以看到Spinner在新旧Android版本上都能很好地工作。


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