【问题标题】:How to use Android Spinner like a drop-down list如何像下拉列表一样使用 Android Spinner
【发布时间】:2011-12-19 23:55:17
【问题描述】:

我花了很长时间才了解 Android Spinner。在几次失败的实施尝试之后,在阅读了许多与我自己的问题部分相似但without satisfactory answers 的问题之后,有些根本没有任何答案,例如herehere,我终于明白,Android 中的“微调器”与桌面应用程序中的“下拉列表”或 HTML 中的选择并不相同。但是,我的应用程序(我猜所有其他类似问题的发帖人的应用程序)需要的是像下拉框一样工作的东西,而不是像微调器一样的东西。

我的两个问题是我最初认为是 OnItemSelectedListener 的特质(我在本网站上将这些问题视为单独的问题,但不是一个问题):

  1. 第一个列表项的初始选择会自动触发,无需用户交互。
  2. 当用户再次选择已选择的项目时,将被忽略。

现在我意识到,当您考虑它时,在 微调器 上发生这种情况是有意义的 - 它必须从选择的默认值开始,而您旋转它只是为了改变它值,而不是“重新选择”一个值 - the documentation 实际上说:“仅当新选择的位置与先前选择的位置不同时才会调用此回调”。我已经看到answers suggesting that you set up a flag 忽略了第一个自动选择 - 我想如果没有其他办法的话我可以忍受。

但是,由于我真正想要的是一个下拉列表,它的行为应该像下拉列表一样(并且用户可以并且应该期望),所以我需要的是一个 类似 的 Spinner表现得像一个下拉菜单,像一个组合框。我不关心任何自动预选(应该在不触发我的听众的情况下发生),并且我想知道 每个 选择,即使它与以前相同(毕竟,用户再次选择相同的项目)。

那么... Android 中有没有可以做到这一点的东西,或者有一些解决方法可以让 Spinner 表现得像一个下拉列表?如果 在这个网站上有一个类似的问题我没有找到,并且有一个令人满意的答案,请告诉我(在这种情况下,我真诚地为重复这个问题道歉)。

