安卓:如何将下拉框与自定义对象列表绑定?

151
在用户界面中,必须有一个包含一些名称(名称可见)且每个名称都有其自己的ID(ID不等于显示序列)的旋转器。当用户从列表中选择名称时,变量currentID必须更改。该应用程序包含ArrayList, 其中User是带有ID和名称的对象。
public class User{
        public int ID;
        public String name;
    }

我不知道如何创建一个下拉菜单,其中显示用户名称列表并将下拉菜单项绑定到ID,因此当选择/更改下拉菜单项时,变量currentID设置为适当的值。

如果有人能够提供解决该问题的解决方案或任何有用的链接,我会非常感激。

谢谢!

17个回答

391

我知道这个帖子已经老旧了,但是以防万一...

用户对象:

public class User{

    private int _id;
    private String _name;

    public User(){
        this._id = 0;
        this._name = "";
    }

    public void setId(int id){
        this._id = id;
    }

    public int getId(){
        return this._id;
    }

    public void setName(String name){
        this._name = name;
    }

    public String getName(){
        return this._name;
    }
}

自定义Spinner适配器(ArrayAdapter)

public class SpinAdapter extends ArrayAdapter<User>{

    // Your sent context
    private Context context;
    // Your custom values for the spinner (User)
    private User[] values;

    public SpinAdapter(Context context, int textViewResourceId,
            User[] values) {
        super(context, textViewResourceId, values);
        this.context = context;
        this.values = values;
    }

    @Override
    public int getCount(){
       return values.length;
    }

    @Override
    public User getItem(int position){
       return values[position];
    }

    @Override
    public long getItemId(int position){
       return position;
    }


    // And the "magic" goes here
    // This is for the "passive" state of the spinner
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // I created a dynamic TextView here, but you can reference your own  custom layout for each spinner item
        TextView label = (TextView) super.getView(position, convertView, parent);
        label.setTextColor(Color.BLACK);
        // Then you can get the current item using the values array (Users array) and the current position
        // You can NOW reference each method you has created in your bean object (User class)
        label.setText(values[position].getName());

        // And finally return your dynamic (or custom) view for each spinner item
        return label;
    }

    // And here is when the "chooser" is popped up
    // Normally is the same view, but you can customize it if you want
    @Override
    public View getDropDownView(int position, View convertView,
            ViewGroup parent) {
        TextView label = (TextView) super.getDropDownView(position, convertView, parent);
        label.setTextColor(Color.BLACK);
        label.setText(values[position].getName());

        return label;
    }
}

实现方式如下:

public class Main extends Activity {
    // You spinner view
    private Spinner mySpinner;
    // Custom Spinner adapter (ArrayAdapter<User>)
    // You can define as a private to use it in the all class
    // This is the object that is going to do the "magic"
    private SpinAdapter adapter;

        @Override
        public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // Create the Users array
        // You can get this retrieving from an external source
        User[] users = new User[2];

        users[0] = new User();
        users[0].setId(1);
        users[0].setName("Joaquin");

        users[1] = new User();
        users[1].setId(2);
        users[1].setName("Alberto");

        // Initialize the adapter sending the current context
        // Send the simple_spinner_item layout
        // And finally send the Users array (Your data)
        adapter = new SpinAdapter(Main.this,
            android.R.layout.simple_spinner_item,
            users);
        mySpinner = (Spinner) findViewById(R.id.miSpinner);
        mySpinner.setAdapter(adapter); // Set the custom adapter to the spinner
        // You can create an anonymous listener to handle the event when is selected an spinner item
        mySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {

            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view,
                    int position, long id) {
                // Here you get the current item (a User object) that is selected by its position
                User user = adapter.getItem(position);
                // Here you can do the action you want to...
                Toast.makeText(Main.this, "ID: " + user.getId() + "\nName: " + user.getName(),
                    Toast.LENGTH_SHORT).show();
            }
            @Override
            public void onNothingSelected(AdapterView<?> adapter) {  }
        });
    }
}

48
这应该是被接受的答案。创建自定义适配器绝对是正确的方法。 - jamesc
12
这个工作很好,非常不错。但有一个问题,下拉菜单的样式现在改变了。我尝试设置新的 XML 文件来改变填充和文本大小,但没有任何效果。即使我从 XML 更改下拉菜单自身,仍然没有任何效果。唯一会改变的是如果我从 SpinAdapter 中更改 TextView 的文本大小。有没有办法保持默认的下拉菜单样式/主题,同时加载这些值? - lantonis
1
我已经做了这个,但是我遇到了巨大的延迟。虽然我只添加了3次。我通过填充视图来创建我的布局,它只包含一个图标和文本。Logcat通过说“跳过了317帧!应用程序可能在其主线程上执行太多工作。”来确认我的问题。有什么想法吗? - CularBytes
3
用户 user = 适配器.getItem(position); (将 Java 代码中的变量名和方法名翻译成中文) - Ahmad Alkhatib
3
为了重复使用视图,而不是创建一个新的TextView,需要进行以下修改:在方法中添加如下代码: TextView label = (TextView) super.getView(position, convertView, parent) - jackcar
显示剩余5条评论

127

最简单的解决方案

在SO上搜索不同的解决方案后,我发现以下是用自定义对象填充Spinner的最简单、最干净的解决方案。以下是完整的实现:

User.java

public class User{
    public int ID;
    public String name;

    @Override
    public String toString() {
        return this.name; // What to display in the Spinner list.
    }
}    

res/layout/spinner.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:padding="10dp"
    android:textSize="14sp"
    android:textColor="#FFFFFF"
    android:spinnerMode="dialog" />

res/layout/your_activity_view.xml

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

    <Spinner android:id="@+id/user" />

</LinearLayout>

在您的活动中

List<User> users = User.all(); // This example assumes you're getting all Users but adjust it for your Class and needs.
ArrayAdapter userAdapter = new ArrayAdapter(this, R.layout.spinner, users);

Spinner userSpinner = (Spinner) findViewById(R.id.user);
userSpinner.setAdapter(userAdapter);
userSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        // Get the value selected by the user
        // e.g. to store it as a field or immediately call a method
        User user = (User) parent.getSelectedItem();
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
    }
});

一个小的注意点是,当Spinner的值改变时,它并不会立即设置currentID。大多数情况下,你只需要在随后点击按钮(如Submit或Save)之后获取Spinner的值,而不是立即在Spinner改变后获取值,如果可以避免这样做,那么这将提供一个更简单的解决方案。 - Joshua Pinter
@x13 没错。要在更改时获取值,您需要设置一个“on change”监听器,然后将getSelectedItem()调用放入其中。感谢您的提示。 - Joshua Pinter
@theapache64 点赞...每天都能学到新东西。 :) - Joshua Pinter
5
已经过去了3年,工作非常出色!难以相信人们会把这个简单的东西搞得那么复杂。 - Juan De la Cruz
1
@JuanDelaCruz Android和Java很容易让事情变得过于复杂化。简化才是胜利! - Joshua Pinter
显示剩余2条评论

58

对于简单的解决方案,您只需覆盖对象中的 "toString" 方法

public class User{
    public int ID;
    public String name;

    @Override
    public String toString() {
        return name;
    }
}

然后你可以使用:

ArrayAdapter<User> dataAdapter = new ArrayAdapter<User>(mContext, android.R.layout.simple_spinner_item, listOfUsers);

这样做可以使您的旋转器仅显示用户名。


如何将EDIT上的Spinner设置为返回响应中的选定项? - Arnold Brown
1
那个覆盖方法发挥了很好的作用。我忘记在我的数据类中添加这个覆盖字符串方法了。 - Agilanbu

47

您可以查看此答案。对于简单的情况,下面的解决方案也是可以的,但您也可以选择自定义适配器。

以下是重新发布的内容:

因此,如果您想在Spinner中同时显示标签和值,可以按照以下步骤执行:

  1. 按照通常的方式创建Spinner
  2. array.xml文件中定义两个大小相等的数组--一个用于标签,一个用于值
  3. 使用android:entries="@array/labels"设置您的Spinner
  4. 当您需要一个值时,可以像这样操作(不,您不必将其链接起来):

      String selectedVal = getResources().getStringArray(R.array.values)[spinner.getSelectedItemPosition()];
    