【问题讨论】:

    标签: android spinner selection


    【解决方案1】:

    看。我不知道这是否会对您有所帮助,但由于您似乎厌倦了寻找没有成功的答案,所以这个想法可能会对您有所帮助,谁知道...

    Spinner 类派生自 AbsSpinner。在这里面,有这个方法:

    void setSelectionInt(int position, boolean animate) {
            if (position != mOldSelectedPosition) {
                mBlockLayoutRequests = true;
                int delta  = position - mSelectedPosition;
                setNextSelectedPositionInt(position);
                layout(delta, animate);
                mBlockLayoutRequests = false;
            }
        }
    

    这是来自1.5 source 的 AFAIK。也许你可以检查那个源,看看 Spinner/AbsSpinner 是如何工作的,也许可以扩展那个类来捕捉正确的方法,检查position != mOldSelectedPosition是否。

    我的意思是...这是一个巨大的“可能”,有很多“如果”(想到 Android 版本控制等),但由于您似乎很沮丧(而且我已经多次使用 Android),也许这可以给你一些“光”。而且我认为通过查看您之前的研究,没有其他明显的答案。

    祝你好运!

    【讨论】:

    • 谢谢,大卫,我一定会看看的。我必须先下载 Android 源代码(对于 Android 来说相对较新,您可能会说 ;-)。找出令人沮丧的一点,有些事情并没有像我想象的那样工作,其他时候看起来很复杂的事情却可以工作,但我想当你必须学习一个新的 API 时,情况总是如此。
    • 您还可以从 Eclipse 内部浏览源代码。请参阅vogella.de/articles/AndroidSourceCode/… 这将使您的工作更加轻松。
    • 再次干杯,大卫。听起来很有用。我也会投票赞成你的帖子,但似乎还没有所需的声誉;-)
    • 别担心。如果这对您有帮助,那么这很重要。
    • 另外,为了澄清费利克斯的回答指出:那个代码块不是你会使用的。我只是指出了源代码中的代码块,以进行研究。该解决方案将涉及扩展类并覆盖所述方法。如何做到这一点取决于你,但他的代码似乎很聪明。我先试试看。
    【解决方案2】:

    +1 对大卫的回答。但是,这里有一个不涉及从源代码复制粘贴代码的实现建议(顺便说一下,这看起来与 David 发布的 in 2.3 as well 完全相同):

    @Override
    void setSelectionInt(int position, boolean animate) {
        mOldSelectedPosition = INVALID_POSITION;
        super.setSelectionInt(position, animate);
    }
    

    这样你会欺骗父方法每次都认为它是一个新位置。

    或者,您可以尝试在单击微调器时将位置设置为无效,然后将其设置回onNothingSelected。这不是很好,因为当对话框启动时用户将看不到选择了什么项目。

    【讨论】:

    • 是的,扩展类绝对是要走的路。巧妙的实现,顺便说一句。
    • 谢谢菲利克斯,我 +1。您更改选择的想法帮助我提出了解决我的两个问题的替代方案(也防止了自动初始选择)。但是,由于我的解决方案可能仅适用于更具体的用例(并且由于我不想冒昧并接受基于您的答案的自己的答案),因此我很乐意为您打勾。干杯!
    • 在 MySpinner 中我尝试了以下方法来重置旧的选择,但它不起作用。 @Override public void setSelection(int position) { super.setSelection(INVALID_POSITION); super.setSelection(位置); }
    • 如何覆盖包私有方法?我错过了什么吗?
    【解决方案3】:

    好的,在 David 和 Felix 的回答的帮助下,我想我已经为自己的情况想出了一个解决方案(我相信 David 帮助了 Felix,这反过来又帮助了我)。我想我会将它与代码示例一起发布在这里,以防其他人发现这种方法也很有用。它还解决了我的两个问题(不需要的自动选择和所需的重新选择触发器)。

    我所做的是添加一个“请选择”虚拟项目作为我列表中的第一项(最初只是为了解决自动选择问题,以便我可以在选择时忽略它没有用户交互),然后,选择另一个另一个 em>项目并且我处理了选择时,我简单地重置 em>旋转器到虚拟项目(忽略)。想一想,在决定在这个网站上发布我的问题之前,我早就应该想到这一点,但事后看来,事情总是更明显......我发现写我的问题实际上帮助我思考了什么我想实现。

    显然,如果有一个虚拟项目不适合您的情况,这可能不是您的理想解决方案,但因为我想要的是在用户选择一个值时触发一个操作(并且让该值保持选中状态)在我的特定情况下不需要),这很好用。我将尝试在下面添加一个简化的代码示例(可能无法按原样编译,我已经从我的工作代码中删除了一些内容并在粘贴之前重命名了一些内容,但希望你能明白这一点)。

    首先,包含微调器的列表活动(在我的例子中),我们称之为 MyListActivity:

    public class MyListActivity extends ListActivity {
    
        private Spinner mySpinner;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            // TODO: other code as required...
    
            mySpinner = (Spinner) findViewById(R.id.mySpinner);
            mySpinner.setAdapter(new MySpinnerAdapter(this));
            mySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
                @Override
                public void onItemSelected(AdapterView<?> aParentView,
                            View aView, int aPosition, long anId) {
                    if (aPosition == 0) {
                        Log.d(getClass().getName(), "Ignoring selection of dummy list item...");
                    } else {
                        Log.d(getClass().getName(), "Handling selection of actual list item...");
                        // TODO: insert code to handle selection
                        resetSelection();
                    }
                }
    
                @Override
                public void onNothingSelected(AdapterView<?> anAdapterView) {
                    // do nothing
                }
            });
        }
    
        /**
         * Reset the filter spinner selection to 0 - which is ignored in
         * onItemSelected() - so that a subsequent selection of another item is
         * triggered, regardless of whether it's the same item that was selected
         * previously.
         */
        protected void resetSelection() {
            Log.d(getClass().getName(), "Resetting selection to 0 (i.e. 'please select' item).");
            mySpinner.setSelection(0);
        }
    }
    

    微调器适配器代码可能看起来像这样(如果您愿意,实际上可以是上述列表活动中的内部类):

    public class MySpinnerAdapter extends BaseAdapter implements SpinnerAdapter {
    
        private List<MyListItem> items; // replace MyListItem with your model object type
        private Context context;
    
        public MySpinnerAdapter(Context aContext) {
            context = aContext;
            items = new ArrayList<MyListItem>();
            items.add(null); // add first dummy item - selection of this will be ignored
            // TODO: add other items;
        }
    
        @Override
        public int getCount() {
            return items.size();
        }
    
        @Override
        public Object getItem(int aPosition) {
            return items.get(aPosition);
        }
    
        @Override
        public long getItemId(int aPosition) {
            return aPosition;
        }
    
        @Override
        public View getView(int aPosition, View aView, ViewGroup aParent) {
            TextView text = new TextView(context);
            if (aPosition == 0) {
                text.setText("-- Please select --"); // text for first dummy item
            } else {
                text.setText(items.get(aPosition).toString());
                // or use whatever model attribute you'd like displayed instead of toString()
            }
            return text;
        }
    }
    

    我想(没有尝试过)使用setSelected(false) 而不是setSelection(0) 可以达到相同的效果,但是重新设置为“请选择”很适合我的目的。而且,“看,妈,没有旗帜!” (虽然我猜忽略 0 的选择并没有那么不同。)

    希望这可以帮助其他有类似用例的人。 :-) 对于其他用例,Felix 的回答可能更合适(感谢 Felix!)。

    【讨论】:

    • 如果需要删除第一行,我们可以在getDropDownView方法中添加这一行 if (position == 0) { view.getLayoutParams().height = 1; } else { view.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT; }
    【解决方案4】:

    如果您希望在同一活动中同时进行多个选择,则修改微调器很有用。 如果您只希望用户进行分层选择,例如:

    你想吃什么?

    水果

    • 苹果
    • 香蕉
    • 橙子

    快餐

    • 汉堡
    • 薯条
    • 热狗,

    那么ExpandableListView 可能更适合您。它允许用户导航不同组的层次结构并选择子元素。这类似于有多个 Spinner 供用户选择 - 如果您不希望同时进行选择。

    【讨论】:

      【解决方案5】:

      在我意识到PopupMenu 小部件是我真正想要的之前,我解决了这个线程中提到的几个问题。这很容易实现,无需更改 Spinner 的功能所需的技巧和变通方法。 PopupMenu 在 2011 年启动此线程时相对较新,但我希望这对现在正在搜索类似功能的人有所帮助。

      【讨论】:

      • PopupMenu 没有持久选择
      • 它没有用于设置所选项目的内置属性,但您可以使用 myPopupMenu.getMenu().getItem(selectedIndex).setChecked(true) 设置选择。
      【解决方案6】:

      这是区分任何(有意或无意的)程序化和用户发起的更改的替代解决方案:

      为微调器创建你的监听器作为 OnTouchListener 和 OnItemSelectedListener

      public class SpinnerInteractionListener implements AdapterView.OnItemSelectedListener, View.OnTouchListener {
      
          boolean userSelect = false;
      
          @Override
          public boolean onTouch(View v, MotionEvent event) {
              userSelect = true;
              return false;
          }
      
          @Override
          public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
              if (userSelect) { 
                  // Your selection handling code here
                  userSelect = false;
              }
          }
      
      }
      

      将侦听器添加到为两种事件类型注册的微调器

      SpinnerInteractionListener listener = new SpinnerInteractionListener();
      mSpinnerView.setOnTouchListener(listener);
      mSpinnerView.setOnItemSelectedListener(listener);
      

      这不会处理用户重新选择同一项目不会触发 onItemSelected 方法(我没有观察到)的情况,但我想这可以通过添加一些代码来处理onTouch 方法。

      无论如何,Amos 指出的问题让我在想到这个解决方案之前就快疯了,所以我想我会尽可能广泛地分享。有很多讨论这个问题的线程,但到目前为止我只看到了一个与此类似的其他解决方案:https://stackoverflow.com/a/25070696/4556980

      【讨论】:

      • 我真的很喜欢这个答案,谢谢。我在 onItemSelected 块中发出了网络请求,并且不想在设置微调器侦听器时仔细检查用户的数据计划
      猜你喜欢
      • 2023-03-07
      • 1970-01-01
      • 2010-12-29
      • 1970-01-01
      • 2010-12-27
      • 1970-01-01
      • 1970-01-01
      • 2011-02-23
      • 1970-01-01
      相关资源
      最近更新 更多