3
有没有一种优雅的方式可以访问已定义的标签(以便与selectedVal进行比较),从而避免在代码中硬编码字符串标签? - Anti Earth
27
从可扩展性的角度来看是非常错误的,这意味着你的“对象”永远无法动态变化,这是一种不良的做法。 - Srneczek
1
@Bostone我没有检查时间,但我认为这在这种情况下是没有关系的。适配器存在某些原因,我敢打赌这并不是关于SDK更改时间的问题。这是他们最初创建适配器的原因之一。所以你可以提供复杂对象列表,所以在我看来,这始终是一种不好的做法,只能在非常简单的情况下使用,但这并不意味着它是好的实践。 - Srneczek
@Srneczek,也许你应该检查答案和帖子的日期,这对用户来说是一个好的相关答案。 - user5530425
4
@Bob'sBurgers,你没有理解我的意思。我并没有说这种方法不能运行,而是说这是一种不好的做法,我的观点是正确的。你知道全局变量或者放在一个非常非常长的文件中的代码同样可以运行吧...另外,你应该评论旧的帖子,因为它们仍然会出现在今天的搜索结果中,人们可能会使用那些(今天)错误的答案。 - Srneczek
显示剩余12条评论

10

目前我发现最简单的方法是:

@Override
public String toString() {
    return this.label;           
}

现在您可以将任何物品粘在您的旋转器上,并且它将显示指定的标签。


9
只需要对Joaquin Alberto的回答进行一小点调整即可解决样式问题。只需在自定义适配器中将getDropDownView函数替换为以下内容,即可:
@Override
    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        View v = super.getDropDownView(position, convertView, parent);
        TextView tv = ((TextView) v);
        tv.setText(values[position].getName());
        tv.setTextColor(Color.BLACK);
        return v;
    }

1
这个回答实际上并没有回答问题,但它指出了一些与此回答相关的重要事项。 - Sruit A.Suk

7

受Joaquin Alberto的启发,以下方法对我有效:

public class SpinAdapter extends ArrayAdapter<User>{


    public SpinAdapter(Context context, int textViewResourceId,
            User[] values) {
        super(context, textViewResourceId, values);
    }



    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        TextView label = (TextView) super.getView(position, convertView, parent);
        label.setTextColor(Color.BLACK);
        label.setText(this.getItem(position).getName());
        return label;
    }

    @Override
    public View getDropDownView(int position, View convertView,ViewGroup parent) {
        TextView label = (TextView) super.getView(position, convertView, parent);
        label.setTextColor(Color.BLACK);
        label.setText(this.getItem(position).getName());
        return label;
    }
}

6

对我来说运行良好,需要在getResource()周围添加的代码如下:

spinner = (Spinner) findViewById(R.id.spinner);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

        @Override
        public void onItemSelected(AdapterView<?> spinner, View v,
                int arg2, long arg3) {
            String selectedVal = getResources().getStringArray(R.array.compass_rate_values)[spinner.getSelectedItemPosition()];
            //Do something with the value
        }

        @Override
        public void onNothingSelected(AdapterView<?> arg0) {
            // TODO Auto-generated method stub
        }

    });

只需要确保(自己)两个数组中的值对齐即可!


5

基于Joaquin Alberto(感谢)的示例,但适用于任何类型(您应该在类型中实现toString(),这样您就可以格式化输出。

import java.util.List;

import android.content.Context;
import android.graphics.Color;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class SpinAdapter<T> extends ArrayAdapter<T> {
private Context context;
private List<T> values;

public SpinAdapter(Context context, int textViewResourceId, List<T> values) {
    super(context, textViewResourceId, values);
    this.context = context;
    this.values = values;
}

public int getCount() {
    return values.size();
}

public T getItem(int position) {
    return values.get(position);
}

public long getItemId(int position) {
    return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    TextView label = new TextView(context);
    label.setTextColor(Color.BLACK);
    label.setText(values.toArray(new Object[values.size()])[position]
            .toString());
    return label;
}

@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
    TextView label = new TextView(context);
    label.setTextColor(Color.BLACK);
    label.setText(values.toArray(new Object[values.size()])[position]
            .toString());

    return label;
}
}

我认为你可以用数组替换列表,这样你就不需要在列表中执行toArray操作了,但是我手头上只有一个列表...... :)


4

kotlin:

data class User(
    var id: Long,
    var name : String? ){

    override fun toString(): String {
        return name
    }
}

实现

 mBinding.ddUser.setOnItemClickListener { parent, view, position, id ->
            val user = adapter.getItem(position)
            Log.i("idUser","${user?.idtoString()} - ${user?.name}")
        }

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